diff --git a/99-openlinkhub.rules b/99-openlinkhub.rules index 5dbdde4..8229f2d 100644 --- a/99-openlinkhub.rules +++ b/99-openlinkhub.rules @@ -66,5 +66,9 @@ SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b1c", ATTRS{idProduct}=="0a62", MODE="0660 SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b1c", ATTRS{idProduct}=="0a64", MODE="0660", OWNER="openlinkhub" SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b1c", ATTRS{idProduct}=="1bac", MODE="0660", OWNER="openlinkhub" SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b1c", ATTRS{idProduct}=="1b55", MODE="0660", OWNER="openlinkhub" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b1c", ATTRS{idProduct}=="1b6b", MODE="0660", OWNER="openlinkhub" SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b1c", ATTRS{idProduct}=="1b49", MODE="0660", OWNER="openlinkhub" -SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b1c", ATTRS{idProduct}=="1bb2", MODE="0660", OWNER="openlinkhub" \ No newline at end of file +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b1c", ATTRS{idProduct}=="1bb2", MODE="0660", OWNER="openlinkhub" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b1c", ATTRS{idProduct}=="2b01", MODE="0660", OWNER="openlinkhub" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b1c", ATTRS{idProduct}=="2b02", MODE="0660", OWNER="openlinkhub" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b1c", ATTRS{idProduct}=="2b14", MODE="0660", OWNER="openlinkhub" \ No newline at end of file diff --git a/README.md b/README.md index d808a71..49847a5 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Open source Linux interface for iCUE LINK Hub and other Corsair AIOs, Hubs. | VENGEANCE RGB | DDR5 | | | DOMINATOR PLATINUM RGB | DDR5 | | | DOMINATOR TITANIUM RGB | DDR5 | | -| Slipstream Wireless | `1bdc`
`1ba6`
`2b00` | K100 AIR RGB
IRONCLAW RGB WIRELESS
NIGHTSABRE WIRELESS
SCIMITAR RGB ELITE WIRELESS
M55 WIRELESS
DARK CORE RGB PRO SE WIRELESS
DARK CORE RGB PRO
M75 AIR WIRELESS
HARPOON RGB WIRELESS
DARKSTAR WIRELESS | +| Slipstream Wireless | `1bdc`
`1ba6`
`2b00` |
ShowK100 AIR RGB
IRONCLAW RGB WIRELESS
NIGHTSABRE WIRELESS
SCIMITAR RGB ELITE WIRELESS
M55 WIRELESS
DARK CORE RGB PRO SE WIRELESS
DARK CORE RGB PRO
M75 AIR WIRELESS
HARPOON RGB WIRELESS
DARKSTAR WIRELESS
K70 CORE TKL WIRELESS
| | K55 CORE RGB | `1bfe` | | | K65 PRO MINI | `1bd7` | | | K70 CORE RGB | `1bfd` | | @@ -52,6 +52,8 @@ Open source Linux interface for iCUE LINK Hub and other Corsair AIOs, Hubs. | K100 AIR RGB | `1bab` | USB | | K100 | `1bc5`
`1b7c`
`1b7d` | USB | | K70 RGB MK.2 | `1b55`
`1b49`
`1b6b` | USB | +| K70 CORE TKL | `2b01` | USB | +| K70 CORE TKL WIRELESS | `2b02` | USB | | KATAR PRO | `1b93` | DPI Control
RGB Control | | KATAR PRO XT | `1bac` | DPI Control
RGB Control | | KATAR PRO WIRELESS | `1b94` | DPI Control | diff --git a/database/keyboard/k100air-eu.json b/database/keyboard/k100air-eu.json index ea9b37f..14e9eb8 100644 --- a/database/keyboard/k100air-eu.json +++ b/database/keyboard/k100air-eu.json @@ -1390,7 +1390,7 @@ } }, "105": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, @@ -1455,7 +1455,7 @@ } }, "110": { - "keyName": "RC", + "keyName": "Menu", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k100air.json b/database/keyboard/k100air.json index 8c5d373..6610c09 100644 --- a/database/keyboard/k100air.json +++ b/database/keyboard/k100air.json @@ -49,7 +49,7 @@ "svg": true }, "4": { - "keyName": "", + "keyName": "LOGO", "width": 404, "height": 40, "left": 400, @@ -1390,7 +1390,7 @@ } }, "105": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, @@ -1455,7 +1455,7 @@ } }, "110": { - "keyName": "RC", + "keyName": "Menu", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k100airW-eu.json b/database/keyboard/k100airW-eu.json index 7807685..4c55db8 100644 --- a/database/keyboard/k100airW-eu.json +++ b/database/keyboard/k100airW-eu.json @@ -1395,7 +1395,7 @@ } }, "105": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, @@ -1460,7 +1460,7 @@ } }, "110": { - "keyName": "RC", + "keyName": "Menu", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k100airW.json b/database/keyboard/k100airW.json index 617bd52..8f0d68e 100644 --- a/database/keyboard/k100airW.json +++ b/database/keyboard/k100airW.json @@ -1395,7 +1395,7 @@ } }, "105": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, @@ -1460,7 +1460,7 @@ } }, "110": { - "keyName": "RC", + "keyName": "Menu", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k55core-eu.json b/database/keyboard/k55core-eu.json index 9a4d979..306496f 100644 --- a/database/keyboard/k55core-eu.json +++ b/database/keyboard/k55core-eu.json @@ -835,7 +835,7 @@ "zone": 1 }, "93": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, @@ -891,7 +891,7 @@ "zone": 6 }, "100": { - "keyName": "RC", + "keyName": "Menu", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k55core.json b/database/keyboard/k55core.json index 1e3780f..f5200cb 100644 --- a/database/keyboard/k55core.json +++ b/database/keyboard/k55core.json @@ -835,7 +835,7 @@ "zone": 1 }, "93": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, @@ -891,7 +891,7 @@ "zone": 6 }, "100": { - "keyName": "RC", + "keyName": "Menu", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k65plus-eu.json b/database/keyboard/k65plus-eu.json index 6b47d7a..fc51e02 100644 --- a/database/keyboard/k65plus-eu.json +++ b/database/keyboard/k65plus-eu.json @@ -963,7 +963,7 @@ } }, "73": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k65plus.json b/database/keyboard/k65plus.json index 20c410b..5eaaf08 100644 --- a/database/keyboard/k65plus.json +++ b/database/keyboard/k65plus.json @@ -963,7 +963,7 @@ } }, "73": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k65plusW-eu.json b/database/keyboard/k65plusW-eu.json index ebd7e3a..1e7afb5 100644 --- a/database/keyboard/k65plusW-eu.json +++ b/database/keyboard/k65plusW-eu.json @@ -608,7 +608,7 @@ "packetIndex": [315] }, "73": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k65plusW.json b/database/keyboard/k65plusW.json index 0188e4a..e9b5252 100644 --- a/database/keyboard/k65plusW.json +++ b/database/keyboard/k65plusW.json @@ -608,7 +608,7 @@ "packetIndex": [315] }, "73": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k65pm-eu.json b/database/keyboard/k65pm-eu.json index 6ba0d9f..9f8f498 100644 --- a/database/keyboard/k65pm-eu.json +++ b/database/keyboard/k65pm-eu.json @@ -189,16 +189,16 @@ } }, "15": { - "keyName": "G1", + "keyName": "Logo", "width": 70, "height": 70, "left": 15, "top": 0, "packetIndex": [387], "color": { - "red": 0, + "red": 255, "green": 255, - "blue": 255 + "blue": 0 } } } @@ -790,7 +790,7 @@ } }, "60": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k65pm.json b/database/keyboard/k65pm.json index b425c17..9b4c22d 100644 --- a/database/keyboard/k65pm.json +++ b/database/keyboard/k65pm.json @@ -189,16 +189,16 @@ } }, "15": { - "keyName": "G1", + "keyName": "Logo", "width": 70, "height": 70, "left": 15, "top": 0, "packetIndex": [387], "color": { - "red": 0, + "red": 255, "green": 255, - "blue": 255 + "blue": 0 } } } @@ -790,7 +790,7 @@ } }, "60": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k70core-eu.json b/database/keyboard/k70core-eu.json index 955d94b..7f0ca9a 100644 --- a/database/keyboard/k70core-eu.json +++ b/database/keyboard/k70core-eu.json @@ -1223,7 +1223,7 @@ } }, "93": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, @@ -1288,7 +1288,7 @@ } }, "98": { - "keyName": "RC", + "keyName": "Menu", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k70core.json b/database/keyboard/k70core.json index 669d44f..4dbf151 100644 --- a/database/keyboard/k70core.json +++ b/database/keyboard/k70core.json @@ -1223,7 +1223,7 @@ } }, "93": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, @@ -1288,7 +1288,7 @@ } }, "98": { - "keyName": "RC", + "keyName": "Menu", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k70coretkl-eu.json b/database/keyboard/k70coretkl-eu.json new file mode 100644 index 0000000..309dba3 --- /dev/null +++ b/database/keyboard/k70coretkl-eu.json @@ -0,0 +1,1137 @@ +{ + "key": "k70coretkl-default", + "device": "K70 CORE TKL", + "layout": "EU", + "rows": 6, + "row": { + "0": { + "keys": { + "1": { + "keyName": "ESC", + "width": 65, + "height": 70, + "left": 0, + "top": 0, + "packetIndex": [123], + "color": { + "red": 0, + "green": 255, + "blue": 0 + } + }, + "2": { + "keyName": "F1", + "width": 65, + "height": 70, + "left": 95, + "top": 0, + "packetIndex": [174], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "3": { + "keyName": "F2", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [177], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "4": { + "keyName": "F3", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [180], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "5": { + "keyName": "F4", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [183], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "6": { + "keyName": "F5", + "width": 65, + "height": 70, + "left": 55, + "top": 0, + "packetIndex": [186], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "7": { + "keyName": "F6", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [189], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "8": { + "keyName": "F7", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [192], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "9": { + "keyName": "F8", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [195], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "10": { + "keyName": "F9", + "width": 65, + "height": 70, + "left": 60, + "top": 0, + "packetIndex": [198], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "11": { + "keyName": "F10", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [201], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "12": { + "keyName": "F11", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [204], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "13": { + "keyName": "F12", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [207], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "14": { + "keyName": "Profile", + "width": 65, + "height": 70, + "left": 25, + "top": 0, + "packetIndex": [372], + "color": { + "red": 255, + "green": 255, + "blue": 0 + } + } + } + }, + "1": { + "keys": { + "15": { + "keyName": "` ~", + "width": 65, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [159], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "16": { + "keyName": "1", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [90], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "17": { + "keyName": "2", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [93], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "18": { + "keyName": "3", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [96], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "19": { + "keyName": "4", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [99], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "20": { + "keyName": "5", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [102], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "21": { + "keyName": "6", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [105], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "22": { + "keyName": "7", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [108], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "23": { + "keyName": "8", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [111], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "24": { + "keyName": "9", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [114], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "25": { + "keyName": "0", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [117], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "26": { + "keyName": "-", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [135], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "27": { + "keyName": "=", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [138], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "28": { + "keyName": "<---", + "width": 150, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [126], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "29": { + "keyName": "Ins", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [219], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "30": { + "keyName": "Home", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [222], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "31": { + "keyName": "PgUp", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [225], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "2": { + "keys": { + "32": { + "keyName": "Tab", + "width": 108, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [129], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "33": { + "keyName": "Q", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [60], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "34": { + "keyName": "W", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [78], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "35": { + "keyName": "E", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [24], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "36": { + "keyName": "R", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [63], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "37": { + "keyName": "T", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [69], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "38": { + "keyName": "Z", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [84], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "39": { + "keyName": "U", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [72], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "40": { + "keyName": "I", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [36], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "41": { + "keyName": "O", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [54], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "42": { + "keyName": "P", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [57], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "43": { + "keyName": "[ {", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [141], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "44": { + "keyName": "] }", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [144], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "45": { + "keyName": "\\ |", + "width": 107, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [147], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "46": { + "keyName": "Del", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [228], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "47": { + "keyName": "End", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [231], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "48": { + "keyName": "PgDn", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [234], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "3": { + "keys": { + "49": { + "keyName": "CAPS", + "width": 131, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [171], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "50": { + "keyName": "A", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [12], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "51": { + "keyName": "S", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [66], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "52": { + "keyName": "D", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [21], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "53": { + "keyName": "F", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [27], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "54": { + "keyName": "G", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [30], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "55": { + "keyName": "H", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [33], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "56": { + "keyName": "J", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [39], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "57": { + "keyName": "K", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [42], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "58": { + "keyName": "L", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [45], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "59": { + "keyName": "; :", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [153], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "60": { + "keyName": "' ''", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [156], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "61": { + "keyName": "Enter", + "width": 164, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [120], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "4": { + "keys": { + "62": { + "keyName": "Shift", + "width": 170, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [318], + "color": { + "red": 255, + "green": 255, + "blue": 0 + } + }, + "63": { + "keyName": "Y", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [87], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "64": { + "keyName": "X", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [81], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "65": { + "keyName": "C", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [18], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "66": { + "keyName": "V", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [75], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "67": { + "keyName": "B", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [15], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "68": { + "keyName": "N", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [51], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "69": { + "keyName": "M", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [48], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "70": { + "keyName": ", <", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [162], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "71": { + "keyName": ". >", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [165], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "72": { + "keyName": "/ ?", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [168], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "73": { + "keyName": "Shift", + "width": 205, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [330], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "74": { + "keyName": "↑", + "width": 65, + "height": 70, + "left": 105, + "top": 15, + "packetIndex": [246], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "5": { + "keys": { + "75": { + "keyName": "Ctrl", + "width": 90, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [315], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "76": { + "keyName": "Win", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [324], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "77": { + "keyName": "Alt", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [321], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "78": { + "keyName": "----------", + "width": 455, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [132], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "79": { + "keyName": "Alt", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [333], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "80": { + "keyName": "Fn", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [366], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "81": { + "keyName": "Menu", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [303], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "82": { + "keyName": "Ctrl", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [327], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "83": { + "keyName": "←", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [240], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "84": { + "keyName": "↓", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [243], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "85": { + "keyName": "→", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [237], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + } + } +} \ No newline at end of file diff --git a/database/keyboard/k70coretkl.json b/database/keyboard/k70coretkl.json new file mode 100644 index 0000000..5f5afe2 --- /dev/null +++ b/database/keyboard/k70coretkl.json @@ -0,0 +1,1137 @@ +{ + "key": "k70coretkl-default", + "device": "K70 CORE TKL", + "layout": "US", + "rows": 6, + "row": { + "0": { + "keys": { + "1": { + "keyName": "ESC", + "width": 65, + "height": 70, + "left": 0, + "top": 0, + "packetIndex": [123], + "color": { + "red": 0, + "green": 255, + "blue": 0 + } + }, + "2": { + "keyName": "F1", + "width": 65, + "height": 70, + "left": 95, + "top": 0, + "packetIndex": [174], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "3": { + "keyName": "F2", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [177], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "4": { + "keyName": "F3", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [180], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "5": { + "keyName": "F4", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [183], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "6": { + "keyName": "F5", + "width": 65, + "height": 70, + "left": 55, + "top": 0, + "packetIndex": [186], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "7": { + "keyName": "F6", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [189], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "8": { + "keyName": "F7", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [192], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "9": { + "keyName": "F8", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [195], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "10": { + "keyName": "F9", + "width": 65, + "height": 70, + "left": 60, + "top": 0, + "packetIndex": [198], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "11": { + "keyName": "F10", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [201], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "12": { + "keyName": "F11", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [204], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "13": { + "keyName": "F12", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [207], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "14": { + "keyName": "Profile", + "width": 65, + "height": 70, + "left": 25, + "top": 0, + "packetIndex": [372], + "color": { + "red": 255, + "green": 255, + "blue": 0 + } + } + } + }, + "1": { + "keys": { + "15": { + "keyName": "` ~", + "width": 65, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [159], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "16": { + "keyName": "1", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [90], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "17": { + "keyName": "2", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [93], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "18": { + "keyName": "3", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [96], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "19": { + "keyName": "4", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [99], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "20": { + "keyName": "5", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [102], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "21": { + "keyName": "6", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [105], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "22": { + "keyName": "7", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [108], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "23": { + "keyName": "8", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [111], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "24": { + "keyName": "9", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [114], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "25": { + "keyName": "0", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [117], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "26": { + "keyName": "-", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [135], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "27": { + "keyName": "=", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [138], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "28": { + "keyName": "<---", + "width": 150, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [126], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "29": { + "keyName": "Ins", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [219], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "30": { + "keyName": "Home", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [222], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "31": { + "keyName": "PgUp", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [225], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "2": { + "keys": { + "32": { + "keyName": "Tab", + "width": 108, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [129], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "33": { + "keyName": "Q", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [60], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "34": { + "keyName": "W", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [78], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "35": { + "keyName": "E", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [24], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "36": { + "keyName": "R", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [63], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "37": { + "keyName": "T", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [69], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "38": { + "keyName": "Y", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [84], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "39": { + "keyName": "U", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [72], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "40": { + "keyName": "I", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [36], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "41": { + "keyName": "O", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [54], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "42": { + "keyName": "P", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [57], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "43": { + "keyName": "[ {", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [141], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "44": { + "keyName": "] }", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [144], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "45": { + "keyName": "\\ |", + "width": 107, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [147], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "46": { + "keyName": "Del", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [228], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "47": { + "keyName": "End", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [231], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "48": { + "keyName": "PgDn", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [234], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "3": { + "keys": { + "49": { + "keyName": "CAPS", + "width": 131, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [171], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "50": { + "keyName": "A", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [12], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "51": { + "keyName": "S", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [66], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "52": { + "keyName": "D", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [21], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "53": { + "keyName": "F", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [27], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "54": { + "keyName": "G", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [30], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "55": { + "keyName": "H", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [33], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "56": { + "keyName": "J", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [39], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "57": { + "keyName": "K", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [42], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "58": { + "keyName": "L", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [45], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "59": { + "keyName": "; :", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [153], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "60": { + "keyName": "' ''", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [156], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "61": { + "keyName": "Enter", + "width": 164, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [120], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "4": { + "keys": { + "62": { + "keyName": "Shift", + "width": 170, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [318], + "color": { + "red": 255, + "green": 255, + "blue": 0 + } + }, + "63": { + "keyName": "Z", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [87], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "64": { + "keyName": "X", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [81], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "65": { + "keyName": "C", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [18], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "66": { + "keyName": "V", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [75], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "67": { + "keyName": "B", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [15], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "68": { + "keyName": "N", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [51], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "69": { + "keyName": "M", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [48], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "70": { + "keyName": ", <", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [162], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "71": { + "keyName": ". >", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [165], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "72": { + "keyName": "/ ?", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [168], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "73": { + "keyName": "Shift", + "width": 205, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [330], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "74": { + "keyName": "↑", + "width": 65, + "height": 70, + "left": 105, + "top": 15, + "packetIndex": [246], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "5": { + "keys": { + "75": { + "keyName": "Ctrl", + "width": 90, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [315], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "76": { + "keyName": "Win", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [324], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "77": { + "keyName": "Alt", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [321], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "78": { + "keyName": "----------", + "width": 455, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [132], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "79": { + "keyName": "Alt", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [333], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "80": { + "keyName": "Fn", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [366], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "81": { + "keyName": "Menu", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [303], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "82": { + "keyName": "Ctrl", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [327], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "83": { + "keyName": "←", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [240], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "84": { + "keyName": "↓", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [243], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "85": { + "keyName": "→", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [237], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + } + } +} \ No newline at end of file diff --git a/database/keyboard/k70coretklW-eu.json b/database/keyboard/k70coretklW-eu.json new file mode 100644 index 0000000..0d98923 --- /dev/null +++ b/database/keyboard/k70coretklW-eu.json @@ -0,0 +1,1156 @@ +{ + "key": "k70coretklW-default", + "device": "K70 CORE TKL WL", + "layout": "EU", + "rows": 6, + "color": { + "red": 0, + "green": 255, + "blue": 255 + }, + "row": { + "0": { + "keys": { + "1": { + "keyName": "ESC", + "width": 65, + "height": 70, + "left": 0, + "top": 0, + "packetIndex": [123], + "color": { + "red": 0, + "green": 255, + "blue": 0 + } + }, + "2": { + "keyName": "F1", + "width": 65, + "height": 70, + "left": 95, + "top": 0, + "packetIndex": [174], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "3": { + "keyName": "F2", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [177], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "4": { + "keyName": "F3", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [180], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "5": { + "keyName": "F4", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [183], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "6": { + "keyName": "F5", + "width": 65, + "height": 70, + "left": 55, + "top": 0, + "packetIndex": [186], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "7": { + "keyName": "F6", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [189], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "8": { + "keyName": "F7", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [192], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "9": { + "keyName": "F8", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [195], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "10": { + "keyName": "F9", + "width": 65, + "height": 70, + "left": 60, + "top": 0, + "packetIndex": [198], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "11": { + "keyName": "F10", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [201], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "12": { + "keyName": "F11", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [204], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "13": { + "keyName": "F12", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [207], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "14": { + "keyName": "icon-speed.svg", + "width": 65, + "height": 70, + "left": 25, + "top": 0, + "packetIndex": [3], + "color": { + "red": 255, + "green": 255, + "blue": 0 + }, + "svg": true + }, + "15": { + "keyName": "Profile", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [6], + "color": { + "red": 255, + "green": 255, + "blue": 0 + } + } + } + }, + "1": { + "keys": { + "16": { + "keyName": "` ~", + "width": 65, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [159], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "17": { + "keyName": "1", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [90], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "18": { + "keyName": "2", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [93], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "19": { + "keyName": "3", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [96], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "20": { + "keyName": "4", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [99], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "21": { + "keyName": "5", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [102], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "22": { + "keyName": "6", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [105], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "23": { + "keyName": "7", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [108], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "24": { + "keyName": "8", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [111], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "25": { + "keyName": "9", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [114], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "26": { + "keyName": "0", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [117], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "27": { + "keyName": "-", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [135], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "28": { + "keyName": "=", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [138], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "29": { + "keyName": "<---", + "width": 150, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [126], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "30": { + "keyName": "Ins", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [219], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "31": { + "keyName": "Home", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [222], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "32": { + "keyName": "PgUp", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [225], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "2": { + "keys": { + "33": { + "keyName": "Tab", + "width": 108, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [129], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "34": { + "keyName": "Q", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [60], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "35": { + "keyName": "W", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [78], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "36": { + "keyName": "E", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [24], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "37": { + "keyName": "R", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [63], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "38": { + "keyName": "T", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [69], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "39": { + "keyName": "Z", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [84], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "40": { + "keyName": "U", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [72], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "41": { + "keyName": "I", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [36], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "42": { + "keyName": "O", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [54], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "43": { + "keyName": "P", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [57], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "44": { + "keyName": "[ {", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [141], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "45": { + "keyName": "] }", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [144], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "46": { + "keyName": "\\ |", + "width": 107, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [147], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "47": { + "keyName": "Del", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [228], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "48": { + "keyName": "End", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [231], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "49": { + "keyName": "PgDn", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [234], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "3": { + "keys": { + "50": { + "keyName": "CAPS", + "width": 131, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [171], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "51": { + "keyName": "A", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [12], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "52": { + "keyName": "S", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [66], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "53": { + "keyName": "D", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [21], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "54": { + "keyName": "F", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [27], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "55": { + "keyName": "G", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [30], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "56": { + "keyName": "H", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [33], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "57": { + "keyName": "J", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [39], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "58": { + "keyName": "K", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [42], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "59": { + "keyName": "L", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [45], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "60": { + "keyName": "; :", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [153], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "61": { + "keyName": "' ''", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [156], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "62": { + "keyName": "Enter", + "width": 164, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [120], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "4": { + "keys": { + "63": { + "keyName": "Shift", + "width": 170, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [318], + "color": { + "red": 255, + "green": 255, + "blue": 0 + } + }, + "64": { + "keyName": "Y", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [87], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "65": { + "keyName": "X", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [81], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "66": { + "keyName": "C", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [18], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "67": { + "keyName": "V", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [75], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "68": { + "keyName": "B", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [15], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "69": { + "keyName": "N", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [51], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "70": { + "keyName": "M", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [48], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "71": { + "keyName": ", <", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [162], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "72": { + "keyName": ". >", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [165], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "73": { + "keyName": "/ ?", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [168], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "74": { + "keyName": "Shift", + "width": 205, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [330], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "75": { + "keyName": "↑", + "width": 65, + "height": 70, + "left": 105, + "top": 15, + "packetIndex": [246], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "5": { + "keys": { + "76": { + "keyName": "Ctrl", + "width": 90, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [315], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "77": { + "keyName": "Win", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [324], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "78": { + "keyName": "Alt", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [321], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "79": { + "keyName": "----------", + "width": 455, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [132], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "80": { + "keyName": "Alt", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [333], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "81": { + "keyName": "Fn", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [366], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "82": { + "keyName": "Menu", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [303], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "83": { + "keyName": "Ctrl", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [327], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "84": { + "keyName": "←", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [240], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "85": { + "keyName": "↓", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [243], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "86": { + "keyName": "→", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [237], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + } + } +} \ No newline at end of file diff --git a/database/keyboard/k70coretklW.json b/database/keyboard/k70coretklW.json new file mode 100644 index 0000000..b38e6fa --- /dev/null +++ b/database/keyboard/k70coretklW.json @@ -0,0 +1,1156 @@ +{ + "key": "k70coretklW-default", + "device": "K70 CORE TKL WL", + "layout": "US", + "rows": 6, + "color": { + "red": 0, + "green": 255, + "blue": 255 + }, + "row": { + "0": { + "keys": { + "1": { + "keyName": "ESC", + "width": 65, + "height": 70, + "left": 0, + "top": 0, + "packetIndex": [123], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "2": { + "keyName": "F1", + "width": 65, + "height": 70, + "left": 95, + "top": 0, + "packetIndex": [174], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "3": { + "keyName": "F2", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [177], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "4": { + "keyName": "F3", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [180], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "5": { + "keyName": "F4", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [183], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "6": { + "keyName": "F5", + "width": 65, + "height": 70, + "left": 55, + "top": 0, + "packetIndex": [186], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "7": { + "keyName": "F6", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [189], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "8": { + "keyName": "F7", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [192], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "9": { + "keyName": "F8", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [195], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "10": { + "keyName": "F9", + "width": 65, + "height": 70, + "left": 60, + "top": 0, + "packetIndex": [198], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "11": { + "keyName": "F10", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [201], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "12": { + "keyName": "F11", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [204], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "13": { + "keyName": "F12", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [207], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "14": { + "keyName": "icon-speed.svg", + "width": 65, + "height": 70, + "left": 25, + "top": 0, + "packetIndex": [3], + "color": { + "red": 255, + "green": 255, + "blue": 0 + }, + "svg": true + }, + "15": { + "keyName": "Profile", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [6], + "color": { + "red": 255, + "green": 255, + "blue": 0 + } + } + } + }, + "1": { + "keys": { + "16": { + "keyName": "` ~", + "width": 65, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [159], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "17": { + "keyName": "1", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [90], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "18": { + "keyName": "2", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [93], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "19": { + "keyName": "3", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [96], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "20": { + "keyName": "4", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [99], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "21": { + "keyName": "5", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [102], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "22": { + "keyName": "6", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [105], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "23": { + "keyName": "7", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [108], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "24": { + "keyName": "8", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [111], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "25": { + "keyName": "9", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [114], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "26": { + "keyName": "0", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [117], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "27": { + "keyName": "-", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [135], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "28": { + "keyName": "=", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [138], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "29": { + "keyName": "<---", + "width": 150, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [126], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "30": { + "keyName": "Ins", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [219], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "31": { + "keyName": "Home", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [222], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "32": { + "keyName": "PgUp", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [225], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "2": { + "keys": { + "33": { + "keyName": "Tab", + "width": 108, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [129], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "34": { + "keyName": "Q", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [60], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "35": { + "keyName": "W", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [78], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "36": { + "keyName": "E", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [24], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "37": { + "keyName": "R", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [63], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "38": { + "keyName": "T", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [69], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "39": { + "keyName": "Y", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [84], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "40": { + "keyName": "U", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [72], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "41": { + "keyName": "I", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [36], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "42": { + "keyName": "O", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [54], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "43": { + "keyName": "P", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [57], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "44": { + "keyName": "[ {", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [141], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "45": { + "keyName": "] }", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [144], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "46": { + "keyName": "\\ |", + "width": 107, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [147], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "47": { + "keyName": "Del", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [228], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "48": { + "keyName": "End", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [231], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "49": { + "keyName": "PgDn", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [234], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "3": { + "keys": { + "50": { + "keyName": "CAPS", + "width": 131, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [171], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "51": { + "keyName": "A", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [12], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "52": { + "keyName": "S", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [66], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "53": { + "keyName": "D", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [21], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "54": { + "keyName": "F", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [27], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "55": { + "keyName": "G", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [30], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "56": { + "keyName": "H", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [33], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "57": { + "keyName": "J", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [39], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "58": { + "keyName": "K", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [42], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "59": { + "keyName": "L", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [45], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "60": { + "keyName": "; :", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [153], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "61": { + "keyName": "' ''", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [156], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "62": { + "keyName": "Enter", + "width": 164, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [120], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "4": { + "keys": { + "63": { + "keyName": "Shift", + "width": 170, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [318], + "color": { + "red": 255, + "green": 255, + "blue": 0 + } + }, + "64": { + "keyName": "Z", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [87], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "65": { + "keyName": "X", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [81], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "66": { + "keyName": "C", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [18], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "67": { + "keyName": "V", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [75], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "68": { + "keyName": "B", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [15], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "69": { + "keyName": "N", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [51], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "70": { + "keyName": "M", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [48], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "71": { + "keyName": ", <", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [162], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "72": { + "keyName": ". >", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [165], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "73": { + "keyName": "/ ?", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [168], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "74": { + "keyName": "Shift", + "width": 205, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [330], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "75": { + "keyName": "↑", + "width": 65, + "height": 70, + "left": 105, + "top": 15, + "packetIndex": [246], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "5": { + "keys": { + "76": { + "keyName": "Ctrl", + "width": 90, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [315], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "77": { + "keyName": "Win", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [324], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "78": { + "keyName": "Alt", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [321], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "79": { + "keyName": "----------", + "width": 455, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [132], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "80": { + "keyName": "Alt", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [333], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "81": { + "keyName": "Fn", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [366], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "82": { + "keyName": "Menu", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [303], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "83": { + "keyName": "Ctrl", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [327], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "84": { + "keyName": "←", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [240], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "85": { + "keyName": "↓", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [243], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "86": { + "keyName": "→", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [237], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + } + } +} \ No newline at end of file diff --git a/database/keyboard/k70mk2-eu.json b/database/keyboard/k70mk2-eu.json index 5a020b5..318ff6b 100644 --- a/database/keyboard/k70mk2-eu.json +++ b/database/keyboard/k70mk2-eu.json @@ -1378,7 +1378,7 @@ } }, "104": { - "keyName": "icon-windows.svg", + "keyName": "Win", "width": 65, "height": 70, "left": 15, @@ -1388,8 +1388,7 @@ "red": 0, "green": 255, "blue": 255 - }, - "svg": true + } }, "105": { "keyName": "Alt", @@ -1431,7 +1430,7 @@ } }, "108": { - "keyName": "icon-windows.svg", + "keyName": "Win", "width": 90, "height": 70, "left": 15, @@ -1441,8 +1440,7 @@ "red": 0, "green": 255, "blue": 255 - }, - "svg": true + } }, "109": { "keyName": "RC", diff --git a/database/keyboard/k70mk2.json b/database/keyboard/k70mk2.json index 7f65c79..081f892 100644 --- a/database/keyboard/k70mk2.json +++ b/database/keyboard/k70mk2.json @@ -1378,7 +1378,7 @@ } }, "104": { - "keyName": "icon-windows.svg", + "keyName": "Win", "width": 65, "height": 70, "left": 15, @@ -1388,8 +1388,7 @@ "red": 0, "green": 255, "blue": 255 - }, - "svg": true + } }, "105": { "keyName": "Alt", @@ -1431,7 +1430,7 @@ } }, "108": { - "keyName": "icon-windows.svg", + "keyName": "Win", "width": 90, "height": 70, "left": 15, @@ -1441,8 +1440,7 @@ "red": 0, "green": 255, "blue": 255 - }, - "svg": true + } }, "109": { "keyName": "RC", diff --git a/database/keyboard/k70pro-eu.json b/database/keyboard/k70pro-eu.json index 23530fb..a78d69e 100644 --- a/database/keyboard/k70pro-eu.json +++ b/database/keyboard/k70pro-eu.json @@ -49,7 +49,7 @@ "svg": true }, "4": { - "keyName": "", + "keyName": "Logo", "width": 404, "height": 40, "left": 400, @@ -1352,7 +1352,7 @@ } }, "102": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, @@ -1443,7 +1443,7 @@ } }, "109": { - "keyName": "RC", + "keyName": "Menu", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k70pro.json b/database/keyboard/k70pro.json index 801f55e..30ffa0c 100644 --- a/database/keyboard/k70pro.json +++ b/database/keyboard/k70pro.json @@ -49,7 +49,7 @@ "svg": true }, "4": { - "keyName": "", + "keyName": "Logo", "width": 404, "height": 40, "left": 400, @@ -1352,7 +1352,7 @@ } }, "102": { - "keyName": "⊞", + "keyName": "Win", "width": 90, "height": 70, "left": 15, @@ -1443,7 +1443,7 @@ } }, "109": { - "keyName": "RC", + "keyName": "Menu", "width": 90, "height": 70, "left": 15, diff --git a/database/keyboard/k70protkl-eu.json b/database/keyboard/k70protkl-eu.json new file mode 100644 index 0000000..56a7de6 --- /dev/null +++ b/database/keyboard/k70protkl-eu.json @@ -0,0 +1,1151 @@ +{ + "key": "k70protkl-default", + "device": "K70 PRO TKL", + "layout": "EU", + "rows": 6, + "row": { + "0": { + "keys": { + "1": { + "keyName": "ESC", + "width": 65, + "height": 70, + "left": 0, + "top": 0, + "packetIndex": [123], + "color": { + "red": 0, + "green": 255, + "blue": 0 + } + }, + "2": { + "keyName": "F1", + "width": 65, + "height": 70, + "left": 95, + "top": 0, + "packetIndex": [174], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "3": { + "keyName": "F2", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [177], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "4": { + "keyName": "F3", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [180], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "5": { + "keyName": "F4", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [183], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "6": { + "keyName": "F5", + "width": 65, + "height": 70, + "left": 55, + "top": 0, + "packetIndex": [186], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "7": { + "keyName": "F6", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [189], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "8": { + "keyName": "F7", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [192], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "9": { + "keyName": "F8", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [195], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "10": { + "keyName": "F9", + "width": 65, + "height": 70, + "left": 60, + "top": 0, + "packetIndex": [198], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "11": { + "keyName": "F10", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [201], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "12": { + "keyName": "F11", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [204], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "13": { + "keyName": "F12", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [207], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "14": { + "keyName": "icon-speed.svg", + "width": 65, + "height": 70, + "left": 25, + "top": 0, + "packetIndex": [390], + "color": { + "red": 255, + "green": 255, + "blue": 0 + }, + "svg": true + }, + "15": { + "keyName": "Profile", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [372], + "color": { + "red": 255, + "green": 255, + "blue": 0 + } + } + } + }, + "1": { + "keys": { + "16": { + "keyName": "` ~", + "width": 65, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [159], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "17": { + "keyName": "1", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [90], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "18": { + "keyName": "2", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [93], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "19": { + "keyName": "3", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [96], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "20": { + "keyName": "4", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [99], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "21": { + "keyName": "5", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [102], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "22": { + "keyName": "6", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [105], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "23": { + "keyName": "7", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [108], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "24": { + "keyName": "8", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [111], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "25": { + "keyName": "9", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [114], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "26": { + "keyName": "0", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [117], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "27": { + "keyName": "-", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [135], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "28": { + "keyName": "=", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [138], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "29": { + "keyName": "<---", + "width": 150, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [126], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "30": { + "keyName": "Ins", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [219], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "31": { + "keyName": "Home", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [222], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "32": { + "keyName": "PgUp", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [225], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "2": { + "keys": { + "33": { + "keyName": "Tab", + "width": 108, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [129], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "34": { + "keyName": "Q", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [60], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "35": { + "keyName": "W", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [78], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "36": { + "keyName": "E", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [24], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "37": { + "keyName": "R", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [63], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "38": { + "keyName": "T", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [69], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "39": { + "keyName": "Z", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [84], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "40": { + "keyName": "U", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [72], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "41": { + "keyName": "I", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [36], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "42": { + "keyName": "O", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [54], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "43": { + "keyName": "P", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [57], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "44": { + "keyName": "[ {", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [141], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "45": { + "keyName": "] }", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [144], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "46": { + "keyName": "\\ |", + "width": 107, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [147], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "47": { + "keyName": "Del", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [228], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "48": { + "keyName": "End", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [231], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "49": { + "keyName": "PgDn", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [234], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "3": { + "keys": { + "50": { + "keyName": "CAPS", + "width": 131, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [171], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "51": { + "keyName": "A", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [12], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "52": { + "keyName": "S", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [66], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "53": { + "keyName": "D", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [21], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "54": { + "keyName": "F", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [27], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "55": { + "keyName": "G", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [30], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "56": { + "keyName": "H", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [33], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "57": { + "keyName": "J", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [39], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "58": { + "keyName": "K", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [42], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "59": { + "keyName": "L", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [45], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "60": { + "keyName": "; :", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [153], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "61": { + "keyName": "' ''", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [156], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "62": { + "keyName": "Enter", + "width": 164, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [120], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "4": { + "keys": { + "63": { + "keyName": "Shift", + "width": 170, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [318], + "color": { + "red": 255, + "green": 255, + "blue": 0 + } + }, + "64": { + "keyName": "Y", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [87], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "65": { + "keyName": "X", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [81], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "66": { + "keyName": "C", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [18], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "67": { + "keyName": "V", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [75], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "68": { + "keyName": "B", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [15], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "69": { + "keyName": "N", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [51], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "70": { + "keyName": "M", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [48], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "71": { + "keyName": ", <", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [162], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "72": { + "keyName": ". >", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [165], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "73": { + "keyName": "/ ?", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [168], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "74": { + "keyName": "Shift", + "width": 205, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [330], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "75": { + "keyName": "↑", + "width": 65, + "height": 70, + "left": 105, + "top": 15, + "packetIndex": [246], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "5": { + "keys": { + "76": { + "keyName": "Ctrl", + "width": 90, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [315], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "77": { + "keyName": "Win", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [324], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "78": { + "keyName": "Alt", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [321], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "79": { + "keyName": "----------", + "width": 455, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [132], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "80": { + "keyName": "Alt", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [333], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "81": { + "keyName": "Fn", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [366], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "82": { + "keyName": "Menu", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [303], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "83": { + "keyName": "Ctrl", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [327], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "84": { + "keyName": "←", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [240], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "85": { + "keyName": "↓", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [243], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "86": { + "keyName": "→", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [237], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + } + } +} \ No newline at end of file diff --git a/database/keyboard/k70protkl.json b/database/keyboard/k70protkl.json new file mode 100644 index 0000000..78995de --- /dev/null +++ b/database/keyboard/k70protkl.json @@ -0,0 +1,1151 @@ +{ + "key": "k70protkl-default", + "device": "K70 PRO TKL", + "layout": "US", + "rows": 6, + "row": { + "0": { + "keys": { + "1": { + "keyName": "ESC", + "width": 65, + "height": 70, + "left": 0, + "top": 0, + "packetIndex": [123], + "color": { + "red": 0, + "green": 255, + "blue": 0 + } + }, + "2": { + "keyName": "F1", + "width": 65, + "height": 70, + "left": 95, + "top": 0, + "packetIndex": [174], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "3": { + "keyName": "F2", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [177], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "4": { + "keyName": "F3", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [180], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "5": { + "keyName": "F4", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [183], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "6": { + "keyName": "F5", + "width": 65, + "height": 70, + "left": 55, + "top": 0, + "packetIndex": [186], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "7": { + "keyName": "F6", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [189], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "8": { + "keyName": "F7", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [192], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "9": { + "keyName": "F8", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [195], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "10": { + "keyName": "F9", + "width": 65, + "height": 70, + "left": 60, + "top": 0, + "packetIndex": [198], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "11": { + "keyName": "F10", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [201], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "12": { + "keyName": "F11", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [204], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "13": { + "keyName": "F12", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [207], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "14": { + "keyName": "icon-speed.svg", + "width": 65, + "height": 70, + "left": 25, + "top": 0, + "packetIndex": [390], + "color": { + "red": 255, + "green": 255, + "blue": 0 + }, + "svg": true + }, + "15": { + "keyName": "Profile", + "width": 65, + "height": 70, + "left": 15, + "top": 0, + "packetIndex": [372], + "color": { + "red": 255, + "green": 255, + "blue": 0 + } + } + } + }, + "1": { + "keys": { + "16": { + "keyName": "` ~", + "width": 65, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [159], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "17": { + "keyName": "1", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [90], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "18": { + "keyName": "2", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [93], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "19": { + "keyName": "3", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [96], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "20": { + "keyName": "4", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [99], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "21": { + "keyName": "5", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [102], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "22": { + "keyName": "6", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [105], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "23": { + "keyName": "7", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [108], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "24": { + "keyName": "8", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [111], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "25": { + "keyName": "9", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [114], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "26": { + "keyName": "0", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [117], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "27": { + "keyName": "-", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [135], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "28": { + "keyName": "=", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [138], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "29": { + "keyName": "<---", + "width": 150, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [126], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "30": { + "keyName": "Ins", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [219], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "31": { + "keyName": "Home", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [222], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "32": { + "keyName": "PgUp", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [225], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "2": { + "keys": { + "33": { + "keyName": "Tab", + "width": 108, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [129], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "34": { + "keyName": "Q", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [60], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "35": { + "keyName": "W", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [78], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "36": { + "keyName": "E", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [24], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "37": { + "keyName": "R", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [63], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "38": { + "keyName": "T", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [69], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "39": { + "keyName": "Y", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [84], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "40": { + "keyName": "U", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [72], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "41": { + "keyName": "I", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [36], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "42": { + "keyName": "O", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [54], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "43": { + "keyName": "P", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [57], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "44": { + "keyName": "[ {", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [141], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "45": { + "keyName": "] }", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [144], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "46": { + "keyName": "\\ |", + "width": 107, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [147], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "47": { + "keyName": "Del", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [228], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "48": { + "keyName": "End", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [231], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "49": { + "keyName": "PgDn", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [234], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "3": { + "keys": { + "50": { + "keyName": "CAPS", + "width": 131, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [171], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "51": { + "keyName": "A", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [12], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "52": { + "keyName": "S", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [66], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "53": { + "keyName": "D", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [21], + "color": { + "red": 255, + "green": 0, + "blue": 0 + } + }, + "54": { + "keyName": "F", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [27], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "55": { + "keyName": "G", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [30], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "56": { + "keyName": "H", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [33], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "57": { + "keyName": "J", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [39], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "58": { + "keyName": "K", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [42], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "59": { + "keyName": "L", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [45], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "60": { + "keyName": "; :", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [153], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "61": { + "keyName": "' ''", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [156], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "62": { + "keyName": "Enter", + "width": 164, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [120], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "4": { + "keys": { + "63": { + "keyName": "Shift", + "width": 170, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [318], + "color": { + "red": 255, + "green": 255, + "blue": 0 + } + }, + "64": { + "keyName": "Z", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [87], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "65": { + "keyName": "X", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [81], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "66": { + "keyName": "C", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [18], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "67": { + "keyName": "V", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [75], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "68": { + "keyName": "B", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [15], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "69": { + "keyName": "N", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [51], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "70": { + "keyName": "M", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [48], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "71": { + "keyName": ", <", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [162], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "72": { + "keyName": ". >", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [165], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "73": { + "keyName": "/ ?", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [168], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "74": { + "keyName": "Shift", + "width": 205, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [330], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "75": { + "keyName": "↑", + "width": 65, + "height": 70, + "left": 105, + "top": 15, + "packetIndex": [246], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + }, + "5": { + "keys": { + "76": { + "keyName": "Ctrl", + "width": 90, + "height": 70, + "left": 0, + "top": 15, + "packetIndex": [315], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "77": { + "keyName": "Win", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [324], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "78": { + "keyName": "Alt", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [321], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "79": { + "keyName": "----------", + "width": 455, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [132], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "80": { + "keyName": "Alt", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [333], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "81": { + "keyName": "Fn", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [366], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "82": { + "keyName": "Menu", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [303], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "83": { + "keyName": "Ctrl", + "width": 90, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [327], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "84": { + "keyName": "←", + "width": 65, + "height": 70, + "left": 25, + "top": 15, + "packetIndex": [240], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "85": { + "keyName": "↓", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [243], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + }, + "86": { + "keyName": "→", + "width": 65, + "height": 70, + "left": 15, + "top": 15, + "packetIndex": [237], + "color": { + "red": 0, + "green": 255, + "blue": 255 + } + } + } + } + } +} \ No newline at end of file diff --git a/src/devices/darkcorergbproW/darkcorergbproW.go b/src/devices/darkcorergbproW/darkcorergbproW.go index 214f0bb..8726e04 100644 --- a/src/devices/darkcorergbproW/darkcorergbproW.go +++ b/src/devices/darkcorergbproW/darkcorergbproW.go @@ -997,9 +997,6 @@ func (d *Device) setDeviceColor() { // DPI dpiColor := d.DeviceProfile.DPIColor - dpiColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) - dpiColor = rgb.ModifyBrightness(*dpiColor) - dpiLeds := d.DeviceProfile.Profiles[d.DeviceProfile.Profile] for i := 0; i < len(dpiLeds.ColorIndex); i++ { dpiColorIndexRange := dpiLeds.ColorIndex[i] diff --git a/src/devices/darkcorergbproWU/darkcorergbproWU.go b/src/devices/darkcorergbproWU/darkcorergbproWU.go index 18a7b94..bee2dd3 100644 --- a/src/devices/darkcorergbproWU/darkcorergbproWU.go +++ b/src/devices/darkcorergbproWU/darkcorergbproWU.go @@ -984,9 +984,6 @@ func (d *Device) setDeviceColor() { // DPI dpiColor := d.DeviceProfile.DPIColor - dpiColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) - dpiColor = rgb.ModifyBrightness(*dpiColor) - dpiLeds := d.DeviceProfile.Profiles[d.DeviceProfile.Profile] for i := 0; i < len(dpiLeds.ColorIndex); i++ { dpiColorIndexRange := dpiLeds.ColorIndex[i] diff --git a/src/devices/darkcorergbproseW/darkcorergbproseW.go b/src/devices/darkcorergbproseW/darkcorergbproseW.go index 582db7b..07b83e3 100644 --- a/src/devices/darkcorergbproseW/darkcorergbproseW.go +++ b/src/devices/darkcorergbproseW/darkcorergbproseW.go @@ -997,9 +997,6 @@ func (d *Device) setDeviceColor() { // DPI dpiColor := d.DeviceProfile.DPIColor - dpiColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) - dpiColor = rgb.ModifyBrightness(*dpiColor) - dpiLeds := d.DeviceProfile.Profiles[d.DeviceProfile.Profile] for i := 0; i < len(dpiLeds.ColorIndex); i++ { dpiColorIndexRange := dpiLeds.ColorIndex[i] diff --git a/src/devices/darkcorergbproseWU/darkcorergbproseWU.go b/src/devices/darkcorergbproseWU/darkcorergbproseWU.go index 94d0fd8..c45f480 100644 --- a/src/devices/darkcorergbproseWU/darkcorergbproseWU.go +++ b/src/devices/darkcorergbproseWU/darkcorergbproseWU.go @@ -984,9 +984,6 @@ func (d *Device) setDeviceColor() { // DPI dpiColor := d.DeviceProfile.DPIColor - dpiColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) - dpiColor = rgb.ModifyBrightness(*dpiColor) - dpiLeds := d.DeviceProfile.Profiles[d.DeviceProfile.Profile] for i := 0; i < len(dpiLeds.ColorIndex); i++ { dpiColorIndexRange := dpiLeds.ColorIndex[i] diff --git a/src/devices/darkstarW/darkstarW.go b/src/devices/darkstarW/darkstarW.go index 118d608..8a3eb31 100644 --- a/src/devices/darkstarW/darkstarW.go +++ b/src/devices/darkstarW/darkstarW.go @@ -1025,9 +1025,6 @@ func (d *Device) setDeviceColor() { // DPI dpiColor := d.DeviceProfile.DPIColor - dpiColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) - dpiColor = rgb.ModifyBrightness(*dpiColor) - dpiLeds := d.DeviceProfile.Profiles[d.DeviceProfile.Profile] for i := 0; i < len(dpiLeds.ColorIndex); i++ { dpiColorIndexRange := dpiLeds.ColorIndex[i] diff --git a/src/devices/darkstarWU/darkstarWU.go b/src/devices/darkstarWU/darkstarWU.go index 3eb70e0..fbdb921 100644 --- a/src/devices/darkstarWU/darkstarWU.go +++ b/src/devices/darkstarWU/darkstarWU.go @@ -1005,8 +1005,6 @@ func (d *Device) setDeviceColor() { // DPI dpiColor := d.DeviceProfile.DPIColor - dpiColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) - dpiColor = rgb.ModifyBrightness(*dpiColor) dpiLeds := d.DeviceProfile.Profiles[d.DeviceProfile.Profile] for i := 0; i < len(dpiLeds.ColorIndex); i++ { diff --git a/src/devices/devices.go b/src/devices/devices.go index 4afcd1a..035fdfc 100644 --- a/src/devices/devices.go +++ b/src/devices/devices.go @@ -27,8 +27,12 @@ import ( "OpenLinkHub/src/devices/k65plusW" "OpenLinkHub/src/devices/k65pm" "OpenLinkHub/src/devices/k70core" + "OpenLinkHub/src/devices/k70coretkl" + "OpenLinkHub/src/devices/k70coretklW" + "OpenLinkHub/src/devices/k70coretklWU" "OpenLinkHub/src/devices/k70mk2" "OpenLinkHub/src/devices/k70pro" + "OpenLinkHub/src/devices/k70protkl" "OpenLinkHub/src/devices/katarpro" "OpenLinkHub/src/devices/katarproW" "OpenLinkHub/src/devices/katarproxt" @@ -87,6 +91,10 @@ const ( productTypeK100AirW = 108 productTypeK100 = 109 productTypeK70MK2 = 110 + productTypeK70CoreTkl = 111 + productTypeK70CoreTklWU = 112 + productTypeK70CoreTklW = 113 + productTypeK70ProTkl = 114 productTypeKatarPro = 201 productTypeIronClawRgb = 202 productTypeIronClawRgbW = 203 @@ -150,7 +158,7 @@ var ( interfaceId = 0 devices = make(map[string]*Device, 0) products = make(map[string]Product, 0) - keyboards = []uint16{7127, 7165, 7166, 7110, 7083, 11024, 11015, 7109, 7091, 7036, 7037, 6985, 6997} + keyboards = []uint16{7127, 7165, 7166, 7110, 7083, 11024, 11015, 7109, 7091, 7036, 7037, 6985, 6997, 7019, 11009, 11010, 11028} mouses = []uint16{7059, 7005, 6988, 7096, 7139, 7131, 11011, 7024, 7038, 7040, 7152, 7154, 7070, 7029, 7006, 7084, 7090} pads = []uint16{7067} headsets = []uint16{2658, 2660} @@ -590,6 +598,28 @@ func ChangeDeviceSleepMode(deviceId string, sleepMode int) uint8 { return 0 } +// ChangeDevicePollingRate will change device polling rate +func ChangeDevicePollingRate(deviceId string, pullingRate int) uint8 { + if device, ok := devices[deviceId]; ok { + methodName := "UpdatePollingRate" + method := reflect.ValueOf(GetDevice(device.Serial)).MethodByName(methodName) + if !method.IsValid() { + logger.Log(logger.Fields{"method": methodName}).Warn("Method not found or method is not supported for this device type") + return 0 + } else { + var reflectArgs []reflect.Value + reflectArgs = append(reflectArgs, reflect.ValueOf(pullingRate)) + results := method.Call(reflectArgs) + if len(results) > 0 { + val := results[0] + uintResult := val.Uint() + return uint8(uintResult) + } + } + } + return 0 +} + // ChangeDeviceMuteIndicator will change device mute indicator func ChangeDeviceMuteIndicator(deviceId string, muteIndicator int) uint8 { if device, ok := devices[deviceId]; ok { @@ -1380,6 +1410,57 @@ func Init() { } }(vendorId, productId, key) } + case 11009: // K70 CORE TKL + { + go func(vendorId, productId uint16, key string) { + dev := k70coretkl.Init(vendorId, productId, key) + if dev == nil { + return + } + devices[dev.Serial] = &Device{ + ProductType: productTypeK70CoreTkl, + Product: dev.Product, + Serial: dev.Serial, + Firmware: dev.Firmware, + Image: "icon-keyboard.svg", + Instance: dev, + } + }(vendorId, productId, key) + } + case 11010: // K70 CORE TKL WIRELESS + { + go func(vendorId, productId uint16, key string) { + dev := k70coretklWU.Init(vendorId, productId, key) + if dev == nil { + return + } + devices[dev.Serial] = &Device{ + ProductType: productTypeK70CoreTklWU, + Product: dev.Product, + Serial: dev.Serial, + Firmware: dev.Firmware, + Image: "icon-keyboard.svg", + Instance: dev, + } + }(vendorId, productId, key) + } + case 11028: // K70 CORE TKL WIRELESS + { + go func(vendorId, productId uint16, key string) { + dev := k70protkl.Init(vendorId, productId, key) + if dev == nil { + return + } + devices[dev.Serial] = &Device{ + ProductType: productTypeK70ProTkl, + Product: dev.Product, + Serial: dev.Serial, + Firmware: dev.Firmware, + Image: "icon-keyboard.svg", + Instance: dev, + } + }(vendorId, productId, key) + } case 7166: // K55 CORE RGB { go func(vendorId, productId uint16, key string) { @@ -1482,7 +1563,7 @@ func Init() { } }(vendorId, productId, key) } - case 7109, 7036, 7037: // K100 RGB + case 7036, 7109, 7037: // K100 RGB { go func(vendorId, productId uint16, key string) { dev := k100.Init(vendorId, productId, key) @@ -1583,7 +1664,7 @@ func Init() { } dev.AddPairedDevice(value.ProductId, d) } - case 7131: + case 7131: // SCIMITAR { d := scimitarW.Init( value.VendorId, @@ -1763,6 +1844,27 @@ func Init() { } dev.AddPairedDevice(value.ProductId, d) } + case 11010: // K70 CORE TKL WIRELESS + { + d := k70coretklW.Init( + value.VendorId, + productId, + value.ProductId, + dev.GetDevice(), + value.Endpoint, + value.Serial, + dev.Serial, + ) + devices[d.Serial] = &Device{ + ProductType: productTypeK70CoreTklW, + Product: "K70 CORE TKL WIRELESS", + Serial: d.Serial, + Firmware: d.Firmware, + Image: "icon-keyboard.svg", + Instance: d, + } + dev.AddPairedDevice(value.ProductId, d) + } default: logger.Log(logger.Fields{"productId": value.ProductId}).Warn("Unsupported device detected") } @@ -1782,7 +1884,7 @@ func Init() { Product: dev.Product, Serial: dev.Serial, Firmware: dev.Firmware, - Image: "icon-headphone.svg", + Image: "icon-headphone-stand.svg", Instance: dev, } }(vendorId, productId, key) diff --git a/src/devices/harpoonW/harpoonW.go b/src/devices/harpoonW/harpoonW.go index 3fb3833..801479d 100644 --- a/src/devices/harpoonW/harpoonW.go +++ b/src/devices/harpoonW/harpoonW.go @@ -936,9 +936,6 @@ func (d *Device) setDeviceColor() { // DPI dpiColor := d.DeviceProfile.Profiles[d.DeviceProfile.Profile].Color - dpiColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) - dpiColor = rgb.ModifyBrightness(*dpiColor) - dpiLeds := d.DeviceProfile.Profiles[d.DeviceProfile.Profile] for i := 0; i < len(dpiLeds.ColorIndex); i++ { dpiColorIndexRange := dpiLeds.ColorIndex[i] diff --git a/src/devices/harpoonWU/harpoonWU.go b/src/devices/harpoonWU/harpoonWU.go index a2e9e09..7970aa3 100644 --- a/src/devices/harpoonWU/harpoonWU.go +++ b/src/devices/harpoonWU/harpoonWU.go @@ -947,9 +947,6 @@ func (d *Device) setDeviceColor() { // DPI dpiColor := d.DeviceProfile.Profiles[d.DeviceProfile.Profile].Color - dpiColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) - dpiColor = rgb.ModifyBrightness(*dpiColor) - dpiLeds := d.DeviceProfile.Profiles[d.DeviceProfile.Profile] for i := 0; i < len(dpiLeds.ColorIndex); i++ { dpiColorIndexRange := dpiLeds.ColorIndex[i] diff --git a/src/devices/harpoonrgbpro/harpoonrgbpro.go b/src/devices/harpoonrgbpro/harpoonrgbpro.go index 1653841..521bf02 100644 --- a/src/devices/harpoonrgbpro/harpoonrgbpro.go +++ b/src/devices/harpoonrgbpro/harpoonrgbpro.go @@ -41,6 +41,7 @@ type DeviceProfile struct { OriginalBrightness uint8 Label string Profile int + PollingRate int DPIColor *rgb.Color ZoneColors map[int]ZoneColors Profiles map[int]DPIProfile @@ -72,6 +73,7 @@ type Device struct { VendorId uint16 ProductId uint16 Brightness map[int]string + PollingRates map[int]string LEDChannels int ChangeableLedChannels int CpuTemp float32 @@ -97,6 +99,7 @@ var ( cmdWrite = byte(0x07) cmdRead = byte(0x0e) cmdFirmware = byte(0x01) + cmdSetPollingRate = []byte{0x0a, 0x00, 0x00} bufferSize = 64 readBufferSize = 16 bufferSizeWrite = bufferSize + 1 @@ -144,6 +147,13 @@ func Init(vendorId, productId uint16, key string) *Device { timerKeepAlive: &time.Ticker{}, autoRefreshChan: make(chan struct{}), timer: &time.Ticker{}, + PollingRates: map[int]string{ + 0: "Not Set", + 8: "125 Hz / 8 msec", + 4: "250 Hu / 4 msec", + 2: "500 Hz / 2 msec", + 1: "1000 Hz / 1 msec", + }, } d.getDebugMode() // Debug mode @@ -350,6 +360,87 @@ func (d *Device) saveRgbProfile() { } } +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + + d.dev = dev + d.setSoftwareMode() // Activate software mode + d.getDeviceFirmware() // Firmware + d.toggleExit() // Remove Exit flag + d.setDeviceColor(false) // Device color + d.controlListener() // Control listener + d.toggleDPI(false) // Set current DPI +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err := d.transfer(cmdWrite, cmdSetPollingRate, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to set mouse polling rate") + return 0 + } + time.Sleep(5000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + // UpdateRgbProfileData will update RGB profile data func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { if d.GetRgbProfile(profileName) == nil { @@ -771,6 +862,7 @@ func (d *Device) saveDeviceProfile() { } deviceProfile.Profile = 1 deviceProfile.SleepMode = 15 + deviceProfile.PollingRate = 1 } else { if d.DeviceProfile.BrightnessSlider == nil { deviceProfile.BrightnessSlider = &defaultBrightness @@ -788,6 +880,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.DPIColor = d.DeviceProfile.DPIColor deviceProfile.ZoneColors = d.DeviceProfile.ZoneColors deviceProfile.SleepMode = d.DeviceProfile.SleepMode + deviceProfile.PollingRate = d.DeviceProfile.PollingRate if len(d.DeviceProfile.Path) < 1 { deviceProfile.Path = profilePath diff --git a/src/devices/ironclaw/ironclaw.go b/src/devices/ironclaw/ironclaw.go index 2143ecb..a57de5d 100644 --- a/src/devices/ironclaw/ironclaw.go +++ b/src/devices/ironclaw/ironclaw.go @@ -25,17 +25,20 @@ import ( // DeviceProfile struct contains all device profile type DeviceProfile struct { - Active bool - Path string - Product string - Serial string - Brightness uint8 - RGBProfile string - Label string - Profile int - DPIColor *rgb.Color - ZoneColors map[int]ZoneColors - Profiles map[int]DPIProfile + Active bool + Path string + Product string + Serial string + Brightness uint8 + BrightnessSlider *uint8 + OriginalBrightness uint8 + RGBProfile string + Label string + Profile int + PollingRate int + DPIColor *rgb.Color + ZoneColors map[int]ZoneColors + Profiles map[int]DPIProfile } type ZoneColors struct { @@ -67,6 +70,7 @@ type Device struct { VendorId uint16 ProductId uint16 Brightness map[int]string + PollingRates map[int]string LEDChannels int CpuTemp float32 GpuTemp float32 @@ -86,6 +90,7 @@ var ( cmdRead = byte(0x0e) cmdWrite = byte(0x13) cmdGetFirmware = byte(0x01) + cmdSetPollingRate = []byte{0x0a, 0x00, 0x00} deviceRefreshInterval = 1000 bufferSize = 64 bufferSizeWrite = bufferSize + 1 @@ -122,6 +127,13 @@ func Init(vendorId, productId uint16, key string) *Device { LEDChannels: 2, autoRefreshChan: make(chan struct{}), timer: &time.Ticker{}, + PollingRates: map[int]string{ + 0: "Not Set", + 8: "125 Hz / 8 msec", + 4: "250 Hu / 4 msec", + 2: "500 Hz / 2 msec", + 1: "1000 Hz / 1 msec", + }, } d.getDebugMode() // Debug mode @@ -348,12 +360,14 @@ func (d *Device) setAutoRefresh() { // saveDeviceProfile will save device profile for persistent configuration func (d *Device) saveDeviceProfile() { + var defaultBrightness = uint8(100) profilePath := pwd + "/database/profiles/" + d.Serial + ".json" deviceProfile := &DeviceProfile{ - Product: d.Product, - Serial: d.Serial, - Path: profilePath, + Product: d.Product, + Serial: d.Serial, + Path: profilePath, + BrightnessSlider: &defaultBrightness, } // First save, assign saved profile to a device @@ -389,7 +403,7 @@ func (d *Device) saveDeviceProfile() { Green: 255, Blue: 0, Brightness: 1, - Hex: fmt.Sprintf("#%02x%02x%02x", 0, 255, 255), + Hex: fmt.Sprintf("#%02x%02x%02x", 0, 255, 0), } deviceProfile.Profiles = map[int]DPIProfile{ 0: { @@ -409,7 +423,14 @@ func (d *Device) saveDeviceProfile() { }, } deviceProfile.Profile = 1 + deviceProfile.PollingRate = 1 } else { + if d.DeviceProfile.BrightnessSlider == nil { + deviceProfile.BrightnessSlider = &defaultBrightness + d.DeviceProfile.BrightnessSlider = &defaultBrightness + } else { + deviceProfile.BrightnessSlider = d.DeviceProfile.BrightnessSlider + } deviceProfile.Active = d.DeviceProfile.Active deviceProfile.Brightness = d.DeviceProfile.Brightness deviceProfile.RGBProfile = d.DeviceProfile.RGBProfile @@ -418,6 +439,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profile = d.DeviceProfile.Profile deviceProfile.DPIColor = d.DeviceProfile.DPIColor deviceProfile.ZoneColors = d.DeviceProfile.ZoneColors + deviceProfile.PollingRate = d.DeviceProfile.PollingRate if len(d.DeviceProfile.Path) < 1 { deviceProfile.Path = profilePath @@ -694,6 +716,87 @@ func (d *Device) saveRgbProfile() { } } +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + + d.dev = dev + d.setSoftwareMode() // Activate software mode + d.getDeviceFirmware() // Firmware + d.toggleExit() // Remove Exit flag + d.setDeviceColor() // Device color + d.controlListener() // Control listener + d.toggleDPI(false) // Set current DPI +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + err := d.transfer(cmdSetPollingRate, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to set mouse polling rate") + return 0 + } + time.Sleep(5000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + // UpdateRgbProfileData will update RGB profile data func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { if d.GetRgbProfile(profileName) == nil { @@ -746,6 +849,45 @@ func (d *Device) ChangeDeviceBrightness(mode uint8) uint8 { return 1 } +// ChangeDeviceBrightnessValue will change device brightness via slider +func (d *Device) ChangeDeviceBrightnessValue(value uint8) uint8 { + if value < 0 || value > 100 { + return 0 + } + + d.DeviceProfile.BrightnessSlider = &value + d.saveDeviceProfile() + + if d.DeviceProfile.RGBProfile == "static" || d.DeviceProfile.RGBProfile == "mouse" { + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + } + return 1 +} + +// SchedulerBrightness will change device brightness via scheduler +func (d *Device) SchedulerBrightness(value uint8) uint8 { + if value == 0 { + d.DeviceProfile.OriginalBrightness = *d.DeviceProfile.BrightnessSlider + d.DeviceProfile.BrightnessSlider = &value + } else { + d.DeviceProfile.BrightnessSlider = &d.DeviceProfile.OriginalBrightness + } + + d.saveDeviceProfile() + if d.DeviceProfile.RGBProfile == "static" || d.DeviceProfile.RGBProfile == "mouse" { + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + } + return 1 +} + // SaveMouseZoneColors will save mouse zone colors func (d *Device) SaveMouseZoneColors(dpi rgb.Color, zoneColors map[int]rgb.Color) uint8 { i := 0 @@ -830,12 +972,12 @@ func (d *Device) setDeviceColor() { if d.DeviceProfile.Brightness != 0 { zoneColor.Color.Brightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) } - - zoneColors := rgb.ModifyBrightness(*zoneColor.Color) + zoneColor.Color.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) + zoneColor.Color = rgb.ModifyBrightness(*zoneColor.Color) static[key] = []byte{ - byte(zoneColors.Red), - byte(zoneColors.Green), - byte(zoneColors.Blue), + byte(zoneColor.Color.Red), + byte(zoneColor.Color.Green), + byte(zoneColor.Color.Blue), } } buffer = rgb.SetColor(static) @@ -851,11 +993,9 @@ func (d *Device) setDeviceColor() { return } - if d.DeviceProfile.Brightness != 0 { - profile.StartColor.Brightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) - } - + profile.StartColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) profileColor := rgb.ModifyBrightness(profile.StartColor) + for i := 0; i < d.LEDChannels; i++ { static[i] = []byte{ byte(profileColor.Red), @@ -918,11 +1058,9 @@ func (d *Device) setDeviceColor() { } // Brightness - if d.DeviceProfile.Brightness > 0 { - r.RGBBrightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) - r.RGBStartColor.Brightness = r.RGBBrightness - r.RGBEndColor.Brightness = r.RGBBrightness - } + r.RGBBrightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) + r.RGBStartColor.Brightness = r.RGBBrightness + r.RGBEndColor.Brightness = r.RGBBrightness switch d.DeviceProfile.RGBProfile { case "off": diff --git a/src/devices/ironclawW/ironclawW.go b/src/devices/ironclawW/ironclawW.go index da7a91e..b0ce154 100644 --- a/src/devices/ironclawW/ironclawW.go +++ b/src/devices/ironclawW/ironclawW.go @@ -31,18 +31,20 @@ type ZoneColors struct { // DeviceProfile struct contains all device profile type DeviceProfile struct { - Active bool - Path string - Product string - Serial string - Brightness uint8 - RGBProfile string - Label string - Profile int - DPIColor *rgb.Color - ZoneColors map[int]ZoneColors - Profiles map[int]DPIProfile - SleepMode int + Active bool + Path string + Product string + Serial string + Brightness uint8 + BrightnessSlider *uint8 + OriginalBrightness uint8 + RGBProfile string + Label string + Profile int + DPIColor *rgb.Color + ZoneColors map[int]ZoneColors + Profiles map[int]DPIProfile + SleepMode int } type DPIProfile struct { @@ -387,6 +389,45 @@ func (d *Device) ChangeDeviceBrightness(mode uint8) uint8 { return 1 } +// ChangeDeviceBrightnessValue will change device brightness via slider +func (d *Device) ChangeDeviceBrightnessValue(value uint8) uint8 { + if value < 0 || value > 100 { + return 0 + } + + d.DeviceProfile.BrightnessSlider = &value + d.saveDeviceProfile() + + if d.DeviceProfile.RGBProfile == "static" || d.DeviceProfile.RGBProfile == "mouse" { + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + } + return 1 +} + +// SchedulerBrightness will change device brightness via scheduler +func (d *Device) SchedulerBrightness(value uint8) uint8 { + if value == 0 { + d.DeviceProfile.OriginalBrightness = *d.DeviceProfile.BrightnessSlider + d.DeviceProfile.BrightnessSlider = &value + } else { + d.DeviceProfile.BrightnessSlider = &d.DeviceProfile.OriginalBrightness + } + + d.saveDeviceProfile() + if d.DeviceProfile.RGBProfile == "static" || d.DeviceProfile.RGBProfile == "mouse" { + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + } + return 1 +} + // SaveUserProfile will generate a new user profile configuration and save it to a file func (d *Device) SaveUserProfile(profileName string) uint8 { if d.DeviceProfile != nil { @@ -571,12 +612,14 @@ func (d *Device) getDeviceFirmware() { // saveDeviceProfile will save device profile for persistent configuration func (d *Device) saveDeviceProfile() { + var defaultBrightness = uint8(100) profilePath := pwd + "/database/profiles/" + d.Serial + ".json" deviceProfile := &DeviceProfile{ - Product: d.Product, - Serial: d.Serial, - Path: profilePath, + Product: d.Product, + Serial: d.Serial, + Path: profilePath, + BrightnessSlider: &defaultBrightness, } // First save, assign saved profile to a device @@ -659,6 +702,12 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profile = 1 deviceProfile.SleepMode = 15 } else { + if d.DeviceProfile.BrightnessSlider == nil { + deviceProfile.BrightnessSlider = &defaultBrightness + d.DeviceProfile.BrightnessSlider = &defaultBrightness + } else { + deviceProfile.BrightnessSlider = d.DeviceProfile.BrightnessSlider + } deviceProfile.Active = d.DeviceProfile.Active deviceProfile.Brightness = d.DeviceProfile.Brightness deviceProfile.RGBProfile = d.DeviceProfile.RGBProfile @@ -853,11 +902,6 @@ func (d *Device) setDeviceColor() { // DPI dpiColor := d.DeviceProfile.DPIColor - if d.DeviceProfile.Brightness != 0 { - dpiColor.Brightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) - } - dpiColor = rgb.ModifyBrightness(*dpiColor) - dpiLeds := d.DeviceProfile.Profiles[d.DeviceProfile.Profile] for i := 0; i < len(dpiLeds.ColorIndex); i++ { dpiColorIndexRange := dpiLeds.ColorIndex[i] @@ -875,11 +919,8 @@ func (d *Device) setDeviceColor() { if d.DeviceProfile.RGBProfile == "mouse" { for _, zoneColor := range d.DeviceProfile.ZoneColors { - if d.DeviceProfile.Brightness != 0 { - zoneColor.Color.Brightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) - } + zoneColor.Color.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) zoneColor.Color = rgb.ModifyBrightness(*zoneColor.Color) - zoneColorIndexRange := zoneColor.ColorIndex for key, zoneColorIndex := range zoneColorIndexRange { switch key { @@ -905,6 +946,7 @@ func (d *Device) setDeviceColor() { if d.DeviceProfile.Brightness != 0 { profile.StartColor.Brightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) } + profile.StartColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) profileColor := rgb.ModifyBrightness(profile.StartColor) for _, zoneColor := range d.DeviceProfile.ZoneColors { @@ -973,11 +1015,9 @@ func (d *Device) setDeviceColor() { } // Brightness - if d.DeviceProfile.Brightness > 0 { - r.RGBBrightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) - r.RGBStartColor.Brightness = r.RGBBrightness - r.RGBEndColor.Brightness = r.RGBBrightness - } + r.RGBBrightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) + r.RGBStartColor.Brightness = r.RGBBrightness + r.RGBEndColor.Brightness = r.RGBBrightness switch d.DeviceProfile.RGBProfile { case "off": diff --git a/src/devices/ironclawWU/ironclawWU.go b/src/devices/ironclawWU/ironclawWU.go index 6b50c03..d86cf7d 100644 --- a/src/devices/ironclawWU/ironclawWU.go +++ b/src/devices/ironclawWU/ironclawWU.go @@ -31,18 +31,20 @@ type ZoneColors struct { // DeviceProfile struct contains all device profile type DeviceProfile struct { - Active bool - Path string - Product string - Serial string - Brightness uint8 - RGBProfile string - Label string - Profile int - DPIColor *rgb.Color - ZoneColors map[int]ZoneColors - Profiles map[int]DPIProfile - SleepMode int + Active bool + Path string + Product string + Serial string + Brightness uint8 + BrightnessSlider *uint8 + OriginalBrightness uint8 + RGBProfile string + Label string + Profile int + DPIColor *rgb.Color + ZoneColors map[int]ZoneColors + Profiles map[int]DPIProfile + SleepMode int } type DPIProfile struct { @@ -391,6 +393,45 @@ func (d *Device) ChangeDeviceBrightness(mode uint8) uint8 { return 1 } +// ChangeDeviceBrightnessValue will change device brightness via slider +func (d *Device) ChangeDeviceBrightnessValue(value uint8) uint8 { + if value < 0 || value > 100 { + return 0 + } + + d.DeviceProfile.BrightnessSlider = &value + d.saveDeviceProfile() + + if d.DeviceProfile.RGBProfile == "static" || d.DeviceProfile.RGBProfile == "mouse" { + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + } + return 1 +} + +// SchedulerBrightness will change device brightness via scheduler +func (d *Device) SchedulerBrightness(value uint8) uint8 { + if value == 0 { + d.DeviceProfile.OriginalBrightness = *d.DeviceProfile.BrightnessSlider + d.DeviceProfile.BrightnessSlider = &value + } else { + d.DeviceProfile.BrightnessSlider = &d.DeviceProfile.OriginalBrightness + } + + d.saveDeviceProfile() + if d.DeviceProfile.RGBProfile == "static" || d.DeviceProfile.RGBProfile == "mouse" { + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + } + return 1 +} + // SaveUserProfile will generate a new user profile configuration and save it to a file func (d *Device) SaveUserProfile(profileName string) uint8 { if d.DeviceProfile != nil { @@ -588,12 +629,14 @@ func (d *Device) getDeviceFirmware() { // saveDeviceProfile will save device profile for persistent configuration func (d *Device) saveDeviceProfile() { + var defaultBrightness = uint8(100) profilePath := pwd + "/database/profiles/" + d.Serial + ".json" deviceProfile := &DeviceProfile{ - Product: d.Product, - Serial: d.Serial, - Path: profilePath, + Product: d.Product, + Serial: d.Serial, + Path: profilePath, + BrightnessSlider: &defaultBrightness, } // First save, assign saved profile to a device @@ -676,6 +719,12 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profile = 1 deviceProfile.SleepMode = 15 } else { + if d.DeviceProfile.BrightnessSlider == nil { + deviceProfile.BrightnessSlider = &defaultBrightness + d.DeviceProfile.BrightnessSlider = &defaultBrightness + } else { + deviceProfile.BrightnessSlider = d.DeviceProfile.BrightnessSlider + } deviceProfile.Active = d.DeviceProfile.Active deviceProfile.Brightness = d.DeviceProfile.Brightness deviceProfile.RGBProfile = d.DeviceProfile.RGBProfile @@ -866,11 +915,6 @@ func (d *Device) setDeviceColor() { // DPI dpiColor := d.DeviceProfile.DPIColor - if d.DeviceProfile.Brightness != 0 { - dpiColor.Brightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) - } - dpiColor = rgb.ModifyBrightness(*dpiColor) - dpiLeds := d.DeviceProfile.Profiles[d.DeviceProfile.Profile] for i := 0; i < len(dpiLeds.ColorIndex); i++ { dpiColorIndexRange := dpiLeds.ColorIndex[i] @@ -888,9 +932,7 @@ func (d *Device) setDeviceColor() { if d.DeviceProfile.RGBProfile == "mouse" { for _, zoneColor := range d.DeviceProfile.ZoneColors { - if d.DeviceProfile.Brightness != 0 { - zoneColor.Color.Brightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) - } + zoneColor.Color.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) zoneColor.Color = rgb.ModifyBrightness(*zoneColor.Color) zoneColorIndexRange := zoneColor.ColorIndex @@ -914,10 +956,7 @@ func (d *Device) setDeviceColor() { if profile == nil { return } - - if d.DeviceProfile.Brightness != 0 { - profile.StartColor.Brightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) - } + profile.StartColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) profileColor := rgb.ModifyBrightness(profile.StartColor) for _, zoneColor := range d.DeviceProfile.ZoneColors { @@ -986,11 +1025,9 @@ func (d *Device) setDeviceColor() { } // Brightness - if d.DeviceProfile.Brightness > 0 { - r.RGBBrightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) - r.RGBStartColor.Brightness = r.RGBBrightness - r.RGBEndColor.Brightness = r.RGBBrightness - } + r.RGBBrightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) + r.RGBStartColor.Brightness = r.RGBBrightness + r.RGBEndColor.Brightness = r.RGBBrightness switch d.DeviceProfile.RGBProfile { case "off": diff --git a/src/devices/k100/k100.go b/src/devices/k100/k100.go index b5eff21..b75a940 100644 --- a/src/devices/k100/k100.go +++ b/src/devices/k100/k100.go @@ -40,6 +40,7 @@ type DeviceProfile struct { Layout string Keyboards map[string]*keyboards.Keyboard Profile string + PollingRate int BrightnessLevel uint16 Profiles []string ControlDial int @@ -62,6 +63,7 @@ type Device struct { VendorId uint16 ProductId uint16 Brightness map[int]string + PollingRates map[int]string LEDChannels int CpuTemp float32 GpuTemp float32 @@ -89,6 +91,7 @@ var ( dataTypeSetColor = []byte{0x12, 0x00} dataTypeSubColor = []byte{0x07, 0x00} cmdWriteColor = []byte{0x06, 0x01} + cmdSetPollingRate = []byte{0x01, 0x01, 0x00} deviceRefreshInterval = 1000 deviceKeepAlive = 20000 transferTimeout = 500 @@ -133,6 +136,16 @@ func Init(vendorId, productId uint16, key string) *Device { keepAliveChan: make(chan struct{}), autoRefreshChan: make(chan struct{}), listener: nil, + PollingRates: map[int]string{ + 0: "Not Set", + 1: "125 Hz / 8 msec", + 2: "250 Hu / 4 msec", + 3: "500 Hz / 2 msec", + 4: "1000 Hz / 1 msec", + 5: "2000 Hz / 0.5 msec", + 6: "4000 Hz / 0.25 msec", + 7: "8000 Hz / 0.125 msec", + }, } d.getDebugMode() // Debug mode @@ -364,6 +377,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Brightness = 0 deviceProfile.Layout = "US" deviceProfile.ControlDial = 1 + deviceProfile.PollingRate = 4 } else { if len(d.DeviceProfile.Layout) == 0 { deviceProfile.Layout = "US" @@ -379,6 +393,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Keyboards = d.DeviceProfile.Keyboards deviceProfile.ControlDial = d.DeviceProfile.ControlDial deviceProfile.BrightnessLevel = d.DeviceProfile.BrightnessLevel + deviceProfile.PollingRate = d.DeviceProfile.PollingRate if len(d.DeviceProfile.Path) < 1 { deviceProfile.Path = profilePath d.DeviceProfile.Path = profilePath @@ -518,9 +533,6 @@ func (d *Device) setKeepAlive() { for { select { case <-d.timerKeepAlive.C: - if d.Exit { - return - } d.keepAlive() case <-d.keepAliveChan: d.timerKeepAlive.Stop() @@ -599,6 +611,87 @@ func (d *Device) saveRgbProfile() { } } +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + d.dev = dev + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.getDeviceFirmware() // Firmware + d.toggleExit() // Toggle exit mode + d.setDeviceColor() // Device color + d.setBrightnessLevel() // Brightness + d.controlListener() // Control listener +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err := d.transfer(cmdSetPollingRate, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + time.Sleep(8000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + // UpdateRgbProfileData will update RGB profile data func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { if d.GetRgbProfile(profileName) == nil { @@ -953,10 +1046,6 @@ func (d *Device) setDeviceColor() { // Reset var buf = make([]byte, colorPacketLength) - for i := 0; i < colorPacketLength; i++ { - buf[i] = 0xff - } - d.writeColor(buf) if d.DeviceProfile == nil { logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. DeviceProfile is null!") return @@ -991,7 +1080,6 @@ func (d *Device) setDeviceColor() { if d.DeviceProfile.Brightness != 0 { profile.StartColor.Brightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) } - profileColor := rgb.ModifyBrightness(profile.StartColor) for _, rows := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { for _, keys := range rows.Keys { @@ -1222,7 +1310,7 @@ func (d *Device) transfer(endpoint, buffer []byte) ([]byte, error) { logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device") return nil, err } - + // Get data from a device if _, err := d.dev.Read(bufferR); err != nil { logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to read data from device") diff --git a/src/devices/k100air/k100air.go b/src/devices/k100air/k100air.go index c16860d..b7f7b51 100644 --- a/src/devices/k100air/k100air.go +++ b/src/devices/k100air/k100air.go @@ -71,6 +71,8 @@ type Device struct { autoRefreshChan chan struct{} keepAliveChan chan struct{} mutex sync.Mutex + UIKeyboard string + UIKeyboardRow string } var ( @@ -83,6 +85,7 @@ var ( dataTypeSetColor = []byte{0x12, 0x00} dataTypeSubColor = []byte{0x07, 0x00} cmdWriteColor = []byte{0x06, 0x01} + cmdKeepAlive = []byte{0x12} deviceRefreshInterval = 1000 deviceKeepAlive = 20000 transferTimeout = 500 @@ -123,6 +126,8 @@ func Init(vendorId, productId uint16, key string) *Device { Layouts: keyboards.GetLayouts(keyboardKey), autoRefreshChan: make(chan struct{}), keepAliveChan: make(chan struct{}), + UIKeyboard: "keyboard-7", + UIKeyboardRow: "keyboard-row-25", } d.getDebugMode() // Debug mode @@ -489,7 +494,7 @@ func (d *Device) keepAlive() { if d.Exit { return } - _, err := d.transfer([]byte{0x12}, nil) + _, err := d.transfer(cmdKeepAlive, nil) if err != nil { logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device") } @@ -502,9 +507,6 @@ func (d *Device) setKeepAlive() { for { select { case <-d.timerKeepAlive.C: - if d.Exit { - return - } d.keepAlive() case <-d.keepAliveChan: d.timerKeepAlive.Stop() diff --git a/src/devices/k100airW/k100airW.go b/src/devices/k100airW/k100airW.go index 7f887f7..1880c01 100644 --- a/src/devices/k100airW/k100airW.go +++ b/src/devices/k100airW/k100airW.go @@ -75,6 +75,8 @@ type Device struct { Endpoint byte mutex sync.Mutex Exit bool + UIKeyboard string + UIKeyboardRow string } var ( @@ -148,6 +150,8 @@ func Init(vendorId, slipstreamId, productId uint16, dev *hid.Device, endpoint by 30: "30 minutes", 60: "1 hour", }, + UIKeyboard: "keyboard-7", + UIKeyboardRow: "keyboard-row-25", } d.getDebugMode() // Debug mode diff --git a/src/devices/k55core/k55core.go b/src/devices/k55core/k55core.go index 9b2b6ac..be09f6b 100644 --- a/src/devices/k55core/k55core.go +++ b/src/devices/k55core/k55core.go @@ -42,6 +42,7 @@ type DeviceProfile struct { Layout string Keyboards map[string]*keyboards.Keyboard Profile string + PollingRate int Profiles []string } @@ -62,6 +63,7 @@ type Device struct { VendorId uint16 ProductId uint16 Brightness map[int]string + PollingRates map[int]string LEDChannels int CpuTemp float32 GpuTemp float32 @@ -73,6 +75,8 @@ type Device struct { autoRefreshChan chan struct{} keepAliveChan chan struct{} mutex sync.Mutex + UIKeyboard string + UIKeyboardRow string } var ( @@ -84,6 +88,8 @@ var ( dataTypeSetColor = []byte{0x12, 0x00} dataTypeSubColor = []byte{0x07, 0x00} cmdWriteColor = []byte{0x06, 0x01} + cmdSetPollingRate = []byte{0x01, 0x01, 0x00} + cmdKeepAlive = []byte{0x12} deviceRefreshInterval = 1000 deviceKeepAlive = 20000 transferTimeout = 500 @@ -124,6 +130,15 @@ func Init(vendorId, productId uint16, key string) *Device { Layouts: keyboards.GetLayouts(keyboardKey), autoRefreshChan: make(chan struct{}), keepAliveChan: make(chan struct{}), + UIKeyboard: "keyboard-6", + UIKeyboardRow: "keyboard-row-25", + PollingRates: map[int]string{ + 0: "Not Set", + 1: "125 Hz / 8 msec", + 2: "250 Hu / 4 msec", + 3: "500 Hz / 2 msec", + 4: "1000 Hz / 1 msec", + }, } d.getDebugMode() // Debug mode @@ -351,6 +366,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profile = "default" deviceProfile.Profiles = []string{"default"} deviceProfile.Layout = "US" + deviceProfile.PollingRate = 4 } else { if d.DeviceProfile.BrightnessSlider == nil { deviceProfile.BrightnessSlider = &defaultBrightness @@ -372,6 +388,8 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profiles = d.DeviceProfile.Profiles deviceProfile.Keyboards = d.DeviceProfile.Keyboards deviceProfile.OriginalBrightness = d.DeviceProfile.OriginalBrightness + deviceProfile.PollingRate = d.DeviceProfile.PollingRate + if len(d.DeviceProfile.Path) < 1 { deviceProfile.Path = profilePath d.DeviceProfile.Path = profilePath @@ -498,7 +516,7 @@ func (d *Device) keepAlive() { if d.Exit { return } - _, err := d.transfer([]byte{0x12}, nil) + _, err := d.transfer(cmdKeepAlive, nil) if err != nil { logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device") } @@ -511,9 +529,6 @@ func (d *Device) setKeepAlive() { for { select { case <-d.timerKeepAlive.C: - if d.Exit { - return - } d.keepAlive() case <-d.keepAliveChan: d.timerKeepAlive.Stop() @@ -592,6 +607,86 @@ func (d *Device) saveRgbProfile() { } } +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + d.dev = dev + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.getDeviceFirmware() // Firmware + d.toggleExit() // Toggle exit mode + d.setDeviceColor() // Device color + d.controlButtonListener() // Control buttons +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err := d.transfer(cmdSetPollingRate, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + time.Sleep(8000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + // UpdateRgbProfileData will update RGB profile data func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { if d.GetRgbProfile(profileName) == nil { diff --git a/src/devices/k65plus/k65plus.go b/src/devices/k65plus/k65plus.go index 76f4cb3..93acddd 100644 --- a/src/devices/k65plus/k65plus.go +++ b/src/devices/k65plus/k65plus.go @@ -74,6 +74,8 @@ type Device struct { autoRefreshChan chan struct{} keepAliveChan chan struct{} mutex sync.Mutex + UIKeyboard string + UIKeyboardRow string } var ( @@ -131,6 +133,8 @@ func Init(vendorId, productId uint16, key string) *Device { }, autoRefreshChan: make(chan struct{}), keepAliveChan: make(chan struct{}), + UIKeyboard: "keyboard-6", + UIKeyboardRow: "keyboard-row-17", } d.getDebugMode() // Debug mode diff --git a/src/devices/k65plusW/k65plusW.go b/src/devices/k65plusW/k65plusW.go index 61974ea..f600484 100644 --- a/src/devices/k65plusW/k65plusW.go +++ b/src/devices/k65plusW/k65plusW.go @@ -79,6 +79,8 @@ type Device struct { keepAliveChan chan struct{} mutex sync.Mutex Connected bool + UIKeyboard string + UIKeyboardRow string } var ( @@ -92,6 +94,7 @@ var ( dataTypeSubColor = []byte{0x07, 0x01} cmdWriteColor = []byte{0x06, 0x01} cmdSleep = []byte{0x01, 0x0e, 0x00} + cmdKeepAlive = []byte{0x12} cmdDongle = 0x08 cmdKeyboard = 0x09 deviceRefreshInterval = 1000 @@ -157,6 +160,8 @@ func Init(vendorId, productId uint16, key string) *Device { }, autoRefreshChan: make(chan struct{}), keepAliveChan: make(chan struct{}), + UIKeyboard: "keyboard-6", + UIKeyboardRow: "keyboard-row-17", } d.getDebugMode() // Debug mode @@ -569,12 +574,14 @@ func (d *Device) keepAlive() { if d.Exit { return } - _, err := d.transfer([]byte{0x12}, nil, byte(cmdDongle)) + _, err := d.transfer(cmdKeepAlive, nil, byte(cmdDongle)) if err != nil { logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device") } - - _, err = d.transfer([]byte{0x12}, nil, byte(cmdKeyboard)) + if d.Exit { + return + } + _, err = d.transfer(cmdKeepAlive, nil, byte(cmdKeyboard)) if err != nil { logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device") } @@ -587,9 +594,6 @@ func (d *Device) setKeepAlive() { for { select { case <-d.timerKeepAlive.C: - if d.Exit { - return - } d.keepAlive() case <-d.keepAliveChan: d.timerKeepAlive.Stop() diff --git a/src/devices/k65pm/k65pm.go b/src/devices/k65pm/k65pm.go index 492fb18..afbec9c 100644 --- a/src/devices/k65pm/k65pm.go +++ b/src/devices/k65pm/k65pm.go @@ -41,6 +41,7 @@ type DeviceProfile struct { Layout string Keyboards map[string]*keyboards.Keyboard Profile string + PollingRate int Profiles []string } @@ -58,7 +59,9 @@ type Device struct { OriginalProfile *DeviceProfile Template string VendorId uint16 + ProductId uint16 Brightness map[int]string + PollingRates map[int]string LEDChannels int CpuTemp float32 GpuTemp float32 @@ -68,6 +71,8 @@ type Device struct { autoRefreshChan chan struct{} mutex sync.Mutex Exit bool + UIKeyboard string + UIKeyboardRow string } var ( @@ -79,6 +84,7 @@ var ( dataTypeSetColor = []byte{0x12, 0x00} dataTypeSubColor = []byte{0x07, 0x00} cmdWriteColor = []byte{0x06, 0x00} + cmdSetPollingRate = []byte{0x01, 0x01, 0x00} deviceRefreshInterval = 1000 transferTimeout = 500 bufferSize = 128 @@ -103,9 +109,10 @@ func Init(vendorId, productId uint16, key string) *Device { // Init new struct with HID device d := &Device{ - dev: dev, - Template: "k65pm.html", - VendorId: vendorId, + dev: dev, + Template: "k65pm.html", + VendorId: vendorId, + ProductId: productId, Brightness: map[int]string{ 0: "RGB Profile", 1: "33 %", @@ -116,6 +123,18 @@ func Init(vendorId, productId uint16, key string) *Device { LEDChannels: 130, Layouts: keyboards.GetLayouts(keyboardKey), autoRefreshChan: make(chan struct{}), + UIKeyboard: "keyboard-5", + UIKeyboardRow: "keyboard-row-17", + PollingRates: map[int]string{ + 0: "Not Set", + 1: "125 Hz / 8 msec", + 2: "250 Hu / 4 msec", + 3: "500 Hz / 2 msec", + 4: "1000 Hz / 1 msec", + 5: "2000 Hz / 0.5 msec", + 6: "4000 Hz / 0.25 msec", + 7: "8000 Hz / 0.125 msec", + }, } d.getDebugMode() // Debug mode @@ -338,6 +357,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profile = "default" deviceProfile.Profiles = []string{"default"} deviceProfile.Layout = "US" + deviceProfile.PollingRate = 4 } else { if d.DeviceProfile.BrightnessSlider == nil { deviceProfile.BrightnessSlider = &defaultBrightness @@ -360,6 +380,8 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profile = d.DeviceProfile.Profile deviceProfile.Profiles = d.DeviceProfile.Profiles deviceProfile.Keyboards = d.DeviceProfile.Keyboards + deviceProfile.PollingRate = d.DeviceProfile.PollingRate + if len(d.DeviceProfile.Path) < 1 { deviceProfile.Path = profilePath d.DeviceProfile.Path = profilePath @@ -550,6 +572,85 @@ func (d *Device) saveRgbProfile() { } } +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + d.dev = dev + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.getDeviceFirmware() // Firmware + d.toggleExit() // Toggle exit mode + d.setDeviceColor() // Device color +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err := d.transfer(cmdSetPollingRate, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + time.Sleep(5000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + // UpdateRgbProfileData will update RGB profile data func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { if d.GetRgbProfile(profileName) == nil { diff --git a/src/devices/k70core/k70core.go b/src/devices/k70core/k70core.go index d1367c2..e8e0a80 100644 --- a/src/devices/k70core/k70core.go +++ b/src/devices/k70core/k70core.go @@ -40,6 +40,7 @@ type DeviceProfile struct { Layout string Keyboards map[string]*keyboards.Keyboard Profile string + PollingRate int Profiles []string BrightnessLevel uint16 ControlDial int @@ -62,6 +63,7 @@ type Device struct { VendorId uint16 ProductId uint16 Brightness map[int]string + PollingRates map[int]string LEDChannels int CpuTemp float32 GpuTemp float32 @@ -74,6 +76,8 @@ type Device struct { autoRefreshChan chan struct{} keepAliveChan chan struct{} mutex sync.Mutex + UIKeyboard string + UIKeyboardRow string } var ( @@ -87,6 +91,7 @@ var ( cmdWriteColor = []byte{0x06, 0x01} cmdBrightness = []byte{0x01, 0x02, 0x00} cmdKeepAlive = []byte{0x12} + cmdSetPollingRate = []byte{0x01, 0x01, 0x00} deviceRefreshInterval = 1000 deviceKeepAlive = 20000 transferTimeout = 500 @@ -131,6 +136,15 @@ func Init(vendorId, productId uint16, key string) *Device { Layouts: keyboards.GetLayouts(keyboardKey), autoRefreshChan: make(chan struct{}), keepAliveChan: make(chan struct{}), + UIKeyboard: "keyboard-6", + UIKeyboardRow: "keyboard-row-25", + PollingRates: map[int]string{ + 0: "Not Set", + 1: "125 Hz / 8 msec", + 2: "250 Hu / 4 msec", + 3: "500 Hz / 2 msec", + 4: "1000 Hz / 1 msec", + }, } d.getDebugMode() // Debug mode @@ -340,9 +354,6 @@ func (d *Device) setKeepAlive() { for { select { case <-d.timerKeepAlive.C: - if d.Exit { - return - } d.keepAlive() case <-d.keepAliveChan: d.timerKeepAlive.Stop() @@ -387,6 +398,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Layout = "US" deviceProfile.ControlDial = 1 deviceProfile.BrightnessLevel = 1000 + deviceProfile.PollingRate = 4 } else { if len(d.DeviceProfile.Layout) == 0 { deviceProfile.Layout = "US" @@ -402,6 +414,8 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profile = d.DeviceProfile.Profile deviceProfile.Profiles = d.DeviceProfile.Profiles deviceProfile.Keyboards = d.DeviceProfile.Keyboards + deviceProfile.PollingRate = d.DeviceProfile.PollingRate + if len(d.DeviceProfile.Path) < 1 { deviceProfile.Path = profilePath d.DeviceProfile.Path = profilePath @@ -592,6 +606,86 @@ func (d *Device) saveRgbProfile() { } } +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + d.dev = dev + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.getDeviceFirmware() // Firmware + d.toggleExit() // Toggle exit mode + d.setDeviceColor() // Device color + d.controlDialListener() // Control Dial +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err := d.transfer(cmdSetPollingRate, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + time.Sleep(8000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + // UpdateRgbProfileData will update RGB profile data func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { if d.GetRgbProfile(profileName) == nil { diff --git a/src/devices/k70coretkl/k70coretkl.go b/src/devices/k70coretkl/k70coretkl.go new file mode 100644 index 0000000..2917bdb --- /dev/null +++ b/src/devices/k70coretkl/k70coretkl.go @@ -0,0 +1,1539 @@ +package k70coretkl + +// Package: K70 CORE TKL +// This is the primary package for K70 CORE TKL. +// All device actions are controlled from this package. +// Author: Nikola Jurkovic +// License: GPL-3.0 or later + +import ( + "OpenLinkHub/src/common" + "OpenLinkHub/src/config" + "OpenLinkHub/src/inputmanager" + "OpenLinkHub/src/keyboards" + "OpenLinkHub/src/logger" + "OpenLinkHub/src/rgb" + "OpenLinkHub/src/temperatures" + "encoding/binary" + "encoding/json" + "fmt" + "github.com/sstallion/go-hid" + "os" + "regexp" + "slices" + "strings" + "sync" + "time" +) + +// DeviceProfile struct contains all device profile +type DeviceProfile struct { + Active bool + Path string + Product string + Serial string + LCDMode uint8 + LCDRotation uint8 + Brightness uint8 + RGBProfile string + Label string + Layout string + Keyboards map[string]*keyboards.Keyboard + Profile string + PollingRate int + Profiles []string + BrightnessLevel uint16 + ControlDial int +} + +type Device struct { + Debug bool + dev *hid.Device + listener *hid.Device + Manufacturer string `json:"manufacturer"` + Product string `json:"product"` + Serial string `json:"serial"` + Firmware string `json:"firmware"` + activeRgb *rgb.ActiveRGB + UserProfiles map[string]*DeviceProfile `json:"userProfiles"` + Devices map[int]string `json:"devices"` + DeviceProfile *DeviceProfile + OriginalProfile *DeviceProfile + Template string + VendorId uint16 + ProductId uint16 + Brightness map[int]string + PollingRates map[int]string + LEDChannels int + CpuTemp float32 + GpuTemp float32 + Layouts []string + Rgb *rgb.RGB + ControlDialOptions map[int]string + Exit bool + timer *time.Ticker + timerKeepAlive *time.Ticker + autoRefreshChan chan struct{} + keepAliveChan chan struct{} + mutex sync.Mutex + UIKeyboard string + UIKeyboardRow string +} + +var ( + pwd = "" + cmdSoftwareMode = []byte{0x01, 0x03, 0x00, 0x02} + cmdHardwareMode = []byte{0x01, 0x03, 0x00, 0x01} + cmdActivateLed = []byte{0x0d, 0x01, 0x22} + cmdGetFirmware = []byte{0x02, 0x13} + dataTypeSetColor = []byte{0x12, 0x00} + dataTypeSubColor = []byte{0x07, 0x01} + cmdWriteColor = []byte{0x06, 0x01} + cmdBrightness = []byte{0x01, 0x02, 0x00} + cmdKeepAlive = []byte{0x12} + cmdSetPollingRate = []byte{0x01, 0x01, 0x00} + deviceRefreshInterval = 1000 + deviceKeepAlive = 20000 + transferTimeout = 500 + bufferSize = 64 + bufferSizeWrite = bufferSize + 1 + headerSize = 2 + headerWriteSize = 4 + maxBufferSizePerRequest = 61 + colorPacketLength = 377 + keyboardKey = "k70coretkl-default" + defaultLayout = "k70coretkl-default-US" +) + +func Init(vendorId, productId uint16, key string) *Device { + // Set global working directory + pwd = config.GetConfig().ConfigPath + + dev, err := hid.OpenPath(key) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": vendorId, "productId": productId}).Error("Unable to open HID device") + return nil + } + + // Init new struct with HID device + d := &Device{ + dev: dev, + Template: "k70coretkl.html", + VendorId: vendorId, + ProductId: productId, + Brightness: map[int]string{ + 0: "RGB Profile", + 1: "33 %", + 2: "66 %", + 3: "100 %", + }, + ControlDialOptions: map[int]string{ + 1: "Volume Control", + 2: "Brightness", + }, + Product: "K70 CORE TKL", + LEDChannels: 125, + Layouts: keyboards.GetLayouts(keyboardKey), + autoRefreshChan: make(chan struct{}), + keepAliveChan: make(chan struct{}), + UIKeyboard: "keyboard-6", + UIKeyboardRow: "keyboard-row-20", + PollingRates: map[int]string{ + 0: "Not Set", + 1: "125 Hz / 8 msec", + 2: "250 Hu / 4 msec", + 3: "500 Hz / 2 msec", + 4: "1000 Hz / 1 msec", + }, + } + + d.getDebugMode() // Debug mode + d.getManufacturer() // Manufacturer + d.getSerial() // Serial + d.loadRgb() // Load RGB + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.getDeviceFirmware() // Firmware + d.loadDeviceProfiles() // Load all device profiles + d.saveDeviceProfile() // Save profile + d.setAutoRefresh() // Set auto device refresh + d.setDeviceColor() // Device color + d.setKeepAlive() // Keepalive + d.controlDialListener() // Control Dial + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Device successfully initialized") + return d +} + +// GetRgbProfiles will return RGB profiles for a target device +func (d *Device) GetRgbProfiles() interface{} { + return d.Rgb +} + +// Stop will stop all device operations and switch a device back to hardware mode +func (d *Device) Stop() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Stop() + } + + d.timer.Stop() + d.timerKeepAlive.Stop() + var once sync.Once + go func() { + once.Do(func() { + if d.autoRefreshChan != nil { + close(d.autoRefreshChan) + } + close(d.keepAliveChan) + }) + }() + + d.setHardwareMode() + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Device stopped") +} + +// loadRgb will load RGB file if found, or create the default. +func (d *Device) loadRgb() { + rgbDirectory := pwd + "/database/rgb/" + rgbFilename := rgbDirectory + d.Serial + ".json" + + // Check if filename has .json extension + if !common.IsValidExtension(rgbFilename, ".json") { + return + } + + if !common.FileExists(rgbFilename) { + profile := rgb.GetRGB() + profile.Device = d.Product + + // Convert to JSON + buffer, err := json.MarshalIndent(profile, "", " ") + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to encode RGB json") + return + } + + // Create profile filename + file, err := os.Create(rgbFilename) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to create RGB json file") + return + } + + // Write JSON buffer to file + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to write to RGB json file") + return + } + + // Close file + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to close RGB json file") + return + } + } + + file, err := os.Open(rgbFilename) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to load RGB") + return + } + if err = json.NewDecoder(file).Decode(&d.Rgb); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to decode profile") + return + } + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"location": rgbFilename, "serial": d.Serial}).Warn("Failed to close file handle") + } +} + +// GetRgbProfile will return rgb.Profile struct +func (d *Device) GetRgbProfile(profile string) *rgb.Profile { + if d.Rgb == nil { + return nil + } + + if val, ok := d.Rgb.Profiles[profile]; ok { + return &val + } + return nil +} + +// GetDeviceTemplate will return device template name +func (d *Device) GetDeviceTemplate() string { + return d.Template +} + +// getManufacturer will return device manufacturer +func (d *Device) getDebugMode() { + d.Debug = config.GetConfig().Debug +} + +// getManufacturer will return device manufacturer +func (d *Device) getManufacturer() { + manufacturer, err := d.dev.GetMfrStr() + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to get manufacturer") + } + d.Manufacturer = manufacturer +} + +// getProduct will return device name +func (d *Device) getProduct() { + product, err := d.dev.GetProductStr() + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to get product") + } + d.Product = product +} + +// getSerial will return device serial number +func (d *Device) getSerial() { + serial, err := d.dev.GetSerialNbr() + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to get device serial number") + } + d.Serial = serial +} + +// setHardwareMode will switch a device to hardware mode +func (d *Device) setHardwareMode() { + _, err := d.transfer(cmdHardwareMode, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to change device mode") + } +} + +// setSoftwareMode will switch a device to software mode +func (d *Device) setSoftwareMode() { + _, err := d.transfer(cmdSoftwareMode, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to change device mode") + } +} + +// getDeviceFirmware will return a device firmware version out as string +func (d *Device) getDeviceFirmware() { + fw, err := d.transfer( + cmdGetFirmware, + nil, + ) + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to write to a device") + } + + v1, v2, v3 := int(fw[3]), int(fw[4]), int(binary.LittleEndian.Uint16(fw[5:7])) + d.Firmware = fmt.Sprintf("%d.%d.%d", v1, v2, v3) +} + +// keepAlive will keep a device alive +func (d *Device) keepAlive() { + if d.Exit { + return + } + _, err := d.transfer(cmdKeepAlive, nil) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device") + } +} + +// setAutoRefresh will refresh device data +func (d *Device) setKeepAlive() { + d.timerKeepAlive = time.NewTicker(time.Duration(deviceKeepAlive) * time.Millisecond) + go func() { + for { + select { + case <-d.timerKeepAlive.C: + d.keepAlive() + case <-d.keepAliveChan: + d.timerKeepAlive.Stop() + return + } + } + }() +} + +// initLeds will initialize LED ports +func (d *Device) initLeds() { + _, err := d.transfer(cmdActivateLed, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to change device mode") + } + time.Sleep(time.Duration(transferTimeout) * time.Millisecond) +} + +// saveDeviceProfile will save device profile for persistent configuration +func (d *Device) saveDeviceProfile() { + profilePath := pwd + "/database/profiles/" + d.Serial + ".json" + keyboardMap := make(map[string]*keyboards.Keyboard, 0) + + deviceProfile := &DeviceProfile{ + Product: d.Product, + Serial: d.Serial, + Path: profilePath, + } + + // First save, assign saved profile to a device + if d.DeviceProfile == nil { + // RGB, Label + deviceProfile.RGBProfile = "keyboard" + deviceProfile.Label = "Keyboard" + deviceProfile.Active = true + keyboardMap["default"] = keyboards.GetKeyboard(defaultLayout) + deviceProfile.Keyboards = keyboardMap + deviceProfile.Profile = "default" + deviceProfile.Profiles = []string{"default"} + deviceProfile.Layout = "US" + deviceProfile.ControlDial = 1 + deviceProfile.BrightnessLevel = 1000 + deviceProfile.PollingRate = 4 + } else { + if len(d.DeviceProfile.Layout) == 0 { + deviceProfile.Layout = "US" + } else { + deviceProfile.Layout = d.DeviceProfile.Layout + } + deviceProfile.ControlDial = d.DeviceProfile.ControlDial + deviceProfile.BrightnessLevel = d.DeviceProfile.BrightnessLevel + deviceProfile.Active = d.DeviceProfile.Active + deviceProfile.Brightness = d.DeviceProfile.Brightness + deviceProfile.RGBProfile = d.DeviceProfile.RGBProfile + deviceProfile.Label = d.DeviceProfile.Label + deviceProfile.Profile = d.DeviceProfile.Profile + deviceProfile.Profiles = d.DeviceProfile.Profiles + deviceProfile.Keyboards = d.DeviceProfile.Keyboards + if len(d.DeviceProfile.Path) < 1 { + deviceProfile.Path = profilePath + d.DeviceProfile.Path = profilePath + } else { + deviceProfile.Path = d.DeviceProfile.Path + } + deviceProfile.LCDMode = d.DeviceProfile.LCDMode + deviceProfile.LCDRotation = d.DeviceProfile.LCDRotation + deviceProfile.PollingRate = d.DeviceProfile.PollingRate + } + + // Convert to JSON + buffer, err := json.MarshalIndent(deviceProfile, "", " ") + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to convert to json format") + return + } + + // Create profile filename + file, fileErr := os.Create(deviceProfile.Path) + if fileErr != nil { + logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Error("Unable to create new device profile") + return + } + + // Write JSON buffer to file + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Error("Unable to write data") + return + } + + // Close file + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Fatal("Unable to close file handle") + } + + d.loadDeviceProfiles() // Reload +} + +// loadDeviceProfiles will load custom user profiles +func (d *Device) loadDeviceProfiles() { + profileList := make(map[string]*DeviceProfile, 0) + userProfileDirectory := pwd + "/database/profiles/" + + files, err := os.ReadDir(userProfileDirectory) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": userProfileDirectory, "serial": d.Serial}).Fatal("Unable to read content of a folder") + } + + for _, fi := range files { + pf := &DeviceProfile{} + if fi.IsDir() { + continue // Exclude folders if any + } + + // Define a full path of filename + profileLocation := userProfileDirectory + fi.Name() + + // Check if filename has .json extension + if !common.IsValidExtension(profileLocation, ".json") { + continue + } + + fileName := strings.Split(fi.Name(), ".")[0] + if m, _ := regexp.MatchString("^[a-zA-Z0-9-]+$", fileName); !m { + continue + } + + fileSerial := "" + if strings.Contains(fileName, "-") { + fileSerial = strings.Split(fileName, "-")[0] + } else { + fileSerial = fileName + } + + if fileSerial != d.Serial { + continue + } + + file, err := os.Open(profileLocation) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": profileLocation}).Warn("Unable to load profile") + continue + } + if err = json.NewDecoder(file).Decode(pf); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": profileLocation}).Warn("Unable to decode profile") + continue + } + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"location": profileLocation, "serial": d.Serial}).Warn("Failed to close file handle") + } + + if pf.Serial == d.Serial { + if fileName == d.Serial { + profileList["default"] = pf + } else { + name := strings.Split(fileName, "-")[1] + profileList[name] = pf + } + logger.Log(logger.Fields{"location": profileLocation, "serial": d.Serial}).Info("Loaded custom user profile") + } + } + d.UserProfiles = profileList + d.getDeviceProfile() +} + +// getDeviceProfile will load persistent device configuration +func (d *Device) getDeviceProfile() { + if len(d.UserProfiles) == 0 { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("No profile found for device. Probably initial start") + } else { + for _, pf := range d.UserProfiles { + if pf.Active { + d.DeviceProfile = pf + } + } + } +} + +// setAutoRefresh will refresh device data +func (d *Device) setAutoRefresh() { + d.timer = time.NewTicker(time.Duration(deviceRefreshInterval) * time.Millisecond) + go func() { + for { + select { + case <-d.timer.C: + if d.Exit { + return + } + d.setTemperatures() + case <-d.autoRefreshChan: + d.timer.Stop() + return + } + } + }() +} + +// setCpuTemperature will store current CPU temperature +func (d *Device) setTemperatures() { + d.CpuTemp = temperatures.GetCpuTemperature() + d.GpuTemp = temperatures.GetGpuTemperature() +} + +// UpdateDeviceLabel will set / update device label +func (d *Device) UpdateDeviceLabel(_ int, label string) uint8 { + d.mutex.Lock() + defer d.mutex.Unlock() + + d.DeviceProfile.Label = label + d.saveDeviceProfile() + return 1 +} + +// saveRgbProfile will save rgb profile data +func (d *Device) saveRgbProfile() { + rgbDirectory := pwd + "/database/rgb/" + rgbFilename := rgbDirectory + d.Serial + ".json" + if common.FileExists(rgbFilename) { + buffer, err := json.MarshalIndent(d.Rgb, "", " ") + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to encode RGB json") + return + } + + // Create profile filename + file, err := os.Create(rgbFilename) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to create RGB json file") + return + } + + // Write JSON buffer to file + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to write to RGB json file") + return + } + + // Close file + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to close RGB json file") + return + } + } +} + +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + d.dev = dev + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.getDeviceFirmware() // Firmware + d.toggleExit() // Toggle exit mode + d.setDeviceColor() // Device color + d.controlDialListener() // Control Dial +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err := d.transfer(cmdSetPollingRate, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + time.Sleep(8000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + +// UpdateRgbProfileData will update RGB profile data +func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { + if d.GetRgbProfile(profileName) == nil { + logger.Log(logger.Fields{"serial": d.Serial, "profile": profile}).Warn("Non-existing RGB profile") + return 0 + } + + pf := d.GetRgbProfile(profileName) + profile.StartColor.Brightness = pf.StartColor.Brightness + profile.EndColor.Brightness = pf.EndColor.Brightness + pf.StartColor = profile.StartColor + pf.EndColor = profile.EndColor + pf.Speed = profile.Speed + + d.Rgb.Profiles[profileName] = *pf + d.saveRgbProfile() + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 +} + +// UpdateRgbProfile will update device RGB profile +func (d *Device) UpdateRgbProfile(_ int, profile string) uint8 { + if d.GetRgbProfile(profile) == nil { + logger.Log(logger.Fields{"serial": d.Serial, "profile": profile}).Warn("Non-existing RGB profile") + return 0 + } + d.DeviceProfile.RGBProfile = profile // Set profile + d.saveDeviceProfile() // Save profile + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 + +} + +// ChangeDeviceBrightness will change device brightness +func (d *Device) ChangeDeviceBrightness(mode uint8) uint8 { + d.DeviceProfile.Brightness = mode + d.saveDeviceProfile() + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 +} + +// ChangeDeviceProfile will change device profile +func (d *Device) ChangeDeviceProfile(profileName string) uint8 { + if profile, ok := d.UserProfiles[profileName]; ok { + currentProfile := d.DeviceProfile + currentProfile.Active = false + d.DeviceProfile = currentProfile + d.saveDeviceProfile() + + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + + newProfile := profile + newProfile.Active = true + d.DeviceProfile = newProfile + d.saveDeviceProfile() + d.setDeviceColor() + return 1 + } + return 0 +} + +// ChangeKeyboardLayout will change keyboard layout +func (d *Device) ChangeKeyboardLayout(layout string) uint8 { + layouts := keyboards.GetLayouts(keyboardKey) + if len(layouts) < 1 { + return 2 + } + + if slices.Contains(layouts, layout) { + if d.DeviceProfile != nil { + if _, ok := d.DeviceProfile.Keyboards["default"]; ok { + layoutKey := fmt.Sprintf("%s-%s", keyboardKey, layout) + keyboardLayout := keyboards.GetKeyboard(layoutKey) + if keyboardLayout == nil { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("Trying to apply non-existing keyboard layout") + return 2 + } + + d.DeviceProfile.Keyboards["default"] = keyboardLayout + d.DeviceProfile.Layout = layout + d.saveDeviceProfile() + return 1 + } + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("DeviceProfile is null") + return 0 + } + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("No such layout") + return 2 + } + return 0 +} + +// getCurrentKeyboard will return current active keyboard +func (d *Device) getCurrentKeyboard() *keyboards.Keyboard { + if keyboard, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + return keyboard + } + return nil +} + +// SaveDeviceProfile will save a new keyboard profile +func (d *Device) SaveDeviceProfile(profileName string, new bool) uint8 { + if new { + if d.DeviceProfile == nil { + return 0 + } + + if slices.Contains(d.DeviceProfile.Profiles, profileName) { + return 2 + } + + if _, ok := d.DeviceProfile.Keyboards[profileName]; ok { + return 2 + } + + d.DeviceProfile.Profiles = append(d.DeviceProfile.Profiles, profileName) + d.DeviceProfile.Keyboards[profileName] = d.getCurrentKeyboard() + d.saveDeviceProfile() + return 1 + } else { + d.saveDeviceProfile() + return 1 + } +} + +// UpdateKeyboardProfile will change keyboard profile +func (d *Device) UpdateKeyboardProfile(profileName string) uint8 { + if d.DeviceProfile == nil { + return 0 + } + + if !slices.Contains(d.DeviceProfile.Profiles, profileName) { + return 2 + } + + if _, ok := d.DeviceProfile.Keyboards[profileName]; !ok { + return 2 + } + + d.DeviceProfile.Profile = profileName + d.saveDeviceProfile() + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() + return 1 +} + +// UpdateControlDial will update control dial function +func (d *Device) UpdateControlDial(value int) uint8 { + d.DeviceProfile.ControlDial = value + d.saveDeviceProfile() + return 1 +} + +// DeleteKeyboardProfile will delete keyboard profile +func (d *Device) DeleteKeyboardProfile(profileName string) uint8 { + if d.DeviceProfile == nil { + return 0 + } + + if profileName == "default" { + return 3 + } + + if !slices.Contains(d.DeviceProfile.Profiles, profileName) { + return 2 + } + + if _, ok := d.DeviceProfile.Keyboards[profileName]; !ok { + return 2 + } + + index := common.IndexOfString(d.DeviceProfile.Profiles, profileName) + if index < 0 { + return 0 + } + + d.DeviceProfile.Profile = "default" + d.DeviceProfile.Profiles = append(d.DeviceProfile.Profiles[:index], d.DeviceProfile.Profiles[index+1:]...) + delete(d.DeviceProfile.Keyboards, profileName) + + d.saveDeviceProfile() + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() + return 1 +} + +// SaveUserProfile will generate a new user profile configuration and save it to a file +func (d *Device) SaveUserProfile(profileName string) uint8 { + if d.DeviceProfile != nil { + profilePath := pwd + "/database/profiles/" + d.Serial + "-" + profileName + ".json" + + newProfile := d.DeviceProfile + newProfile.Path = profilePath + newProfile.Active = false + + buffer, err := json.Marshal(newProfile) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to convert to json format") + return 0 + } + + // Create profile filename + file, err := os.Create(profilePath) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to create new device profile") + return 0 + } + + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to write data") + return 0 + } + + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to close file handle") + return 0 + } + d.loadDeviceProfiles() + return 1 + } + return 0 +} + +// UpdateDeviceColor will update device color based on selected input +func (d *Device) UpdateDeviceColor(keyId, keyOption int, color rgb.Color) uint8 { + switch keyOption { + case 0: + { + for rowIndex, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for keyIndex, key := range row.Keys { + if keyIndex == keyId { + key.Color = rgb.Color{ + Red: color.Red, + Green: color.Green, + Blue: color.Blue, + Brightness: 0, + } + d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row[rowIndex].Keys[keyIndex] = key + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 + } + } + } + } + case 1: + { + rowId := -1 + for rowIndex, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for keyIndex := range row.Keys { + if keyIndex == keyId { + rowId = rowIndex + break + } + } + } + + if rowId < 0 { + return 0 + } + + for keyIndex, key := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row[rowId].Keys { + key.Color = rgb.Color{ + Red: color.Red, + Green: color.Green, + Blue: color.Blue, + Brightness: 0, + } + d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row[rowId].Keys[keyIndex] = key + } + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 + } + case 2: + { + for rowIndex, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for keyIndex, key := range row.Keys { + key.Color = rgb.Color{ + Red: color.Red, + Green: color.Green, + Blue: color.Blue, + Brightness: 0, + } + d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row[rowIndex].Keys[keyIndex] = key + } + } + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 + } + } + return 0 +} + +// setDeviceColor will activate and set device RGB +func (d *Device) setDeviceColor() { + if d.DeviceProfile == nil { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. DeviceProfile is null!") + return + } + + if d.DeviceProfile.BrightnessLevel == 0 { + return + } + // Reset + reset := map[int][]byte{} + var buffer []byte + + // Reset all channels + color := &rgb.Color{ + Red: 0, + Green: 0, + Blue: 0, + Brightness: 0, + } + + for i := 0; i < d.LEDChannels; i++ { + reset[i] = []byte{ + byte(color.Red), + byte(color.Green), + byte(color.Blue), + } + } + + buffer = rgb.SetColor(reset) + d.writeColor(buffer) + + if d.DeviceProfile.RGBProfile == "keyboard" { + var buf = make([]byte, colorPacketLength) + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + for _, rows := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, keys := range rows.Keys { + for _, packetIndex := range keys.PacketIndex { + buf[packetIndex] = byte(keys.Color.Red) + buf[packetIndex+1] = byte(keys.Color.Green) + buf[packetIndex+2] = byte(keys.Color.Blue) + } + } + } + d.writeColor(buf) // Write color once + return + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. Unknown keyboard") + return + } + } + + if d.DeviceProfile.RGBProfile == "static" { + profile := d.GetRgbProfile("static") + if d.DeviceProfile.Brightness != 0 { + profile.StartColor.Brightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) + } + + profileColor := rgb.ModifyBrightness(profile.StartColor) + for i := 0; i < d.LEDChannels; i++ { + reset[i] = []byte{ + byte(profileColor.Red), + byte(profileColor.Green), + byte(profileColor.Blue), + } + } + buffer = rgb.SetColor(reset) + d.writeColor(buffer) // Write color once + return + } + + go func(lightChannels int) { + startTime := time.Now() + d.activeRgb = rgb.Exit() + + // Generate random colors + d.activeRgb.RGBStartColor = rgb.GenerateRandomColor(1) + d.activeRgb.RGBEndColor = rgb.GenerateRandomColor(1) + + for { + select { + case <-d.activeRgb.Exit: + return + default: + buff := make([]byte, 0) + + rgbCustomColor := true + profile := d.GetRgbProfile(d.DeviceProfile.RGBProfile) + if profile == nil { + for i := 0; i < d.LEDChannels; i++ { + buff = append(buff, []byte{0, 0, 0}...) + } + logger.Log(logger.Fields{"profile": d.DeviceProfile.RGBProfile, "serial": d.Serial}).Warn("No such RGB profile found") + continue + } + rgbModeSpeed := common.FClamp(profile.Speed, 0.1, 10) + // Check if we have custom colors + if (rgb.Color{}) == profile.StartColor || (rgb.Color{}) == profile.EndColor { + rgbCustomColor = false + } + + r := rgb.New( + d.LEDChannels, + rgbModeSpeed, + nil, + nil, + profile.Brightness, + common.Clamp(profile.Smoothness, 1, 100), + time.Duration(rgbModeSpeed)*time.Second, + rgbCustomColor, + ) + + if rgbCustomColor { + r.RGBStartColor = &profile.StartColor + r.RGBEndColor = &profile.EndColor + } else { + r.RGBStartColor = d.activeRgb.RGBStartColor + r.RGBEndColor = d.activeRgb.RGBEndColor + } + + // Brightness + if d.DeviceProfile.Brightness > 0 { + r.RGBBrightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) + r.RGBStartColor.Brightness = r.RGBBrightness + r.RGBEndColor.Brightness = r.RGBBrightness + } + + switch d.DeviceProfile.RGBProfile { + case "off": + { + for n := 0; n < d.LEDChannels; n++ { + buff = append(buff, []byte{0, 0, 0}...) + } + } + case "rainbow": + { + r.Rainbow(startTime) + buff = append(buff, r.Output...) + } + case "watercolor": + { + r.Watercolor(startTime) + buff = append(buff, r.Output...) + } + case "cpu-temperature": + { + r.MinTemp = profile.MinTemp + r.MaxTemp = profile.MaxTemp + r.Temperature(float64(d.CpuTemp)) + buff = append(buff, r.Output...) + } + case "gpu-temperature": + { + r.MinTemp = profile.MinTemp + r.MaxTemp = profile.MaxTemp + r.Temperature(float64(d.GpuTemp)) + buff = append(buff, r.Output...) + } + case "colorpulse": + { + r.Colorpulse(&startTime) + buff = append(buff, r.Output...) + } + case "static": + { + r.Static() + buff = append(buff, r.Output...) + } + case "rotator": + { + r.Rotator(&startTime) + buff = append(buff, r.Output...) + } + case "wave": + { + r.Wave(&startTime) + buff = append(buff, r.Output...) + } + case "storm": + { + r.Storm() + buff = append(buff, r.Output...) + } + case "flickering": + { + r.Flickering(&startTime) + buff = append(buff, r.Output...) + } + case "colorshift": + { + r.Colorshift(&startTime, d.activeRgb) + buff = append(buff, r.Output...) + } + case "circleshift": + { + r.CircleShift(&startTime) + buff = append(buff, r.Output...) + } + case "circle": + { + r.Circle(&startTime) + buff = append(buff, r.Output...) + } + case "spinner": + { + r.Spinner(&startTime) + buff = append(buff, r.Output...) + } + case "colorwarp": + { + r.Colorwarp(&startTime, d.activeRgb) + buff = append(buff, r.Output...) + } + } + + var buf = make([]byte, colorPacketLength) + for _, rows := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, keys := range rows.Keys { + for _, packetIndex := range keys.PacketIndex { + buf[packetIndex] = buff[packetIndex] + buf[packetIndex+1] = buff[packetIndex+1] + buf[packetIndex+2] = buff[packetIndex+2] + } + } + } + + // Send it + d.writeColor(buf) + time.Sleep(20 * time.Millisecond) + } + } + }(d.LEDChannels) +} + +// writeColor will write data to the device with a specific endpoint. +// writeColor does not require endpoint closing and opening like normal Write requires. +// Endpoint is open only once. Once the endpoint is open, color can be sent continuously. +func (d *Device) writeColor(data []byte) { + if d.Exit { + return + } + + buffer := make([]byte, len(dataTypeSetColor)+len(data)+headerWriteSize) + binary.LittleEndian.PutUint16(buffer[0:2], uint16(len(data))) + copy(buffer[headerWriteSize:headerWriteSize+len(dataTypeSetColor)], dataTypeSetColor) + copy(buffer[headerWriteSize+len(dataTypeSetColor):], data) + + // Split packet into chunks + chunks := common.ProcessMultiChunkPacket(buffer, maxBufferSizePerRequest) + for i, chunk := range chunks { + if i == 0 { + // Initial packet is using cmdWriteColor + _, err := d.transfer(cmdWriteColor, chunk) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to color endpoint") + } + } else { + // Chunks don't use cmdWriteColor, they use static dataTypeSubColor + _, err := d.transfer(dataTypeSubColor, chunk) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to endpoint") + } + } + } +} + +func (d *Device) resetDeviceColor() { + if d.DeviceProfile == nil { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. DeviceProfile is null!") + return + } + + if d.DeviceProfile.RGBProfile == "keyboard" { + var buf = make([]byte, colorPacketLength) + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + for _, rows := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, keys := range rows.Keys { + for _, packetIndex := range keys.PacketIndex { + buf[packetIndex] = byte(keys.Color.Red) + buf[packetIndex+1] = byte(keys.Color.Green) + buf[packetIndex+2] = byte(keys.Color.Blue) + } + } + } + d.writeColor(buf) // Write color once + return + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. Unknown keyboard") + return + } + } else if d.DeviceProfile.RGBProfile == "static" { + // Reset + reset := map[int][]byte{} + var buffer []byte + + profile := d.GetRgbProfile("static") + if d.DeviceProfile.Brightness != 0 { + profile.StartColor.Brightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) + } + + profileColor := rgb.ModifyBrightness(profile.StartColor) + for i := 0; i < d.LEDChannels; i++ { + reset[i] = []byte{ + byte(profileColor.Red), + byte(profileColor.Green), + byte(profileColor.Blue), + } + } + buffer = rgb.SetColor(reset) + d.writeColor(buffer) // Write color once + return + } +} + +// setBrightnessLevel will set global brightness level +func (d *Device) setBrightnessLevel() { + if d.Exit { + return + } + + if d.DeviceProfile != nil { + if d.DeviceProfile.BrightnessLevel == 0 { + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf[0:2], d.DeviceProfile.BrightnessLevel) + _, err := d.transfer(cmdBrightness, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change brightness") + } + } else { + if d.DeviceProfile.RGBProfile == "keyboard" || d.DeviceProfile.RGBProfile == "static" { + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf[0:2], d.DeviceProfile.BrightnessLevel) + _, err := d.transfer(cmdBrightness, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change brightness") + } + d.resetDeviceColor() + } else { + if d.activeRgb == nil { + d.setDeviceColor() + } + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf[0:2], d.DeviceProfile.BrightnessLevel) + _, err := d.transfer(cmdBrightness, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change brightness") + } + } + } + } +} + +// getListenerData will listen for keyboard events and return data on success or nil on failure. +// ReadWithTimeout is mandatory due to the nature of listening for events +func (d *Device) getListenerData() []byte { + data := make([]byte, bufferSize) + n, err := d.listener.ReadWithTimeout(data, 100*time.Millisecond) + if err != nil || n == 0 { + return nil + } + return data +} + +// controlDialListener will listen for events from the control dial +func (d *Device) controlDialListener() { + var brightness uint16 = 0 + brightness = d.DeviceProfile.BrightnessLevel + + go func() { + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == 2 { + listener, err := hid.OpenPath(info.Path) + if err != nil { + return err + } + d.listener = listener + } + return nil + }) + + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to enumerate devices") + } + + for { + select { + default: + if d.Exit { + err = d.listener.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Failed to close listener") + return + } + return + } + + data := d.getListenerData() + if len(data) == 0 || data == nil { + continue + } + + switch data[1] { + case 0x05: // // Right horizontal spinning wheel + { + switch data[4] { + case 0x01: // Up direction + { + switch d.DeviceProfile.ControlDial { + case 1: + inputmanager.InputControl(inputmanager.VolumeUp, d.Serial) + break + case 2: + if brightness >= 1000 { + brightness = 1000 + break + } + + brightness += 200 + + if d.DeviceProfile != nil { + d.DeviceProfile.BrightnessLevel = brightness + d.saveDeviceProfile() + d.setBrightnessLevel() + } + break + } + } + break + case 0xff: // Down direction + { + switch d.DeviceProfile.ControlDial { + case 1: + inputmanager.InputControl(inputmanager.VolumeDown, d.Serial) + break + case 2: + if d.DeviceProfile.BrightnessLevel != 0 { + if brightness <= 0 { + brightness = 0 + } else { + brightness -= 200 + } + + if d.DeviceProfile != nil { + d.DeviceProfile.BrightnessLevel = brightness + d.saveDeviceProfile() + d.setBrightnessLevel() + } + } + break + } + } + } + } + case 0x02: + { + if data[18] == 0x02 { + switch d.DeviceProfile.ControlDial { + case 1: + inputmanager.InputControl(inputmanager.VolumeMute, d.Serial) + break + case 2: + if brightness > 0 { + brightness = 0 + } else { + brightness = 1000 + } + + if d.DeviceProfile != nil { + d.DeviceProfile.BrightnessLevel = brightness + d.saveDeviceProfile() + d.setBrightnessLevel() + } + break + } + } + } + } + } + } + }() +} + +// transfer will send data to a device and retrieve device output +func (d *Device) transfer(endpoint, buffer []byte) ([]byte, error) { + // Packet control, mandatory for this device + d.mutex.Lock() + defer d.mutex.Unlock() + + // Create write buffer + bufferW := make([]byte, bufferSizeWrite) + bufferW[1] = 0x08 + endpointHeaderPosition := bufferW[headerSize : headerSize+len(endpoint)] + copy(endpointHeaderPosition, endpoint) + if len(buffer) > 0 { + copy(bufferW[headerSize+len(endpoint):headerSize+len(endpoint)+len(buffer)], buffer) + } + + // Create read buffer + bufferR := make([]byte, bufferSize) + + // Send command to a device + if _, err := d.dev.Write(bufferW); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device") + return nil, err + } + + // Get data from a device + if _, err := d.dev.Read(bufferR); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to read data from device") + return nil, err + } + + return bufferR, nil +} diff --git a/src/devices/k70coretklW/k70coretklW.go b/src/devices/k70coretklW/k70coretklW.go new file mode 100644 index 0000000..c099f46 --- /dev/null +++ b/src/devices/k70coretklW/k70coretklW.go @@ -0,0 +1,1388 @@ +package k70coretklW + +// Package: K70 CORE TKL WIRELESS +// This is the primary package for K70 CORE TKL WIRELESS. +// All device actions are controlled from this package. +// Author: Nikola Jurkovic +// License: GPL-3.0 or later + +import ( + "OpenLinkHub/src/common" + "OpenLinkHub/src/config" + "OpenLinkHub/src/inputmanager" + "OpenLinkHub/src/keyboards" + "OpenLinkHub/src/logger" + "OpenLinkHub/src/rgb" + "encoding/binary" + "encoding/json" + "fmt" + "github.com/sstallion/go-hid" + "os" + "regexp" + "slices" + "strings" + "sync" + "time" +) + +// DeviceProfile struct contains all device profile +type DeviceProfile struct { + Active bool + Path string + Product string + Serial string + LCDMode uint8 + LCDRotation uint8 + Brightness uint8 + RGBProfile string + Label string + Layout string + Keyboards map[string]*keyboards.Keyboard + Profile string + Profiles []string + ControlDial int + BrightnessLevel uint16 + SleepMode int +} + +type Device struct { + Debug bool + dev *hid.Device + listener *hid.Device + Manufacturer string `json:"manufacturer"` + Product string `json:"product"` + Serial string `json:"serial"` + SlipstreamSerial string `json:"slipstreamSerial"` + Firmware string `json:"firmware"` + DongleFirmware string `json:"dongleFirmware"` + activeRgb *rgb.ActiveRGB + UserProfiles map[string]*DeviceProfile `json:"userProfiles"` + Devices map[int]string `json:"devices"` + DeviceProfile *DeviceProfile + OriginalProfile *DeviceProfile + Template string + VendorId uint16 + Brightness map[int]string + CpuTemp float32 + GpuTemp float32 + Layouts []string + ProductId uint16 + SlipstreamId uint16 + ControlDialOptions map[int]string + RGBModes map[string]string + SleepModes map[int]string + KeyAmount int + Connected bool + Rgb *rgb.RGB + Endpoint byte + mutex sync.Mutex + Exit bool + UIKeyboard string + UIKeyboardRow string +} + +var ( + pwd = "" + cmdCloseEndpoint = []byte{0x05, 0x01, 0x01} + cmdSoftwareMode = []byte{0x01, 0x03, 0x00, 0x06} + cmdHardwareMode = []byte{0x01, 0x03, 0x00, 0x01} + cmdSleepMode = []byte{0x01, 0x03, 0x00, 0x04} + cmdOpenColorEndpoint = []byte{0x0d, 0x01} + cmdSetLeds = []byte{0x13} + cmdInitProtocol = []byte{0x0b, 0x65, 0x6d} + cmdOpenWriteEndpoint = []byte{0x01, 0x0d, 0x00, 0x01} + cmdFlush = []byte{0x15, 0x01} + cmdRead = []byte{0x09, 0x01} + cmdActivateLed = []byte{0x65, 0x6d} + cmdBrightness = []byte{0x01, 0x02, 0x00} + cmdGetFirmware = []byte{0x02, 0x13} + dataTypeSetColor = []byte{0x7e, 0x20, 0x01} + dataTypeSubColor = []byte{0x07, 0x01} + cmdWriteColor = []byte{0x06, 0x01} + cmdSleep = []byte{0x01, 0x0e, 0x00} + transferTimeout = 500 + bufferSize = 64 + bufferSizeWrite = bufferSize + 1 + headerSize = 2 + headerWriteSize = 4 + maxBufferSizePerRequest = 61 + keyboardKey = "k70coretklW-default" + defaultLayout = "k70coretklW-default-US" +) + +func Init(vendorId, slipstreamId, productId uint16, dev *hid.Device, endpoint byte, serial, serialSlipstream string) *Device { + // Set global working directory + pwd = config.GetConfig().ConfigPath + + // Init new struct with HID device + d := &Device{ + dev: dev, + Template: "k70coretklW.html", + VendorId: vendorId, + ProductId: productId, + SlipstreamId: slipstreamId, + Serial: serial + "W", + SlipstreamSerial: serialSlipstream, + Endpoint: endpoint, + Firmware: "n/a", + Brightness: map[int]string{ + 0: "RGB Profile", + 1: "33 %", + 2: "66 %", + 3: "100 %", + }, + ControlDialOptions: map[int]string{ + 1: "Volume Control", + 2: "Brightness", + }, + Product: "K70 CORE TKL WIRELESS", + Layouts: keyboards.GetLayouts(keyboardKey), + RGBModes: map[string]string{ + "watercolor": "Watercolor", + "colorpulse": "Color Pulse", + "colorshift": "Color Shift", + "colorwave": "Color Wave", + "rain": "Rain", + "rainbowwave": "Rainbow Wave", + "spiralrainbow": "Spiral Rainbow", + "tlr": "Type Lighting - Ripple", + "keyboard": "Keyboard", + "off": "Off", + "visor": "Visor", + }, + SleepModes: map[int]string{ + 1: "1 minute", + 5: "5 minutes", + 10: "10 minutes", + 15: "15 minutes", + 30: "30 minutes", + 60: "1 hour", + }, + UIKeyboard: "keyboard-6", + UIKeyboardRow: "keyboard-row-20", + } + + d.getDebugMode() // Debug mode + d.loadRgb() // Load RGB + d.loadDeviceProfiles() // Load all device profiles + d.saveDeviceProfile() // Save profile + return d +} + +// GetRgbProfiles will return RGB profiles for a target device +func (d *Device) GetRgbProfiles() interface{} { + return d.Rgb +} + +// Stop will stop all device operations and switch a device back to hardware mode +func (d *Device) Stop() { + // Placeholder +} + +// StopInternal will stop all device operations and switch a device back to hardware mode +func (d *Device) StopInternal() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Stop() + } + d.setHardwareMode() +} + +// SetConnected will change connected status +func (d *Device) SetConnected(value bool) { + d.Connected = value +} + +// Connect will connect to a device +func (d *Device) Connect() { + if !d.Connected { + d.Connected = true + d.setSoftwareMode() // Activate software mode + d.getDeviceFirmware() // Firmware + d.setKeyAmount() // Set number of keys + d.initLeds() // Init LED ports + d.setDeviceColor() // Device color + d.setBrightnessLevel() // Brightness + d.setSleepTimer() // Sleep + } +} + +// loadRgb will load RGB file if found, or create the default. +func (d *Device) loadRgb() { + rgbDirectory := pwd + "/database/rgb/" + rgbFilename := rgbDirectory + d.Serial + ".json" + + // Check if filename has .json extension + if !common.IsValidExtension(rgbFilename, ".json") { + return + } + + if !common.FileExists(rgbFilename) { + profile := rgb.GetRGB() + profile.Device = d.Product + + // Convert to JSON + buffer, err := json.MarshalIndent(profile, "", " ") + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to encode RGB json") + return + } + + // Create profile filename + file, err := os.Create(rgbFilename) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to create RGB json file") + return + } + + // Write JSON buffer to file + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to write to RGB json file") + return + } + + // Close file + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to close RGB json file") + return + } + } + + file, err := os.Open(rgbFilename) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to load RGB") + return + } + if err = json.NewDecoder(file).Decode(&d.Rgb); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to decode profile") + return + } + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"location": rgbFilename, "serial": d.Serial}).Warn("Failed to close file handle") + } +} + +// GetRgbProfile will return rgb.Profile struct +func (d *Device) GetRgbProfile(profile string) *rgb.Profile { + if d.Rgb == nil { + return nil + } + + if val, ok := d.Rgb.Profiles[profile]; ok { + return &val + } + return nil +} + +// GetDeviceTemplate will return device template name +func (d *Device) GetDeviceTemplate() string { + return d.Template +} + +// setKeyAmount will set global key amount +func (d *Device) setKeyAmount() { + index := 0 + for _, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, key := range row.Keys { + for range key.PacketIndex { + index++ + } + } + } + d.KeyAmount = index +} + +// getManufacturer will return device manufacturer +func (d *Device) getDebugMode() { + d.Debug = config.GetConfig().Debug +} + +// getManufacturer will return device manufacturer +func (d *Device) getManufacturer() { + manufacturer, err := d.dev.GetMfrStr() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to get manufacturer") + } + d.Manufacturer = manufacturer +} + +// getProduct will return device name +func (d *Device) getProduct() { + product, err := d.dev.GetProductStr() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to get product") + } + d.Product = product +} + +// setHardwareMode will switch a device to hardware mode +func (d *Device) setHardwareMode() { + if d.Connected { + _, err := d.transfer(cmdHardwareMode, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode") + } + } +} + +// setSoftwareMode will switch a device to software mode +func (d *Device) setSoftwareMode() { + _, err := d.transfer(cmdSoftwareMode, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode") + } + d.Connected = true +} + +// SetSleepMode will switch a device to sleep mode +func (d *Device) SetSleepMode() { + _, err := d.transfer(cmdSleepMode, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode") + } + //d.Connected = false +} + +// GetSleepMode will return current sleep mode +func (d *Device) GetSleepMode() int { + if d.DeviceProfile != nil { + return d.DeviceProfile.SleepMode + } + return 0 +} + +// getDeviceFirmware will return a device firmware version out as string +func (d *Device) getDeviceFirmware() { + fw, err := d.transfer(cmdGetFirmware, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to write to a device") + } + + v1, v2, v3 := int(fw[3]), int(fw[4]), int(binary.LittleEndian.Uint16(fw[5:7])) + d.Firmware = fmt.Sprintf("%d.%d.%d", v1, v2, v3) +} + +// initLeds will initialize LED ports +func (d *Device) initLeds() { + _, err := d.transfer(cmdOpenColorEndpoint, cmdSetLeds) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode") + } + + buf := make([]byte, 12) + buf[0] = 0x08 + buf[4] = 0x69 + buf[5] = 0x6c + buf[6] = 0x01 + buf[8] = 0x08 + buf[10] = 0x65 + buf[11] = 0x6d + _, err = d.transfer(cmdWriteColor, buf) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode") + } + + _, err = d.transfer(cmdCloseEndpoint, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode") + } + + _, err = d.transfer(cmdInitProtocol, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode") + } + + // We need to wait around 500 ms for physical ports to re-initialize + // After that we can grab any new connected / disconnected device values + time.Sleep(time.Duration(transferTimeout) * time.Millisecond) +} + +// saveDeviceProfile will save device profile for persistent configuration +func (d *Device) saveDeviceProfile() { + profilePath := pwd + "/database/profiles/" + d.Serial + ".json" + keyboardMap := make(map[string]*keyboards.Keyboard, 0) + + deviceProfile := &DeviceProfile{ + Product: d.Product, + Serial: d.Serial, + Path: profilePath, + } + + // First save, assign saved profile to a device + if d.DeviceProfile == nil { + // RGB, Label + deviceProfile.RGBProfile = "keyboard" + deviceProfile.Label = "Keyboard" + deviceProfile.Active = true + keyboardMap["default"] = keyboards.GetKeyboard(defaultLayout) + deviceProfile.Keyboards = keyboardMap + deviceProfile.Profile = "default" + deviceProfile.Profiles = []string{"default"} + deviceProfile.Layout = "US" + deviceProfile.ControlDial = 1 + deviceProfile.BrightnessLevel = 1000 + deviceProfile.SleepMode = 15 + } else { + if len(d.DeviceProfile.Layout) == 0 { + deviceProfile.Layout = "US" + } else { + deviceProfile.Layout = d.DeviceProfile.Layout + } + + deviceProfile.Active = d.DeviceProfile.Active + deviceProfile.Brightness = d.DeviceProfile.Brightness + deviceProfile.RGBProfile = d.DeviceProfile.RGBProfile + deviceProfile.Label = d.DeviceProfile.Label + deviceProfile.Profile = d.DeviceProfile.Profile + deviceProfile.Profiles = d.DeviceProfile.Profiles + deviceProfile.Keyboards = d.DeviceProfile.Keyboards + deviceProfile.ControlDial = d.DeviceProfile.ControlDial + deviceProfile.SleepMode = d.DeviceProfile.SleepMode + deviceProfile.BrightnessLevel = d.DeviceProfile.BrightnessLevel + + if len(d.DeviceProfile.Path) < 1 { + deviceProfile.Path = profilePath + d.DeviceProfile.Path = profilePath + } else { + deviceProfile.Path = d.DeviceProfile.Path + } + deviceProfile.LCDMode = d.DeviceProfile.LCDMode + deviceProfile.LCDRotation = d.DeviceProfile.LCDRotation + } + + // Convert to JSON + buffer, err := json.MarshalIndent(deviceProfile, "", " ") + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to convert to json format") + return + } + + // Create profile filename + file, fileErr := os.Create(deviceProfile.Path) + if fileErr != nil { + logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Error("Unable to create new device profile") + return + } + + // Write JSON buffer to file + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Error("Unable to write data") + return + } + + // Close file + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Error("Unable to close file handle") + } + + d.loadDeviceProfiles() // Reload +} + +// loadDeviceProfiles will load custom user profiles +func (d *Device) loadDeviceProfiles() { + profileList := make(map[string]*DeviceProfile, 0) + userProfileDirectory := pwd + "/database/profiles/" + + files, err := os.ReadDir(userProfileDirectory) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": userProfileDirectory, "serial": d.Serial}).Error("Unable to read content of a folder") + } + + for _, fi := range files { + pf := &DeviceProfile{} + if fi.IsDir() { + continue // Exclude folders if any + } + + // Define a full path of filename + profileLocation := userProfileDirectory + fi.Name() + + // Check if filename has .json extension + if !common.IsValidExtension(profileLocation, ".json") { + continue + } + + fileName := strings.Split(fi.Name(), ".")[0] + if m, _ := regexp.MatchString("^[a-zA-Z0-9-]+$", fileName); !m { + continue + } + + fileSerial := "" + if strings.Contains(fileName, "-") { + fileSerial = strings.Split(fileName, "-")[0] + } else { + fileSerial = fileName + } + + if fileSerial != d.Serial { + continue + } + + file, err := os.Open(profileLocation) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": profileLocation}).Warn("Unable to load profile") + continue + } + if err = json.NewDecoder(file).Decode(pf); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": profileLocation}).Warn("Unable to decode profile") + continue + } + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"location": profileLocation, "serial": d.Serial}).Warn("Failed to close file handle") + } + + if pf.Serial == d.Serial { + if fileName == d.Serial { + profileList["default"] = pf + } else { + name := strings.Split(fileName, "-")[1] + profileList[name] = pf + } + logger.Log(logger.Fields{"location": profileLocation, "serial": d.Serial}).Info("Loaded custom user profile") + } + } + d.UserProfiles = profileList + d.getDeviceProfile() +} + +// getDeviceProfile will load persistent device configuration +func (d *Device) getDeviceProfile() { + if len(d.UserProfiles) == 0 { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("No profile found for device. Probably initial start") + } else { + for _, pf := range d.UserProfiles { + if pf.Active { + d.DeviceProfile = pf + } + } + } +} + +// setSleepTimer will set device sleep timer +func (d *Device) setSleepTimer() uint8 { + if d.Exit { + return 0 + } + + if d.DeviceProfile != nil { + _, err := d.transfer(cmdOpenWriteEndpoint, nil) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change device sleep timer") + return 0 + } + + buf := make([]byte, 4) + sleep := d.DeviceProfile.SleepMode * (60 * 1000) + binary.LittleEndian.PutUint32(buf, uint32(sleep)) + _, err = d.transfer(cmdSleep, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change device sleep timer") + return 0 + } + return 1 + } + return 0 +} + +// UpdateSleepTimer will update device sleep timer +func (d *Device) UpdateSleepTimer(minutes int) uint8 { + if d.DeviceProfile != nil { + d.DeviceProfile.SleepMode = minutes + d.saveDeviceProfile() + d.setSleepTimer() + return 1 + } + return 0 +} + +// UpdateDeviceLabel will set / update device label +func (d *Device) UpdateDeviceLabel(_ int, label string) uint8 { + d.mutex.Lock() + defer d.mutex.Unlock() + + d.DeviceProfile.Label = label + d.saveDeviceProfile() + return 1 +} + +// saveRgbProfile will save rgb profile data +func (d *Device) saveRgbProfile() { + rgbDirectory := pwd + "/database/rgb/" + rgbFilename := rgbDirectory + d.Serial + ".json" + if common.FileExists(rgbFilename) { + buffer, err := json.MarshalIndent(d.Rgb, "", " ") + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to encode RGB json") + return + } + + // Create profile filename + file, err := os.Create(rgbFilename) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to create RGB json file") + return + } + + // Write JSON buffer to file + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to write to RGB json file") + return + } + + // Close file + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to close RGB json file") + return + } + } +} + +// UpdateRgbProfileData will update RGB profile data +func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { + if d.GetRgbProfile(profileName) == nil { + logger.Log(logger.Fields{"serial": d.Serial, "profile": profile}).Warn("Non-existing RGB profile") + return 0 + } + + pf := d.GetRgbProfile(profileName) + profile.StartColor.Brightness = pf.StartColor.Brightness + profile.EndColor.Brightness = pf.EndColor.Brightness + pf.StartColor = profile.StartColor + pf.EndColor = profile.EndColor + pf.Speed = profile.Speed + + d.Rgb.Profiles[profileName] = *pf + d.saveRgbProfile() + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 +} + +// UpdateRgbProfile will update device RGB profile +func (d *Device) UpdateRgbProfile(_ int, profile string) uint8 { + if _, ok := d.RGBModes[profile]; !ok { + logger.Log(logger.Fields{"serial": d.Serial, "profile": profile}).Warn("Non-existing RGB profile") + return 0 + } + + d.DeviceProfile.RGBProfile = profile // Set profile + d.saveDeviceProfile() // Save profile + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 +} + +// ChangeDeviceBrightness will change device brightness +func (d *Device) ChangeDeviceBrightness(mode uint8) uint8 { + d.DeviceProfile.Brightness = mode + d.DeviceProfile.BrightnessLevel = 1000 + + switch mode { + case 1: + d.DeviceProfile.BrightnessLevel = 300 + case 2: + d.DeviceProfile.BrightnessLevel = 600 + case 3: + d.DeviceProfile.BrightnessLevel = 1000 + case 4: + d.DeviceProfile.BrightnessLevel = 0 + } + + d.saveDeviceProfile() + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf[0:2], d.DeviceProfile.BrightnessLevel) + _, err := d.transfer(cmdBrightness, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change brightness") + } + + return 1 +} + +// ChangeDeviceProfile will change device profile +func (d *Device) ChangeDeviceProfile(profileName string) uint8 { + if profile, ok := d.UserProfiles[profileName]; ok { + currentProfile := d.DeviceProfile + currentProfile.Active = false + d.DeviceProfile = currentProfile + d.saveDeviceProfile() + + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + + newProfile := profile + newProfile.Active = true + d.DeviceProfile = newProfile + d.saveDeviceProfile() + d.setDeviceColor() + return 1 + } + return 0 +} + +// ChangeKeyboardLayout will change keyboard layout +func (d *Device) ChangeKeyboardLayout(layout string) uint8 { + layouts := keyboards.GetLayouts(keyboardKey) + if len(layouts) < 1 { + return 2 + } + + if slices.Contains(layouts, layout) { + if d.DeviceProfile != nil { + if _, ok := d.DeviceProfile.Keyboards["default"]; ok { + layoutKey := fmt.Sprintf("%s-%s", keyboardKey, layout) + keyboardLayout := keyboards.GetKeyboard(layoutKey) + if keyboardLayout == nil { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Trying to apply non-existing keyboard layout") + return 2 + } + + d.DeviceProfile.Keyboards["default"] = keyboardLayout + d.DeviceProfile.Layout = layout + d.saveDeviceProfile() + return 1 + } + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("DeviceProfile is null") + return 0 + } + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("No such layout") + return 2 + } + return 0 +} + +// getCurrentKeyboard will return current active keyboard +func (d *Device) getCurrentKeyboard() *keyboards.Keyboard { + if keyboard, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + return keyboard + } + return nil +} + +// SaveDeviceProfile will save a new keyboard profile +func (d *Device) SaveDeviceProfile(profileName string, new bool) uint8 { + if new { + if d.DeviceProfile == nil { + return 0 + } + + if slices.Contains(d.DeviceProfile.Profiles, profileName) { + return 2 + } + + if _, ok := d.DeviceProfile.Keyboards[profileName]; ok { + return 2 + } + + d.DeviceProfile.Profiles = append(d.DeviceProfile.Profiles, profileName) + d.DeviceProfile.Keyboards[profileName] = d.getCurrentKeyboard() + d.saveDeviceProfile() + return 1 + } else { + d.saveDeviceProfile() + return 1 + } +} + +// UpdateKeyboardProfile will change keyboard profile +func (d *Device) UpdateKeyboardProfile(profileName string) uint8 { + if d.DeviceProfile == nil { + return 0 + } + + if !slices.Contains(d.DeviceProfile.Profiles, profileName) { + return 2 + } + + if _, ok := d.DeviceProfile.Keyboards[profileName]; !ok { + return 2 + } + + d.DeviceProfile.Profile = profileName + d.saveDeviceProfile() + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() + return 1 +} + +// UpdateControlDial will update control dial function +func (d *Device) UpdateControlDial(value int) uint8 { + d.DeviceProfile.ControlDial = value + d.saveDeviceProfile() + return 1 +} + +// DeleteKeyboardProfile will delete keyboard profile +func (d *Device) DeleteKeyboardProfile(profileName string) uint8 { + if d.DeviceProfile == nil { + return 0 + } + + if profileName == "default" { + return 3 + } + + if !slices.Contains(d.DeviceProfile.Profiles, profileName) { + return 2 + } + + if _, ok := d.DeviceProfile.Keyboards[profileName]; !ok { + return 2 + } + + index := common.IndexOfString(d.DeviceProfile.Profiles, profileName) + if index < 0 { + return 0 + } + + d.DeviceProfile.Profile = "default" + d.DeviceProfile.Profiles = append(d.DeviceProfile.Profiles[:index], d.DeviceProfile.Profiles[index+1:]...) + delete(d.DeviceProfile.Keyboards, profileName) + + d.saveDeviceProfile() + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() + return 1 +} + +// SaveUserProfile will generate a new user profile configuration and save it to a file +func (d *Device) SaveUserProfile(profileName string) uint8 { + if d.DeviceProfile != nil { + profilePath := pwd + "/database/profiles/" + d.Serial + "-" + profileName + ".json" + + newProfile := d.DeviceProfile + newProfile.Path = profilePath + newProfile.Active = false + + buffer, err := json.Marshal(newProfile) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to convert to json format") + return 0 + } + + // Create profile filename + file, err := os.Create(profilePath) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to create new device profile") + return 0 + } + + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to write data") + return 0 + } + + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to close file handle") + return 0 + } + d.loadDeviceProfiles() + return 1 + } + return 0 +} + +// UpdateDeviceColor will update device color based on selected input +func (d *Device) UpdateDeviceColor(_, keyOption int, color rgb.Color) uint8 { + if d.DeviceProfile == nil { + return 0 + } + switch keyOption { + case 2: + { + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Color = color + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 + } + } + } + return 0 +} + +// setBrightnessLevel will set global brightness level +func (d *Device) setBrightnessLevel() { + if d.DeviceProfile != nil { + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf[0:2], d.DeviceProfile.BrightnessLevel) + _, err := d.transfer(cmdBrightness, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change brightness") + } + } +} + +// setDeviceColor will activate and set device RGB +func (d *Device) setDeviceColor() { + if d.DeviceProfile == nil { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. DeviceProfile is null!") + return + } + + switch d.DeviceProfile.RGBProfile { + case "off": + { + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + var buf = make([]byte, 98) + buf[3] = 0x01 + buf[4] = 0xff + buf[5] = 0 + buf[6] = 0 + buf[7] = 0 + buf[8] = byte(d.KeyAmount) + start := 9 + for _, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, key := range row.Keys { + for packet := range key.PacketIndex { + value := key.PacketIndex[packet] / 3 + buf[start] = byte(value) + start++ + } + } + } + dataTypeSetColor = []byte{0x7e, 0x20, 0x01} + d.writeColor(buf) + return + } + } + case "keyboard": + { + if keyboard, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + var buf = make([]byte, 98) + buf[3] = 0x01 + buf[4] = 0xff + buf[5] = byte(keyboard.Color.Blue) + buf[6] = byte(keyboard.Color.Green) + buf[7] = byte(keyboard.Color.Red) + buf[8] = byte(d.KeyAmount) + start := 9 + for _, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, key := range row.Keys { + for packet := range key.PacketIndex { + value := key.PacketIndex[packet] / 3 + buf[start] = byte(value) + start++ + } + } + } + dataTypeSetColor = []byte{0x7e, 0x20, 0x01} + d.writeColor(buf) + return + } + } + case "rain": + { + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + var buf = make([]byte, 94) + buf[2] = byte(d.KeyAmount) + start := 3 + for _, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, key := range row.Keys { + for packet := range key.PacketIndex { + value := key.PacketIndex[packet] / 3 + buf[start] = byte(value) + start++ + } + } + } + dataTypeSetColor = []byte{0x7e, 0xa0, 0x02, 0x04, 0x01} + d.writeColor(buf) + return + } + } + case "tlk": + { + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + var buf = make([]byte, 94) + buf[3] = byte(d.KeyAmount) + start := 4 + for _, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, key := range row.Keys { + for packet := range key.PacketIndex { + value := key.PacketIndex[packet] / 3 + buf[start] = byte(value) + start++ + } + } + } + dataTypeSetColor = []byte{0xf9, 0xb1, 0x02, 0x04} + d.writeColor(buf) + return + } + } + case "tlr": + { + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + var buf = make([]byte, 94) + buf[3] = byte(d.KeyAmount) + start := 4 + for _, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, key := range row.Keys { + for packet := range key.PacketIndex { + value := key.PacketIndex[packet] / 3 + buf[start] = byte(value) + start++ + } + } + } + dataTypeSetColor = []byte{0xa2, 0x09, 0x02, 0x04} + d.writeColor(buf) + return + } + } + case "spiralrainbow": + { + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + var buf = make([]byte, 94) + buf[2] = byte(d.KeyAmount) + start := 3 + for _, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, key := range row.Keys { + for packet := range key.PacketIndex { + value := key.PacketIndex[packet] / 3 + buf[start] = byte(value) + start++ + } + } + } + dataTypeSetColor = []byte{0x87, 0xab, 0x00, 0x04, 0x06} + d.writeColor(buf) + return + } + } + case "colorpulse": + { + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + var buf = make([]byte, 94) + buf[3] = byte(d.KeyAmount) + start := 4 + for _, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, key := range row.Keys { + for packet := range key.PacketIndex { + value := key.PacketIndex[packet] / 3 + buf[start] = byte(value) + start++ + } + } + } + dataTypeSetColor = []byte{0x4f, 0xad, 0x02, 0x04} + d.writeColor(buf) + return + } + } + case "colorshift": + { + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + var buf = make([]byte, 94) + buf[3] = byte(d.KeyAmount) + start := 4 + for _, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, key := range row.Keys { + for packet := range key.PacketIndex { + value := key.PacketIndex[packet] / 3 + buf[start] = byte(value) + start++ + } + } + } + dataTypeSetColor = []byte{0xfa, 0xa5, 0x02, 0x04} + d.writeColor(buf) + return + } + } + case "colorwave": + { + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + var buf = make([]byte, 94) + buf[2] = byte(d.KeyAmount) + start := 3 + for _, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, key := range row.Keys { + for packet := range key.PacketIndex { + value := key.PacketIndex[packet] / 3 + buf[start] = byte(value) + start++ + } + } + } + dataTypeSetColor = []byte{0xff, 0x7b, 0x02, 0x04, 0x04} + d.writeColor(buf) + return + } + } + case "rainbowwave": + { + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + var buf = make([]byte, 94) + buf[2] = byte(d.KeyAmount) + start := 3 + for _, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, key := range row.Keys { + for packet := range key.PacketIndex { + value := key.PacketIndex[packet] / 3 + buf[start] = byte(value) + start++ + } + } + } + dataTypeSetColor = []byte{0x4c, 0xb9, 0x00, 0x04, 0x04} + d.writeColor(buf) + return + } + } + case "visor": + { + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + var buf = make([]byte, 94) + buf[2] = byte(d.KeyAmount) + start := 3 + for _, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, key := range row.Keys { + for packet := range key.PacketIndex { + value := key.PacketIndex[packet] / 3 + buf[start] = byte(value) + start++ + } + } + } + dataTypeSetColor = []byte{0xc0, 0x90, 0x02, 0x04, 0x04} + d.writeColor(buf) + return + } + } + case "watercolor": + { + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + var buf = make([]byte, 98) + buf[2] = 0x01 + buf[3] = 0xff + buf[4] = 0xff + buf[5] = 0xff + buf[6] = 0xff + buf[7] = byte(d.KeyAmount) + start := 8 + for _, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, key := range row.Keys { + for packet := range key.PacketIndex { + value := key.PacketIndex[packet] / 3 + buf[start] = byte(value) + start++ + } + } + } + dataTypeSetColor = []byte{0x22, 0x00, 0x03, 0x04} + d.writeColor(buf) + return + } + } + } +} + +// writeColor will write data to the device with a specific endpoint. +// writeColor does not require endpoint closing and opening like normal Write requires. +// Endpoint is open only once. Once the endpoint is open, color can be sent continuously. +func (d *Device) writeColor(data []byte) { + if d.Exit { + return + } + buffer := make([]byte, len(dataTypeSetColor)+len(data)+headerWriteSize) + binary.LittleEndian.PutUint16(buffer[0:2], uint16(len(data))) + copy(buffer[headerWriteSize:headerWriteSize+len(dataTypeSetColor)], dataTypeSetColor) + copy(buffer[headerWriteSize+len(dataTypeSetColor):], data) + + _, err := d.transfer(cmdOpenColorEndpoint, cmdActivateLed) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode") + return + } + + _, err = d.transfer(cmdRead, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode") + return + } + + // Split packet into chunks + chunks := common.ProcessMultiChunkPacket(buffer, maxBufferSizePerRequest) + for i, chunk := range chunks { + if i == 0 { + // Initial packet is using cmdWriteColor + _, err := d.transfer(cmdWriteColor, chunk) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to color endpoint") + } + } else { + // Chunks don't use cmdWriteColor, they use static dataTypeSubColor + _, err := d.transfer(dataTypeSubColor, chunk) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to endpoint") + } + } + } + + _, err = d.transfer(cmdCloseEndpoint, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode") + return + } + + // Close endpoint + _, err = d.transfer(cmdFlush, nil) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to close endpoint") + } +} + +// transfer will send data to a device and retrieve device output +func (d *Device) transfer(endpoint, buffer []byte) ([]byte, error) { + // Packet control, mandatory for this device + d.mutex.Lock() + defer d.mutex.Unlock() + + // Create write buffer + bufferW := make([]byte, bufferSizeWrite) + bufferW[1] = d.Endpoint + endpointHeaderPosition := bufferW[headerSize : headerSize+len(endpoint)] + copy(endpointHeaderPosition, endpoint) + if len(buffer) > 0 { + copy(bufferW[headerSize+len(endpoint):headerSize+len(endpoint)+len(buffer)], buffer) + } + + // Create read buffer + bufferR := make([]byte, bufferSize) + + // Send command to a device + if _, err := d.dev.Write(bufferW); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device") + return nil, err + } + + // Get data from a device + if _, err := d.dev.Read(bufferR); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to read data from device") + return nil, err + } + return bufferR, nil +} + +// ControlDial will modify brightness via control button +func (d *Device) ControlDial(data []byte) { + var brightness uint16 = 0 + brightness = d.DeviceProfile.BrightnessLevel + + switch data[1] { + case 0x05: // // Right horizontal spinning wheel + { + switch data[4] { + case 0x01: // Up direction + { + switch d.DeviceProfile.ControlDial { + case 1: + inputmanager.InputControl(inputmanager.VolumeUp, d.SlipstreamSerial) + break + case 2: + if brightness >= 1000 { + brightness = 1000 + break + } + + brightness += 200 + + if d.DeviceProfile != nil { + d.DeviceProfile.BrightnessLevel = brightness + d.saveDeviceProfile() + d.setBrightnessLevel() + } + break + } + } + break + case 0xff: // Down direction + { + switch d.DeviceProfile.ControlDial { + case 1: + inputmanager.InputControl(inputmanager.VolumeDown, d.SlipstreamSerial) + break + case 2: + if d.DeviceProfile.BrightnessLevel != 0 { + if brightness <= 0 { + brightness = 0 + } else { + brightness -= 200 + } + + if d.DeviceProfile != nil { + d.DeviceProfile.BrightnessLevel = brightness + d.saveDeviceProfile() + d.setBrightnessLevel() + } + } + break + } + } + } + } + case 0x02: + { + if data[2] == 0x04 { + switch d.DeviceProfile.ControlDial { + case 1: + inputmanager.InputControl(inputmanager.VolumeMute, d.SlipstreamSerial) + break + case 2: + if brightness > 0 { + brightness = 0 + } else { + brightness = 1000 + } + + if d.DeviceProfile != nil { + d.DeviceProfile.BrightnessLevel = brightness + d.saveDeviceProfile() + d.setBrightnessLevel() + } + break + } + } + } + } +} diff --git a/src/devices/k70coretklWU/k70coretklWU.go b/src/devices/k70coretklWU/k70coretklWU.go new file mode 100644 index 0000000..1f368d9 --- /dev/null +++ b/src/devices/k70coretklWU/k70coretklWU.go @@ -0,0 +1,1455 @@ +package k70coretklWU + +// Package: K70 CORE TKL WIRELESS +// This is the primary package for K70 CORE TKL WIRELESS. +// All device actions are controlled from this package. +// Author: Nikola Jurkovic +// License: GPL-3.0 or later + +import ( + "OpenLinkHub/src/common" + "OpenLinkHub/src/config" + "OpenLinkHub/src/inputmanager" + "OpenLinkHub/src/keyboards" + "OpenLinkHub/src/logger" + "OpenLinkHub/src/rgb" + "OpenLinkHub/src/temperatures" + "encoding/binary" + "encoding/json" + "fmt" + "github.com/sstallion/go-hid" + "os" + "regexp" + "slices" + "strings" + "sync" + "time" +) + +// DeviceProfile struct contains all device profile +type DeviceProfile struct { + Active bool + Path string + Product string + Serial string + LCDMode uint8 + LCDRotation uint8 + Brightness uint8 + RGBProfile string + Label string + Layout string + Keyboards map[string]*keyboards.Keyboard + Profile string + Profiles []string + BrightnessLevel uint16 + ControlDial int +} + +type Device struct { + Debug bool + dev *hid.Device + listener *hid.Device + Manufacturer string `json:"manufacturer"` + Product string `json:"product"` + Serial string `json:"serial"` + Firmware string `json:"firmware"` + activeRgb *rgb.ActiveRGB + UserProfiles map[string]*DeviceProfile `json:"userProfiles"` + Devices map[int]string `json:"devices"` + DeviceProfile *DeviceProfile + OriginalProfile *DeviceProfile + Template string + VendorId uint16 + ProductId uint16 + Brightness map[int]string + LEDChannels int + CpuTemp float32 + GpuTemp float32 + Layouts []string + Rgb *rgb.RGB + ControlDialOptions map[int]string + Exit bool + timer *time.Ticker + timerKeepAlive *time.Ticker + autoRefreshChan chan struct{} + keepAliveChan chan struct{} + mutex sync.Mutex + UIKeyboard string + UIKeyboardRow string +} + +var ( + pwd = "" + cmdSoftwareMode = []byte{0x01, 0x03, 0x00, 0x02} + cmdHardwareMode = []byte{0x01, 0x03, 0x00, 0x01} + cmdActivateLed = []byte{0x0d, 0x01, 0x22} + cmdGetFirmware = []byte{0x02, 0x13} + dataTypeSetColor = []byte{0x12, 0x00} + dataTypeSubColor = []byte{0x07, 0x01} + cmdWriteColor = []byte{0x06, 0x01} + cmdBrightness = []byte{0x01, 0x02, 0x00} + cmdKeepAlive = []byte{0x12} + deviceRefreshInterval = 1000 + deviceKeepAlive = 20000 + transferTimeout = 500 + bufferSize = 64 + bufferSizeWrite = bufferSize + 1 + headerSize = 2 + headerWriteSize = 4 + maxBufferSizePerRequest = 61 + colorPacketLength = 371 + keyboardKey = "k70coretklW-default" + defaultLayout = "k70coretklW-default-US" +) + +func Init(vendorId, productId uint16, key string) *Device { + // Set global working directory + pwd = config.GetConfig().ConfigPath + + dev, err := hid.OpenPath(key) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": vendorId, "productId": productId}).Error("Unable to open HID device") + return nil + } + + // Init new struct with HID device + d := &Device{ + dev: dev, + Template: "k70coretklWU.html", + VendorId: vendorId, + ProductId: productId, + Brightness: map[int]string{ + 0: "RGB Profile", + 1: "33 %", + 2: "66 %", + 3: "100 %", + }, + ControlDialOptions: map[int]string{ + 1: "Volume Control", + 2: "Brightness", + }, + Product: "K70 CORE TKL WIRELESS", + LEDChannels: 123, + Layouts: keyboards.GetLayouts(keyboardKey), + autoRefreshChan: make(chan struct{}), + keepAliveChan: make(chan struct{}), + UIKeyboard: "keyboard-6", + UIKeyboardRow: "keyboard-row-20", + } + + d.getDebugMode() // Debug mode + d.getManufacturer() // Manufacturer + d.getSerial() // Serial + d.loadRgb() // Load RGB + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.getDeviceFirmware() // Firmware + d.loadDeviceProfiles() // Load all device profiles + d.saveDeviceProfile() // Save profile + d.setAutoRefresh() // Set auto device refresh + d.setDeviceColor() // Device color + d.setKeepAlive() // Keepalive + d.controlDialListener() // Control Dial + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Device successfully initialized") + return d +} + +// GetRgbProfiles will return RGB profiles for a target device +func (d *Device) GetRgbProfiles() interface{} { + return d.Rgb +} + +// Stop will stop all device operations and switch a device back to hardware mode +func (d *Device) Stop() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Stop() + } + + d.timer.Stop() + d.timerKeepAlive.Stop() + var once sync.Once + go func() { + once.Do(func() { + if d.autoRefreshChan != nil { + close(d.autoRefreshChan) + } + close(d.keepAliveChan) + }) + }() + + d.setHardwareMode() + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Device stopped") +} + +// loadRgb will load RGB file if found, or create the default. +func (d *Device) loadRgb() { + rgbDirectory := pwd + "/database/rgb/" + rgbFilename := rgbDirectory + d.Serial + ".json" + + // Check if filename has .json extension + if !common.IsValidExtension(rgbFilename, ".json") { + return + } + + if !common.FileExists(rgbFilename) { + profile := rgb.GetRGB() + profile.Device = d.Product + + // Convert to JSON + buffer, err := json.MarshalIndent(profile, "", " ") + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to encode RGB json") + return + } + + // Create profile filename + file, err := os.Create(rgbFilename) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to create RGB json file") + return + } + + // Write JSON buffer to file + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to write to RGB json file") + return + } + + // Close file + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to close RGB json file") + return + } + } + + file, err := os.Open(rgbFilename) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to load RGB") + return + } + if err = json.NewDecoder(file).Decode(&d.Rgb); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to decode profile") + return + } + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"location": rgbFilename, "serial": d.Serial}).Warn("Failed to close file handle") + } +} + +// GetRgbProfile will return rgb.Profile struct +func (d *Device) GetRgbProfile(profile string) *rgb.Profile { + if d.Rgb == nil { + return nil + } + + if val, ok := d.Rgb.Profiles[profile]; ok { + return &val + } + return nil +} + +// GetDeviceTemplate will return device template name +func (d *Device) GetDeviceTemplate() string { + return d.Template +} + +// getManufacturer will return device manufacturer +func (d *Device) getDebugMode() { + d.Debug = config.GetConfig().Debug +} + +// getManufacturer will return device manufacturer +func (d *Device) getManufacturer() { + manufacturer, err := d.dev.GetMfrStr() + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to get manufacturer") + } + d.Manufacturer = manufacturer +} + +// getProduct will return device name +func (d *Device) getProduct() { + product, err := d.dev.GetProductStr() + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to get product") + } + d.Product = product +} + +// getSerial will return device serial number +func (d *Device) getSerial() { + serial, err := d.dev.GetSerialNbr() + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to get device serial number") + } + d.Serial = serial +} + +// setHardwareMode will switch a device to hardware mode +func (d *Device) setHardwareMode() { + _, err := d.transfer(cmdHardwareMode, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to change device mode") + } +} + +// setSoftwareMode will switch a device to software mode +func (d *Device) setSoftwareMode() { + _, err := d.transfer(cmdSoftwareMode, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to change device mode") + } +} + +// getDeviceFirmware will return a device firmware version out as string +func (d *Device) getDeviceFirmware() { + fw, err := d.transfer( + cmdGetFirmware, + nil, + ) + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to write to a device") + } + + v1, v2, v3 := int(fw[3]), int(fw[4]), int(binary.LittleEndian.Uint16(fw[5:7])) + d.Firmware = fmt.Sprintf("%d.%d.%d", v1, v2, v3) +} + +// keepAlive will keep a device alive +func (d *Device) keepAlive() { + if d.Exit { + return + } + _, err := d.transfer(cmdKeepAlive, nil) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device") + } +} + +// setAutoRefresh will refresh device data +func (d *Device) setKeepAlive() { + d.timerKeepAlive = time.NewTicker(time.Duration(deviceKeepAlive) * time.Millisecond) + go func() { + for { + select { + case <-d.timerKeepAlive.C: + d.keepAlive() + case <-d.keepAliveChan: + d.timerKeepAlive.Stop() + return + } + } + }() +} + +// initLeds will initialize LED ports +func (d *Device) initLeds() { + _, err := d.transfer(cmdActivateLed, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to change device mode") + } + // We need to wait around 500 ms for physical ports to re-initialize + // After that we can grab any new connected / disconnected device values + time.Sleep(time.Duration(transferTimeout) * time.Millisecond) +} + +// saveDeviceProfile will save device profile for persistent configuration +func (d *Device) saveDeviceProfile() { + profilePath := pwd + "/database/profiles/" + d.Serial + ".json" + keyboardMap := make(map[string]*keyboards.Keyboard, 0) + + deviceProfile := &DeviceProfile{ + Product: d.Product, + Serial: d.Serial, + Path: profilePath, + } + + // First save, assign saved profile to a device + if d.DeviceProfile == nil { + // RGB, Label + deviceProfile.RGBProfile = "keyboard" + deviceProfile.Label = "Keyboard" + deviceProfile.Active = true + keyboardMap["default"] = keyboards.GetKeyboard(defaultLayout) + deviceProfile.Keyboards = keyboardMap + deviceProfile.Profile = "default" + deviceProfile.Profiles = []string{"default"} + deviceProfile.Layout = "US" + deviceProfile.ControlDial = 1 + deviceProfile.BrightnessLevel = 1000 + } else { + if len(d.DeviceProfile.Layout) == 0 { + deviceProfile.Layout = "US" + } else { + deviceProfile.Layout = d.DeviceProfile.Layout + } + deviceProfile.ControlDial = d.DeviceProfile.ControlDial + deviceProfile.BrightnessLevel = d.DeviceProfile.BrightnessLevel + deviceProfile.Active = d.DeviceProfile.Active + deviceProfile.Brightness = d.DeviceProfile.Brightness + deviceProfile.RGBProfile = d.DeviceProfile.RGBProfile + deviceProfile.Label = d.DeviceProfile.Label + deviceProfile.Profile = d.DeviceProfile.Profile + deviceProfile.Profiles = d.DeviceProfile.Profiles + deviceProfile.Keyboards = d.DeviceProfile.Keyboards + if len(d.DeviceProfile.Path) < 1 { + deviceProfile.Path = profilePath + d.DeviceProfile.Path = profilePath + } else { + deviceProfile.Path = d.DeviceProfile.Path + } + deviceProfile.LCDMode = d.DeviceProfile.LCDMode + deviceProfile.LCDRotation = d.DeviceProfile.LCDRotation + } + + // Convert to JSON + buffer, err := json.MarshalIndent(deviceProfile, "", " ") + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to convert to json format") + return + } + + // Create profile filename + file, fileErr := os.Create(deviceProfile.Path) + if fileErr != nil { + logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Error("Unable to create new device profile") + return + } + + // Write JSON buffer to file + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Error("Unable to write data") + return + } + + // Close file + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Fatal("Unable to close file handle") + } + + d.loadDeviceProfiles() // Reload +} + +// loadDeviceProfiles will load custom user profiles +func (d *Device) loadDeviceProfiles() { + profileList := make(map[string]*DeviceProfile, 0) + userProfileDirectory := pwd + "/database/profiles/" + + files, err := os.ReadDir(userProfileDirectory) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": userProfileDirectory, "serial": d.Serial}).Fatal("Unable to read content of a folder") + } + + for _, fi := range files { + pf := &DeviceProfile{} + if fi.IsDir() { + continue // Exclude folders if any + } + + // Define a full path of filename + profileLocation := userProfileDirectory + fi.Name() + + // Check if filename has .json extension + if !common.IsValidExtension(profileLocation, ".json") { + continue + } + + fileName := strings.Split(fi.Name(), ".")[0] + if m, _ := regexp.MatchString("^[a-zA-Z0-9-]+$", fileName); !m { + continue + } + + fileSerial := "" + if strings.Contains(fileName, "-") { + fileSerial = strings.Split(fileName, "-")[0] + } else { + fileSerial = fileName + } + + if fileSerial != d.Serial { + continue + } + + file, err := os.Open(profileLocation) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": profileLocation}).Warn("Unable to load profile") + continue + } + if err = json.NewDecoder(file).Decode(pf); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": profileLocation}).Warn("Unable to decode profile") + continue + } + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"location": profileLocation, "serial": d.Serial}).Warn("Failed to close file handle") + } + + if pf.Serial == d.Serial { + if fileName == d.Serial { + profileList["default"] = pf + } else { + name := strings.Split(fileName, "-")[1] + profileList[name] = pf + } + logger.Log(logger.Fields{"location": profileLocation, "serial": d.Serial}).Info("Loaded custom user profile") + } + } + d.UserProfiles = profileList + d.getDeviceProfile() +} + +// getDeviceProfile will load persistent device configuration +func (d *Device) getDeviceProfile() { + if len(d.UserProfiles) == 0 { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("No profile found for device. Probably initial start") + } else { + for _, pf := range d.UserProfiles { + if pf.Active { + d.DeviceProfile = pf + } + } + } +} + +// setAutoRefresh will refresh device data +func (d *Device) setAutoRefresh() { + d.timer = time.NewTicker(time.Duration(deviceRefreshInterval) * time.Millisecond) + go func() { + for { + select { + case <-d.timer.C: + if d.Exit { + return + } + d.setTemperatures() + case <-d.autoRefreshChan: + d.timer.Stop() + return + } + } + }() +} + +// setCpuTemperature will store current CPU temperature +func (d *Device) setTemperatures() { + d.CpuTemp = temperatures.GetCpuTemperature() + d.GpuTemp = temperatures.GetGpuTemperature() +} + +// UpdateDeviceLabel will set / update device label +func (d *Device) UpdateDeviceLabel(_ int, label string) uint8 { + d.mutex.Lock() + defer d.mutex.Unlock() + + d.DeviceProfile.Label = label + d.saveDeviceProfile() + return 1 +} + +// saveRgbProfile will save rgb profile data +func (d *Device) saveRgbProfile() { + rgbDirectory := pwd + "/database/rgb/" + rgbFilename := rgbDirectory + d.Serial + ".json" + if common.FileExists(rgbFilename) { + buffer, err := json.MarshalIndent(d.Rgb, "", " ") + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to encode RGB json") + return + } + + // Create profile filename + file, err := os.Create(rgbFilename) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to create RGB json file") + return + } + + // Write JSON buffer to file + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to write to RGB json file") + return + } + + // Close file + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to close RGB json file") + return + } + } +} + +// UpdateRgbProfileData will update RGB profile data +func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { + if d.GetRgbProfile(profileName) == nil { + logger.Log(logger.Fields{"serial": d.Serial, "profile": profile}).Warn("Non-existing RGB profile") + return 0 + } + + pf := d.GetRgbProfile(profileName) + profile.StartColor.Brightness = pf.StartColor.Brightness + profile.EndColor.Brightness = pf.EndColor.Brightness + pf.StartColor = profile.StartColor + pf.EndColor = profile.EndColor + pf.Speed = profile.Speed + + d.Rgb.Profiles[profileName] = *pf + d.saveRgbProfile() + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 +} + +// UpdateRgbProfile will update device RGB profile +func (d *Device) UpdateRgbProfile(_ int, profile string) uint8 { + if d.GetRgbProfile(profile) == nil { + logger.Log(logger.Fields{"serial": d.Serial, "profile": profile}).Warn("Non-existing RGB profile") + return 0 + } + d.DeviceProfile.RGBProfile = profile // Set profile + d.saveDeviceProfile() // Save profile + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 + +} + +// ChangeDeviceBrightness will change device brightness +func (d *Device) ChangeDeviceBrightness(mode uint8) uint8 { + d.DeviceProfile.Brightness = mode + d.saveDeviceProfile() + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 +} + +// ChangeDeviceProfile will change device profile +func (d *Device) ChangeDeviceProfile(profileName string) uint8 { + if profile, ok := d.UserProfiles[profileName]; ok { + currentProfile := d.DeviceProfile + currentProfile.Active = false + d.DeviceProfile = currentProfile + d.saveDeviceProfile() + + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + + newProfile := profile + newProfile.Active = true + d.DeviceProfile = newProfile + d.saveDeviceProfile() + d.setDeviceColor() + return 1 + } + return 0 +} + +// ChangeKeyboardLayout will change keyboard layout +func (d *Device) ChangeKeyboardLayout(layout string) uint8 { + layouts := keyboards.GetLayouts(keyboardKey) + if len(layouts) < 1 { + return 2 + } + + if slices.Contains(layouts, layout) { + if d.DeviceProfile != nil { + if _, ok := d.DeviceProfile.Keyboards["default"]; ok { + layoutKey := fmt.Sprintf("%s-%s", keyboardKey, layout) + keyboardLayout := keyboards.GetKeyboard(layoutKey) + if keyboardLayout == nil { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("Trying to apply non-existing keyboard layout") + return 2 + } + + d.DeviceProfile.Keyboards["default"] = keyboardLayout + d.DeviceProfile.Layout = layout + d.saveDeviceProfile() + return 1 + } + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("DeviceProfile is null") + return 0 + } + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("No such layout") + return 2 + } + return 0 +} + +// getCurrentKeyboard will return current active keyboard +func (d *Device) getCurrentKeyboard() *keyboards.Keyboard { + if keyboard, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + return keyboard + } + return nil +} + +// SaveDeviceProfile will save a new keyboard profile +func (d *Device) SaveDeviceProfile(profileName string, new bool) uint8 { + if new { + if d.DeviceProfile == nil { + return 0 + } + + if slices.Contains(d.DeviceProfile.Profiles, profileName) { + return 2 + } + + if _, ok := d.DeviceProfile.Keyboards[profileName]; ok { + return 2 + } + + d.DeviceProfile.Profiles = append(d.DeviceProfile.Profiles, profileName) + d.DeviceProfile.Keyboards[profileName] = d.getCurrentKeyboard() + d.saveDeviceProfile() + return 1 + } else { + d.saveDeviceProfile() + return 1 + } +} + +// UpdateKeyboardProfile will change keyboard profile +func (d *Device) UpdateKeyboardProfile(profileName string) uint8 { + if d.DeviceProfile == nil { + return 0 + } + + if !slices.Contains(d.DeviceProfile.Profiles, profileName) { + return 2 + } + + if _, ok := d.DeviceProfile.Keyboards[profileName]; !ok { + return 2 + } + + d.DeviceProfile.Profile = profileName + d.saveDeviceProfile() + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() + return 1 +} + +// UpdateControlDial will update control dial function +func (d *Device) UpdateControlDial(value int) uint8 { + d.DeviceProfile.ControlDial = value + d.saveDeviceProfile() + return 1 +} + +// DeleteKeyboardProfile will delete keyboard profile +func (d *Device) DeleteKeyboardProfile(profileName string) uint8 { + if d.DeviceProfile == nil { + return 0 + } + + if profileName == "default" { + return 3 + } + + if !slices.Contains(d.DeviceProfile.Profiles, profileName) { + return 2 + } + + if _, ok := d.DeviceProfile.Keyboards[profileName]; !ok { + return 2 + } + + index := common.IndexOfString(d.DeviceProfile.Profiles, profileName) + if index < 0 { + return 0 + } + + d.DeviceProfile.Profile = "default" + d.DeviceProfile.Profiles = append(d.DeviceProfile.Profiles[:index], d.DeviceProfile.Profiles[index+1:]...) + delete(d.DeviceProfile.Keyboards, profileName) + + d.saveDeviceProfile() + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() + return 1 +} + +// SaveUserProfile will generate a new user profile configuration and save it to a file +func (d *Device) SaveUserProfile(profileName string) uint8 { + if d.DeviceProfile != nil { + profilePath := pwd + "/database/profiles/" + d.Serial + "-" + profileName + ".json" + + newProfile := d.DeviceProfile + newProfile.Path = profilePath + newProfile.Active = false + + buffer, err := json.Marshal(newProfile) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to convert to json format") + return 0 + } + + // Create profile filename + file, err := os.Create(profilePath) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to create new device profile") + return 0 + } + + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to write data") + return 0 + } + + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to close file handle") + return 0 + } + d.loadDeviceProfiles() + return 1 + } + return 0 +} + +// UpdateDeviceColor will update device color based on selected input +func (d *Device) UpdateDeviceColor(keyId, keyOption int, color rgb.Color) uint8 { + switch keyOption { + case 0: + { + for rowIndex, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for keyIndex, key := range row.Keys { + if keyIndex == keyId { + key.Color = rgb.Color{ + Red: color.Red, + Green: color.Green, + Blue: color.Blue, + Brightness: 0, + } + d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row[rowIndex].Keys[keyIndex] = key + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 + } + } + } + } + case 1: + { + rowId := -1 + for rowIndex, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for keyIndex := range row.Keys { + if keyIndex == keyId { + rowId = rowIndex + break + } + } + } + + if rowId < 0 { + return 0 + } + + for keyIndex, key := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row[rowId].Keys { + key.Color = rgb.Color{ + Red: color.Red, + Green: color.Green, + Blue: color.Blue, + Brightness: 0, + } + d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row[rowId].Keys[keyIndex] = key + } + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 + } + case 2: + { + for rowIndex, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for keyIndex, key := range row.Keys { + key.Color = rgb.Color{ + Red: color.Red, + Green: color.Green, + Blue: color.Blue, + Brightness: 0, + } + d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row[rowIndex].Keys[keyIndex] = key + } + } + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 + } + } + return 0 +} + +// setDeviceColor will activate and set device RGB +func (d *Device) setDeviceColor() { + if d.DeviceProfile == nil { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. DeviceProfile is null!") + return + } + + if d.DeviceProfile.BrightnessLevel == 0 { + return + } + // Reset + reset := map[int][]byte{} + var buffer []byte + + // Reset all channels + color := &rgb.Color{ + Red: 0, + Green: 0, + Blue: 0, + Brightness: 0, + } + + for i := 0; i < d.LEDChannels; i++ { + reset[i] = []byte{ + byte(color.Red), + byte(color.Green), + byte(color.Blue), + } + } + + buffer = rgb.SetColor(reset) + d.writeColor(buffer) + + if d.DeviceProfile.RGBProfile == "keyboard" { + var buf = make([]byte, colorPacketLength) + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + for _, rows := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, keys := range rows.Keys { + for _, packetIndex := range keys.PacketIndex { + buf[packetIndex] = byte(keys.Color.Red) + buf[packetIndex+1] = byte(keys.Color.Green) + buf[packetIndex+2] = byte(keys.Color.Blue) + } + } + } + d.writeColor(buf) // Write color once + return + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. Unknown keyboard") + return + } + } + + if d.DeviceProfile.RGBProfile == "static" { + profile := d.GetRgbProfile("static") + if d.DeviceProfile.Brightness != 0 { + profile.StartColor.Brightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) + } + profileColor := rgb.ModifyBrightness(profile.StartColor) + var buf = make([]byte, colorPacketLength) + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + for _, rows := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, keys := range rows.Keys { + for _, packetIndex := range keys.PacketIndex { + buf[packetIndex] = byte(profileColor.Red) + buf[packetIndex+1] = byte(profileColor.Green) + buf[packetIndex+2] = byte(profileColor.Blue) + } + } + } + d.writeColor(buf) // Write color once + return + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. Unknown keyboard") + return + } + } + + go func(lightChannels int) { + startTime := time.Now() + d.activeRgb = rgb.Exit() + + // Generate random colors + d.activeRgb.RGBStartColor = rgb.GenerateRandomColor(1) + d.activeRgb.RGBEndColor = rgb.GenerateRandomColor(1) + + for { + select { + case <-d.activeRgb.Exit: + return + default: + buff := make([]byte, 0) + + rgbCustomColor := true + profile := d.GetRgbProfile(d.DeviceProfile.RGBProfile) + if profile == nil { + for i := 0; i < d.LEDChannels; i++ { + buff = append(buff, []byte{0, 0, 0}...) + } + logger.Log(logger.Fields{"profile": d.DeviceProfile.RGBProfile, "serial": d.Serial}).Warn("No such RGB profile found") + continue + } + rgbModeSpeed := common.FClamp(profile.Speed, 0.1, 10) + // Check if we have custom colors + if (rgb.Color{}) == profile.StartColor || (rgb.Color{}) == profile.EndColor { + rgbCustomColor = false + } + + r := rgb.New( + d.LEDChannels, + rgbModeSpeed, + nil, + nil, + profile.Brightness, + common.Clamp(profile.Smoothness, 1, 100), + time.Duration(rgbModeSpeed)*time.Second, + rgbCustomColor, + ) + + if rgbCustomColor { + r.RGBStartColor = &profile.StartColor + r.RGBEndColor = &profile.EndColor + } else { + r.RGBStartColor = d.activeRgb.RGBStartColor + r.RGBEndColor = d.activeRgb.RGBEndColor + } + + // Brightness + if d.DeviceProfile.Brightness > 0 { + r.RGBBrightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) + r.RGBStartColor.Brightness = r.RGBBrightness + r.RGBEndColor.Brightness = r.RGBBrightness + } + + switch d.DeviceProfile.RGBProfile { + case "off": + { + for n := 0; n < d.LEDChannels; n++ { + buff = append(buff, []byte{0, 0, 0}...) + } + } + case "rainbow": + { + r.Rainbow(startTime) + buff = append(buff, r.Output...) + } + case "watercolor": + { + r.Watercolor(startTime) + buff = append(buff, r.Output...) + } + case "cpu-temperature": + { + r.MinTemp = profile.MinTemp + r.MaxTemp = profile.MaxTemp + r.Temperature(float64(d.CpuTemp)) + buff = append(buff, r.Output...) + } + case "gpu-temperature": + { + r.MinTemp = profile.MinTemp + r.MaxTemp = profile.MaxTemp + r.Temperature(float64(d.GpuTemp)) + buff = append(buff, r.Output...) + } + case "colorpulse": + { + r.Colorpulse(&startTime) + buff = append(buff, r.Output...) + } + case "static": + { + r.Static() + buff = append(buff, r.Output...) + } + case "rotator": + { + r.Rotator(&startTime) + buff = append(buff, r.Output...) + } + case "wave": + { + r.Wave(&startTime) + buff = append(buff, r.Output...) + } + case "storm": + { + r.Storm() + buff = append(buff, r.Output...) + } + case "flickering": + { + r.Flickering(&startTime) + buff = append(buff, r.Output...) + } + case "colorshift": + { + r.Colorshift(&startTime, d.activeRgb) + buff = append(buff, r.Output...) + } + case "circleshift": + { + r.CircleShift(&startTime) + buff = append(buff, r.Output...) + } + case "circle": + { + r.Circle(&startTime) + buff = append(buff, r.Output...) + } + case "spinner": + { + r.Spinner(&startTime) + buff = append(buff, r.Output...) + } + case "colorwarp": + { + r.Colorwarp(&startTime, d.activeRgb) + buff = append(buff, r.Output...) + } + } + + var buf = make([]byte, colorPacketLength) + for _, rows := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, keys := range rows.Keys { + for _, packetIndex := range keys.PacketIndex { + buf[packetIndex] = buff[packetIndex] + buf[packetIndex+1] = buff[packetIndex+1] + buf[packetIndex+2] = buff[packetIndex+2] + } + } + } + + // Send it + d.writeColor(buf) + time.Sleep(20 * time.Millisecond) + } + } + }(d.LEDChannels) +} + +// writeColor will write data to the device with a specific endpoint. +// writeColor does not require endpoint closing and opening like normal Write requires. +// Endpoint is open only once. Once the endpoint is open, color can be sent continuously. +func (d *Device) writeColor(data []byte) { + if d.Exit { + return + } + + buffer := make([]byte, len(dataTypeSetColor)+len(data)+headerWriteSize) + binary.LittleEndian.PutUint16(buffer[0:2], uint16(len(data))) + copy(buffer[headerWriteSize:headerWriteSize+len(dataTypeSetColor)], dataTypeSetColor) + copy(buffer[headerWriteSize+len(dataTypeSetColor):], data) + + // Split packet into chunks + chunks := common.ProcessMultiChunkPacket(buffer, maxBufferSizePerRequest) + for i, chunk := range chunks { + if i == 0 { + // Initial packet is using cmdWriteColor + _, err := d.transfer(cmdWriteColor, chunk) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to color endpoint") + } + } else { + // Chunks don't use cmdWriteColor, they use static dataTypeSubColor + _, err := d.transfer(dataTypeSubColor, chunk) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to endpoint") + } + } + } +} + +func (d *Device) resetDeviceColor() { + if d.DeviceProfile == nil { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. DeviceProfile is null!") + return + } + + if d.DeviceProfile.RGBProfile == "keyboard" { + var buf = make([]byte, colorPacketLength) + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + for _, rows := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, keys := range rows.Keys { + for _, packetIndex := range keys.PacketIndex { + buf[packetIndex] = byte(keys.Color.Red) + buf[packetIndex+1] = byte(keys.Color.Green) + buf[packetIndex+2] = byte(keys.Color.Blue) + } + } + } + d.writeColor(buf) // Write color once + return + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. Unknown keyboard") + return + } + } else if d.DeviceProfile.RGBProfile == "static" { + // Reset + reset := map[int][]byte{} + var buffer []byte + + profile := d.GetRgbProfile("static") + if d.DeviceProfile.Brightness != 0 { + profile.StartColor.Brightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) + } + + profileColor := rgb.ModifyBrightness(profile.StartColor) + for i := 0; i < d.LEDChannels; i++ { + reset[i] = []byte{ + byte(profileColor.Red), + byte(profileColor.Green), + byte(profileColor.Blue), + } + } + buffer = rgb.SetColor(reset) + d.writeColor(buffer) // Write color once + return + } +} + +// setBrightnessLevel will set global brightness level +func (d *Device) setBrightnessLevel() { + if d.Exit { + return + } + + if d.DeviceProfile != nil { + if d.DeviceProfile.BrightnessLevel == 0 { + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf[0:2], d.DeviceProfile.BrightnessLevel) + _, err := d.transfer(cmdBrightness, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change brightness") + } + } else { + if d.DeviceProfile.RGBProfile == "keyboard" || d.DeviceProfile.RGBProfile == "static" { + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf[0:2], d.DeviceProfile.BrightnessLevel) + _, err := d.transfer(cmdBrightness, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change brightness") + } + d.resetDeviceColor() + } else { + if d.activeRgb == nil { + d.setDeviceColor() + } + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf[0:2], d.DeviceProfile.BrightnessLevel) + _, err := d.transfer(cmdBrightness, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change brightness") + } + } + } + } +} + +// getListenerData will listen for keyboard events and return data on success or nil on failure. +// ReadWithTimeout is mandatory due to the nature of listening for events +func (d *Device) getListenerData() []byte { + data := make([]byte, bufferSize) + n, err := d.listener.ReadWithTimeout(data, 100*time.Millisecond) + if err != nil || n == 0 { + return nil + } + return data +} + +// controlDialListener will listen for events from the control dial +func (d *Device) controlDialListener() { + var brightness uint16 = 0 + brightness = d.DeviceProfile.BrightnessLevel + + go func() { + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == 2 { + listener, err := hid.OpenPath(info.Path) + if err != nil { + return err + } + d.listener = listener + } + return nil + }) + + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to enumerate devices") + } + + for { + select { + default: + if d.Exit { + err = d.listener.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Failed to close listener") + return + } + return + } + + data := d.getListenerData() + if len(data) == 0 || data == nil { + continue + } + + switch data[1] { + case 0x05: // // Right horizontal spinning wheel + { + switch data[4] { + case 0x01: // Up direction + { + switch d.DeviceProfile.ControlDial { + case 1: + inputmanager.InputControl(inputmanager.VolumeUp, d.Serial) + break + case 2: + if brightness >= 1000 { + brightness = 1000 + break + } + + brightness += 200 + + if d.DeviceProfile != nil { + d.DeviceProfile.BrightnessLevel = brightness + d.saveDeviceProfile() + d.setBrightnessLevel() + } + break + } + } + break + case 0xff: // Down direction + { + switch d.DeviceProfile.ControlDial { + case 1: + inputmanager.InputControl(inputmanager.VolumeDown, d.Serial) + break + case 2: + if d.DeviceProfile.BrightnessLevel != 0 { + if brightness <= 0 { + brightness = 0 + } else { + brightness -= 200 + } + + if d.DeviceProfile != nil { + d.DeviceProfile.BrightnessLevel = brightness + d.saveDeviceProfile() + d.setBrightnessLevel() + } + } + break + } + } + } + } + case 0x02: + { + if data[2] == 0x04 { + switch d.DeviceProfile.ControlDial { + case 1: + inputmanager.InputControl(inputmanager.VolumeMute, d.Serial) + break + case 2: + if brightness > 0 { + brightness = 0 + } else { + brightness = 1000 + } + + if d.DeviceProfile != nil { + d.DeviceProfile.BrightnessLevel = brightness + d.saveDeviceProfile() + d.setBrightnessLevel() + } + break + } + } + } + } + } + } + }() +} + +// transfer will send data to a device and retrieve device output +func (d *Device) transfer(endpoint, buffer []byte) ([]byte, error) { + // Packet control, mandatory for this device + d.mutex.Lock() + defer d.mutex.Unlock() + + // Create write buffer + bufferW := make([]byte, bufferSizeWrite) + bufferW[1] = 0x08 + endpointHeaderPosition := bufferW[headerSize : headerSize+len(endpoint)] + copy(endpointHeaderPosition, endpoint) + if len(buffer) > 0 { + copy(bufferW[headerSize+len(endpoint):headerSize+len(endpoint)+len(buffer)], buffer) + } + + // Create read buffer + bufferR := make([]byte, bufferSize) + + // Send command to a device + if _, err := d.dev.Write(bufferW); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device") + return nil, err + } + + // Get data from a device + if _, err := d.dev.Read(bufferR); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to read data from device") + return nil, err + } + + return bufferR, nil +} diff --git a/src/devices/k70mk2/k70mk2.go b/src/devices/k70mk2/k70mk2.go index f749eca..c4bbcb1 100644 --- a/src/devices/k70mk2/k70mk2.go +++ b/src/devices/k70mk2/k70mk2.go @@ -37,6 +37,7 @@ type DeviceProfile struct { Layout string Keyboards map[string]*keyboards.Keyboard Profile string + PollingRate int Profiles []string BrightnessSlider uint8 OriginalBrightness uint8 @@ -59,6 +60,7 @@ type Device struct { VendorId uint16 ProductId uint16 Brightness map[int]string + PollingRates map[int]string LEDChannels int CpuTemp float32 GpuTemp float32 @@ -68,6 +70,8 @@ type Device struct { timer *time.Ticker autoRefreshChan chan struct{} mutex sync.Mutex + UIKeyboard string + UIKeyboardRow string } var ( @@ -79,6 +83,7 @@ var ( cmdWrite = byte(0x07) cmdRead = byte(0x0e) cmdActivateKey = byte(0x40) + cmdSetPollingRate = []byte{0x0a, 0x00, 0x00} deviceRefreshInterval = 1000 bufferSize = 64 bufferSizeWrite = bufferSize + 1 @@ -116,6 +121,15 @@ func Init(vendorId, productId uint16, key string) *Device { Layouts: keyboards.GetLayouts(keyboardKey), autoRefreshChan: make(chan struct{}), listener: nil, + UIKeyboard: "keyboard-7", + UIKeyboardRow: "keyboard-row-25", + PollingRates: map[int]string{ + 0: "Not Set", + 8: "125 Hz / 8 msec", + 4: "250 Hu / 4 msec", + 2: "500 Hz / 2 msec", + 1: "1000 Hz / 1 msec", + }, } if d.ProductId == 7019 { @@ -368,6 +382,8 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profiles = []string{"default"} deviceProfile.Layout = "US" deviceProfile.BrightnessSlider = 100 + deviceProfile.PollingRate = 1 + } else { if len(d.DeviceProfile.Layout) == 0 { deviceProfile.Layout = "US" @@ -388,8 +404,7 @@ func (d *Device) saveDeviceProfile() { } else { deviceProfile.Path = d.DeviceProfile.Path } - deviceProfile.LCDMode = d.DeviceProfile.LCDMode - deviceProfile.LCDRotation = d.DeviceProfile.LCDRotation + deviceProfile.PollingRate = d.DeviceProfile.PollingRate } // Convert to JSON @@ -572,6 +587,86 @@ func (d *Device) saveRgbProfile() { } } +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + d.dev = dev + d.getDeviceFirmware() // Firmware + d.setSoftwareMode() // Activate software mode + d.toggleExit() // Toggle exit mode + d.setupKeys() // Setup keys + d.setDeviceColor() // Device color + d.controlListener() // Control listener +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + err := d.transfer(cmdWrite, cmdSetPollingRate, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + time.Sleep(5000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + // UpdateRgbProfileData will update RGB profile data func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { if d.GetRgbProfile(profileName) == nil { diff --git a/src/devices/k70pro/k70pro.go b/src/devices/k70pro/k70pro.go index a2ce2e4..b29628f 100644 --- a/src/devices/k70pro/k70pro.go +++ b/src/devices/k70pro/k70pro.go @@ -39,6 +39,7 @@ type DeviceProfile struct { Layout string Keyboards map[string]*keyboards.Keyboard Profile string + PollingRate int Profiles []string BrightnessLevel uint16 } @@ -60,6 +61,7 @@ type Device struct { VendorId uint16 ProductId uint16 Brightness map[int]string + PollingRates map[int]string LEDChannels int CpuTemp float32 GpuTemp float32 @@ -69,6 +71,8 @@ type Device struct { timer *time.Ticker autoRefreshChan chan struct{} mutex sync.Mutex + UIKeyboard string + UIKeyboardRow string } var ( @@ -81,6 +85,7 @@ var ( dataTypeSubColor = []byte{0x07, 0x00} cmdWriteColor = []byte{0x06, 0x01} cmdBrightness = []byte{0x01, 0x02, 0x00} + cmdSetPollingRate = []byte{0x01, 0x01, 0x00} deviceRefreshInterval = 1000 transferTimeout = 500 bufferSize = 1024 @@ -120,6 +125,18 @@ func Init(vendorId, productId uint16, key string) *Device { Layouts: keyboards.GetLayouts(keyboardKey), autoRefreshChan: make(chan struct{}), listener: nil, + UIKeyboard: "keyboard-7", + UIKeyboardRow: "keyboard-row-25", + PollingRates: map[int]string{ + 0: "Not Set", + 1: "125 Hz / 8 msec", + 2: "250 Hu / 4 msec", + 3: "500 Hz / 2 msec", + 4: "1000 Hz / 1 msec", + 5: "2000 Hz / 0.5 msec", + 6: "4000 Hz / 0.25 msec", + 7: "8000 Hz / 0.125 msec", + }, } d.getDebugMode() // Debug mode @@ -343,6 +360,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profiles = []string{"default"} deviceProfile.Layout = "US" deviceProfile.BrightnessLevel = 1000 + deviceProfile.PollingRate = 4 } else { if len(d.DeviceProfile.Layout) == 0 { deviceProfile.Layout = "US" @@ -357,6 +375,8 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profiles = d.DeviceProfile.Profiles deviceProfile.Keyboards = d.DeviceProfile.Keyboards deviceProfile.BrightnessLevel = d.DeviceProfile.BrightnessLevel + deviceProfile.PollingRate = d.DeviceProfile.PollingRate + if len(d.DeviceProfile.Path) < 1 { deviceProfile.Path = profilePath d.DeviceProfile.Path = profilePath @@ -547,6 +567,87 @@ func (d *Device) saveRgbProfile() { } } +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + d.dev = dev + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.getDeviceFirmware() // Firmware + d.toggleExit() // Toggle exit mode + d.setDeviceColor() // Device color + d.setBrightnessLevel() // Brightness + d.controlListener() // Control listener +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err := d.transfer(cmdSetPollingRate, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + time.Sleep(8000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + // UpdateRgbProfileData will update RGB profile data func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { if d.GetRgbProfile(profileName) == nil { diff --git a/src/devices/k70protkl/k70protkl.go b/src/devices/k70protkl/k70protkl.go new file mode 100644 index 0000000..52c31c9 --- /dev/null +++ b/src/devices/k70protkl/k70protkl.go @@ -0,0 +1,1519 @@ +package k70protkl + +// Package: K70 PRO TKL +// This is the primary package for K70 PRO TKL. +// All device actions are controlled from this package. +// Author: Nikola Jurkovic +// License: GPL-3.0 or later + +import ( + "OpenLinkHub/src/common" + "OpenLinkHub/src/config" + "OpenLinkHub/src/inputmanager" + "OpenLinkHub/src/keyboards" + "OpenLinkHub/src/logger" + "OpenLinkHub/src/rgb" + "OpenLinkHub/src/temperatures" + "encoding/binary" + "encoding/json" + "fmt" + "github.com/sstallion/go-hid" + "os" + "regexp" + "slices" + "strings" + "sync" + "time" +) + +// DeviceProfile struct contains all device profile +type DeviceProfile struct { + Active bool + Path string + Product string + Serial string + LCDMode uint8 + LCDRotation uint8 + Brightness uint8 + RGBProfile string + Label string + Layout string + Keyboards map[string]*keyboards.Keyboard + Profile string + PollingRate int + Profiles []string + BrightnessLevel uint16 + ControlDial int +} + +type Device struct { + Debug bool + dev *hid.Device + listener *hid.Device + Manufacturer string `json:"manufacturer"` + Product string `json:"product"` + Serial string `json:"serial"` + Firmware string `json:"firmware"` + activeRgb *rgb.ActiveRGB + UserProfiles map[string]*DeviceProfile `json:"userProfiles"` + Devices map[int]string `json:"devices"` + DeviceProfile *DeviceProfile + OriginalProfile *DeviceProfile + Template string + VendorId uint16 + ProductId uint16 + Brightness map[int]string + PollingRates map[int]string + LEDChannels int + CpuTemp float32 + GpuTemp float32 + Layouts []string + Rgb *rgb.RGB + ControlDialOptions map[int]string + Exit bool + timer *time.Ticker + timerKeepAlive *time.Ticker + autoRefreshChan chan struct{} + keepAliveChan chan struct{} + mutex sync.Mutex + UIKeyboard string + UIKeyboardRow string +} + +var ( + pwd = "" + cmdSoftwareMode = []byte{0x01, 0x03, 0x00, 0x02} + cmdHardwareMode = []byte{0x01, 0x03, 0x00, 0x01} + cmdActivateLed = []byte{0x0d, 0x01, 0x22} + cmdGetFirmware = []byte{0x02, 0x13} + dataTypeSetColor = []byte{0x12, 0x00} + dataTypeSubColor = []byte{0x07, 0x01} + cmdWriteColor = []byte{0x06, 0x01} + cmdBrightness = []byte{0x01, 0x02, 0x00} + cmdKeepAlive = []byte{0x12} + cmdSetPollingRate = []byte{0x01, 0x01, 0x00} + deviceRefreshInterval = 1000 + deviceKeepAlive = 20000 + transferTimeout = 500 + bufferSize = 256 + bufferSizeWrite = bufferSize + 1 + headerSize = 2 + headerWriteSize = 4 + maxBufferSizePerRequest = 253 + colorPacketLength = 395 + keyboardKey = "k70protkl-default" + defaultLayout = "k70protkl-default-US" +) + +func Init(vendorId, productId uint16, key string) *Device { + // Set global working directory + pwd = config.GetConfig().ConfigPath + + dev, err := hid.OpenPath(key) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": vendorId, "productId": productId}).Error("Unable to open HID device") + return nil + } + + // Init new struct with HID device + d := &Device{ + dev: dev, + Template: "k70protkl.html", + VendorId: vendorId, + ProductId: productId, + Brightness: map[int]string{ + 0: "RGB Profile", + 1: "33 %", + 2: "66 %", + 3: "100 %", + }, + ControlDialOptions: map[int]string{ + 1: "Volume Control", + 2: "Brightness", + }, + Product: "K70 PRO TKL", + LEDChannels: 139, + Layouts: keyboards.GetLayouts(keyboardKey), + autoRefreshChan: make(chan struct{}), + keepAliveChan: make(chan struct{}), + UIKeyboard: "keyboard-6", + UIKeyboardRow: "keyboard-row-20", + PollingRates: map[int]string{ + 0: "Not Set", + 1: "125 Hz / 8 msec", + 2: "250 Hu / 4 msec", + 3: "500 Hz / 2 msec", + 4: "1000 Hz / 1 msec", + 5: "2000 Hz / 0.5 msec", + 6: "4000 Hz / 0.25 msec", + 7: "8000 Hz / 0.125 msec", + }, + } + + d.getDebugMode() // Debug mode + d.getManufacturer() // Manufacturer + d.getSerial() // Serial + d.loadRgb() // Load RGB + d.setSoftwareMode() // Activate software mode + d.getDeviceFirmware() // Firmware + d.loadDeviceProfiles() // Load all device profiles + d.saveDeviceProfile() // Save profile + d.setAutoRefresh() // Set auto device refresh + d.initLeds() // Init LED ports + d.setDeviceColor() // Device color + d.setKeepAlive() // Keepalive + d.controlDialListener() // Control Dial + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Device successfully initialized") + return d +} + +// GetRgbProfiles will return RGB profiles for a target device +func (d *Device) GetRgbProfiles() interface{} { + return d.Rgb +} + +// Stop will stop all device operations and switch a device back to hardware mode +func (d *Device) Stop() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Stop() + } + + d.timer.Stop() + d.timerKeepAlive.Stop() + var once sync.Once + go func() { + once.Do(func() { + if d.autoRefreshChan != nil { + close(d.autoRefreshChan) + } + close(d.keepAliveChan) + }) + }() + + d.setHardwareMode() + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Device stopped") +} + +// loadRgb will load RGB file if found, or create the default. +func (d *Device) loadRgb() { + rgbDirectory := pwd + "/database/rgb/" + rgbFilename := rgbDirectory + d.Serial + ".json" + + // Check if filename has .json extension + if !common.IsValidExtension(rgbFilename, ".json") { + return + } + + if !common.FileExists(rgbFilename) { + profile := rgb.GetRGB() + profile.Device = d.Product + + // Convert to JSON + buffer, err := json.MarshalIndent(profile, "", " ") + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to encode RGB json") + return + } + + // Create profile filename + file, err := os.Create(rgbFilename) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to create RGB json file") + return + } + + // Write JSON buffer to file + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to write to RGB json file") + return + } + + // Close file + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to close RGB json file") + return + } + } + + file, err := os.Open(rgbFilename) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to load RGB") + return + } + if err = json.NewDecoder(file).Decode(&d.Rgb); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to decode profile") + return + } + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"location": rgbFilename, "serial": d.Serial}).Warn("Failed to close file handle") + } +} + +// GetRgbProfile will return rgb.Profile struct +func (d *Device) GetRgbProfile(profile string) *rgb.Profile { + if d.Rgb == nil { + return nil + } + + if val, ok := d.Rgb.Profiles[profile]; ok { + return &val + } + return nil +} + +// GetDeviceTemplate will return device template name +func (d *Device) GetDeviceTemplate() string { + return d.Template +} + +// getManufacturer will return device manufacturer +func (d *Device) getDebugMode() { + d.Debug = config.GetConfig().Debug +} + +// getManufacturer will return device manufacturer +func (d *Device) getManufacturer() { + manufacturer, err := d.dev.GetMfrStr() + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to get manufacturer") + } + d.Manufacturer = manufacturer +} + +// getProduct will return device name +func (d *Device) getProduct() { + product, err := d.dev.GetProductStr() + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to get product") + } + d.Product = product +} + +// getSerial will return device serial number +func (d *Device) getSerial() { + serial, err := d.dev.GetSerialNbr() + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to get device serial number") + } + d.Serial = serial +} + +// setHardwareMode will switch a device to hardware mode +func (d *Device) setHardwareMode() { + _, err := d.transfer(cmdHardwareMode, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to change device mode") + } +} + +// setSoftwareMode will switch a device to software mode +func (d *Device) setSoftwareMode() { + _, err := d.transfer(cmdSoftwareMode, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to change device mode") + } +} + +// getDeviceFirmware will return a device firmware version out as string +func (d *Device) getDeviceFirmware() { + fw, err := d.transfer( + cmdGetFirmware, + nil, + ) + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to write to a device") + } + + v1, v2, v3 := int(fw[3]), int(fw[4]), int(binary.LittleEndian.Uint16(fw[5:7])) + d.Firmware = fmt.Sprintf("%d.%d.%d", v1, v2, v3) +} + +// keepAlive will keep a device alive +func (d *Device) keepAlive() { + if d.Exit { + return + } + _, err := d.transfer(cmdKeepAlive, nil) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device") + } +} + +// setAutoRefresh will refresh device data +func (d *Device) setKeepAlive() { + d.timerKeepAlive = time.NewTicker(time.Duration(deviceKeepAlive) * time.Millisecond) + go func() { + for { + select { + case <-d.timerKeepAlive.C: + d.keepAlive() + case <-d.keepAliveChan: + d.timerKeepAlive.Stop() + return + } + } + }() +} + +// initLeds will initialize LED ports +func (d *Device) initLeds() { + _, err := d.transfer(cmdActivateLed, nil) + if err != nil { + logger.Log(logger.Fields{"error": err}).Fatal("Unable to change device mode") + } + // We need to wait around 500 ms for physical ports to re-initialize + // After that we can grab any new connected / disconnected device values + time.Sleep(time.Duration(transferTimeout) * time.Millisecond) +} + +// saveDeviceProfile will save device profile for persistent configuration +func (d *Device) saveDeviceProfile() { + profilePath := pwd + "/database/profiles/" + d.Serial + ".json" + keyboardMap := make(map[string]*keyboards.Keyboard, 0) + + deviceProfile := &DeviceProfile{ + Product: d.Product, + Serial: d.Serial, + Path: profilePath, + } + + // First save, assign saved profile to a device + if d.DeviceProfile == nil { + // RGB, Label + deviceProfile.RGBProfile = "keyboard" + deviceProfile.Label = "Keyboard" + deviceProfile.Active = true + keyboardMap["default"] = keyboards.GetKeyboard(defaultLayout) + deviceProfile.Keyboards = keyboardMap + deviceProfile.Profile = "default" + deviceProfile.Profiles = []string{"default"} + deviceProfile.Layout = "US" + deviceProfile.ControlDial = 1 + deviceProfile.BrightnessLevel = 1000 + deviceProfile.PollingRate = 4 + } else { + if len(d.DeviceProfile.Layout) == 0 { + deviceProfile.Layout = "US" + } else { + deviceProfile.Layout = d.DeviceProfile.Layout + } + deviceProfile.ControlDial = d.DeviceProfile.ControlDial + deviceProfile.BrightnessLevel = d.DeviceProfile.BrightnessLevel + deviceProfile.Active = d.DeviceProfile.Active + deviceProfile.Brightness = d.DeviceProfile.Brightness + deviceProfile.RGBProfile = d.DeviceProfile.RGBProfile + deviceProfile.Label = d.DeviceProfile.Label + deviceProfile.Profile = d.DeviceProfile.Profile + deviceProfile.Profiles = d.DeviceProfile.Profiles + deviceProfile.Keyboards = d.DeviceProfile.Keyboards + deviceProfile.PollingRate = d.DeviceProfile.PollingRate + if len(d.DeviceProfile.Path) < 1 { + deviceProfile.Path = profilePath + d.DeviceProfile.Path = profilePath + } else { + deviceProfile.Path = d.DeviceProfile.Path + } + deviceProfile.LCDMode = d.DeviceProfile.LCDMode + deviceProfile.LCDRotation = d.DeviceProfile.LCDRotation + } + + // Convert to JSON + buffer, err := json.MarshalIndent(deviceProfile, "", " ") + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to convert to json format") + return + } + + // Create profile filename + file, fileErr := os.Create(deviceProfile.Path) + if fileErr != nil { + logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Error("Unable to create new device profile") + return + } + + // Write JSON buffer to file + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Error("Unable to write data") + return + } + + // Close file + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Fatal("Unable to close file handle") + } + + d.loadDeviceProfiles() // Reload +} + +// loadDeviceProfiles will load custom user profiles +func (d *Device) loadDeviceProfiles() { + profileList := make(map[string]*DeviceProfile, 0) + userProfileDirectory := pwd + "/database/profiles/" + + files, err := os.ReadDir(userProfileDirectory) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": userProfileDirectory, "serial": d.Serial}).Fatal("Unable to read content of a folder") + } + + for _, fi := range files { + pf := &DeviceProfile{} + if fi.IsDir() { + continue // Exclude folders if any + } + + // Define a full path of filename + profileLocation := userProfileDirectory + fi.Name() + + // Check if filename has .json extension + if !common.IsValidExtension(profileLocation, ".json") { + continue + } + + fileName := strings.Split(fi.Name(), ".")[0] + if m, _ := regexp.MatchString("^[a-zA-Z0-9-]+$", fileName); !m { + continue + } + + fileSerial := "" + if strings.Contains(fileName, "-") { + fileSerial = strings.Split(fileName, "-")[0] + } else { + fileSerial = fileName + } + + if fileSerial != d.Serial { + continue + } + + file, err := os.Open(profileLocation) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": profileLocation}).Warn("Unable to load profile") + continue + } + if err = json.NewDecoder(file).Decode(pf); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": profileLocation}).Warn("Unable to decode profile") + continue + } + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"location": profileLocation, "serial": d.Serial}).Warn("Failed to close file handle") + } + + if pf.Serial == d.Serial { + if fileName == d.Serial { + profileList["default"] = pf + } else { + name := strings.Split(fileName, "-")[1] + profileList[name] = pf + } + logger.Log(logger.Fields{"location": profileLocation, "serial": d.Serial}).Info("Loaded custom user profile") + } + } + d.UserProfiles = profileList + d.getDeviceProfile() +} + +// getDeviceProfile will load persistent device configuration +func (d *Device) getDeviceProfile() { + if len(d.UserProfiles) == 0 { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("No profile found for device. Probably initial start") + } else { + for _, pf := range d.UserProfiles { + if pf.Active { + d.DeviceProfile = pf + } + } + } +} + +// setAutoRefresh will refresh device data +func (d *Device) setAutoRefresh() { + d.timer = time.NewTicker(time.Duration(deviceRefreshInterval) * time.Millisecond) + go func() { + for { + select { + case <-d.timer.C: + if d.Exit { + return + } + d.setTemperatures() + case <-d.autoRefreshChan: + d.timer.Stop() + return + } + } + }() +} + +// setCpuTemperature will store current CPU temperature +func (d *Device) setTemperatures() { + d.CpuTemp = temperatures.GetCpuTemperature() + d.GpuTemp = temperatures.GetGpuTemperature() +} + +// UpdateDeviceLabel will set / update device label +func (d *Device) UpdateDeviceLabel(_ int, label string) uint8 { + d.mutex.Lock() + defer d.mutex.Unlock() + + d.DeviceProfile.Label = label + d.saveDeviceProfile() + return 1 +} + +// saveRgbProfile will save rgb profile data +func (d *Device) saveRgbProfile() { + rgbDirectory := pwd + "/database/rgb/" + rgbFilename := rgbDirectory + d.Serial + ".json" + if common.FileExists(rgbFilename) { + buffer, err := json.MarshalIndent(d.Rgb, "", " ") + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to encode RGB json") + return + } + + // Create profile filename + file, err := os.Create(rgbFilename) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to create RGB json file") + return + } + + // Write JSON buffer to file + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to write to RGB json file") + return + } + + // Close file + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to close RGB json file") + return + } + } +} + +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + d.dev = dev + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.getDeviceFirmware() // Firmware + d.toggleExit() // Toggle exit mode + d.setDeviceColor() // Device color + d.controlDialListener() // Control Dial +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err := d.transfer(cmdSetPollingRate, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + time.Sleep(10000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + +// UpdateRgbProfileData will update RGB profile data +func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { + if d.GetRgbProfile(profileName) == nil { + logger.Log(logger.Fields{"serial": d.Serial, "profile": profile}).Warn("Non-existing RGB profile") + return 0 + } + + pf := d.GetRgbProfile(profileName) + profile.StartColor.Brightness = pf.StartColor.Brightness + profile.EndColor.Brightness = pf.EndColor.Brightness + pf.StartColor = profile.StartColor + pf.EndColor = profile.EndColor + pf.Speed = profile.Speed + + d.Rgb.Profiles[profileName] = *pf + d.saveRgbProfile() + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 +} + +// UpdateRgbProfile will update device RGB profile +func (d *Device) UpdateRgbProfile(_ int, profile string) uint8 { + if d.GetRgbProfile(profile) == nil { + logger.Log(logger.Fields{"serial": d.Serial, "profile": profile}).Warn("Non-existing RGB profile") + return 0 + } + d.DeviceProfile.RGBProfile = profile // Set profile + d.saveDeviceProfile() // Save profile + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 + +} + +// ChangeDeviceBrightness will change device brightness +func (d *Device) ChangeDeviceBrightness(mode uint8) uint8 { + d.DeviceProfile.Brightness = mode + d.saveDeviceProfile() + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 +} + +// ChangeDeviceProfile will change device profile +func (d *Device) ChangeDeviceProfile(profileName string) uint8 { + if profile, ok := d.UserProfiles[profileName]; ok { + currentProfile := d.DeviceProfile + currentProfile.Active = false + d.DeviceProfile = currentProfile + d.saveDeviceProfile() + + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + + newProfile := profile + newProfile.Active = true + d.DeviceProfile = newProfile + d.saveDeviceProfile() + d.setDeviceColor() + return 1 + } + return 0 +} + +// ChangeKeyboardLayout will change keyboard layout +func (d *Device) ChangeKeyboardLayout(layout string) uint8 { + layouts := keyboards.GetLayouts(keyboardKey) + if len(layouts) < 1 { + return 2 + } + + if slices.Contains(layouts, layout) { + if d.DeviceProfile != nil { + if _, ok := d.DeviceProfile.Keyboards["default"]; ok { + layoutKey := fmt.Sprintf("%s-%s", keyboardKey, layout) + keyboardLayout := keyboards.GetKeyboard(layoutKey) + if keyboardLayout == nil { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("Trying to apply non-existing keyboard layout") + return 2 + } + + d.DeviceProfile.Keyboards["default"] = keyboardLayout + d.DeviceProfile.Layout = layout + d.saveDeviceProfile() + return 1 + } + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("DeviceProfile is null") + return 0 + } + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Warn("No such layout") + return 2 + } + return 0 +} + +// getCurrentKeyboard will return current active keyboard +func (d *Device) getCurrentKeyboard() *keyboards.Keyboard { + if keyboard, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + return keyboard + } + return nil +} + +// SaveDeviceProfile will save a new keyboard profile +func (d *Device) SaveDeviceProfile(profileName string, new bool) uint8 { + if new { + if d.DeviceProfile == nil { + return 0 + } + + if slices.Contains(d.DeviceProfile.Profiles, profileName) { + return 2 + } + + if _, ok := d.DeviceProfile.Keyboards[profileName]; ok { + return 2 + } + + d.DeviceProfile.Profiles = append(d.DeviceProfile.Profiles, profileName) + d.DeviceProfile.Keyboards[profileName] = d.getCurrentKeyboard() + d.saveDeviceProfile() + return 1 + } else { + d.saveDeviceProfile() + return 1 + } +} + +// UpdateKeyboardProfile will change keyboard profile +func (d *Device) UpdateKeyboardProfile(profileName string) uint8 { + if d.DeviceProfile == nil { + return 0 + } + + if !slices.Contains(d.DeviceProfile.Profiles, profileName) { + return 2 + } + + if _, ok := d.DeviceProfile.Keyboards[profileName]; !ok { + return 2 + } + + d.DeviceProfile.Profile = profileName + d.saveDeviceProfile() + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() + return 1 +} + +// UpdateControlDial will update control dial function +func (d *Device) UpdateControlDial(value int) uint8 { + d.DeviceProfile.ControlDial = value + d.saveDeviceProfile() + return 1 +} + +// DeleteKeyboardProfile will delete keyboard profile +func (d *Device) DeleteKeyboardProfile(profileName string) uint8 { + if d.DeviceProfile == nil { + return 0 + } + + if profileName == "default" { + return 3 + } + + if !slices.Contains(d.DeviceProfile.Profiles, profileName) { + return 2 + } + + if _, ok := d.DeviceProfile.Keyboards[profileName]; !ok { + return 2 + } + + index := common.IndexOfString(d.DeviceProfile.Profiles, profileName) + if index < 0 { + return 0 + } + + d.DeviceProfile.Profile = "default" + d.DeviceProfile.Profiles = append(d.DeviceProfile.Profiles[:index], d.DeviceProfile.Profiles[index+1:]...) + delete(d.DeviceProfile.Keyboards, profileName) + + d.saveDeviceProfile() + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() + return 1 +} + +// SaveUserProfile will generate a new user profile configuration and save it to a file +func (d *Device) SaveUserProfile(profileName string) uint8 { + if d.DeviceProfile != nil { + profilePath := pwd + "/database/profiles/" + d.Serial + "-" + profileName + ".json" + + newProfile := d.DeviceProfile + newProfile.Path = profilePath + newProfile.Active = false + + buffer, err := json.Marshal(newProfile) + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to convert to json format") + return 0 + } + + // Create profile filename + file, err := os.Create(profilePath) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to create new device profile") + return 0 + } + + _, err = file.Write(buffer) + if err != nil { + logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to write data") + return 0 + } + + err = file.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to close file handle") + return 0 + } + d.loadDeviceProfiles() + return 1 + } + return 0 +} + +// UpdateDeviceColor will update device color based on selected input +func (d *Device) UpdateDeviceColor(keyId, keyOption int, color rgb.Color) uint8 { + switch keyOption { + case 0: + { + for rowIndex, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for keyIndex, key := range row.Keys { + if keyIndex == keyId { + key.Color = rgb.Color{ + Red: color.Red, + Green: color.Green, + Blue: color.Blue, + Brightness: 0, + } + d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row[rowIndex].Keys[keyIndex] = key + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 + } + } + } + } + case 1: + { + rowId := -1 + for rowIndex, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for keyIndex := range row.Keys { + if keyIndex == keyId { + rowId = rowIndex + break + } + } + } + + if rowId < 0 { + return 0 + } + + for keyIndex, key := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row[rowId].Keys { + key.Color = rgb.Color{ + Red: color.Red, + Green: color.Green, + Blue: color.Blue, + Brightness: 0, + } + d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row[rowId].Keys[keyIndex] = key + } + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 + } + case 2: + { + for rowIndex, row := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for keyIndex, key := range row.Keys { + key.Color = rgb.Color{ + Red: color.Red, + Green: color.Green, + Blue: color.Blue, + Brightness: 0, + } + d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row[rowIndex].Keys[keyIndex] = key + } + } + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.setDeviceColor() // Restart RGB + return 1 + } + } + return 0 +} + +// setDeviceColor will activate and set device RGB +func (d *Device) setDeviceColor() { + if d.DeviceProfile == nil { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. DeviceProfile is null!") + return + } + + if d.DeviceProfile.BrightnessLevel == 0 { + return + } + + if d.DeviceProfile.RGBProfile == "keyboard" { + var buf = make([]byte, colorPacketLength) + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + for _, rows := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, keys := range rows.Keys { + for _, packetIndex := range keys.PacketIndex { + buf[packetIndex] = byte(keys.Color.Red) + buf[packetIndex+1] = byte(keys.Color.Green) + buf[packetIndex+2] = byte(keys.Color.Blue) + } + } + } + d.writeColor(buf) // Write color once + return + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. Unknown keyboard") + return + } + } + + if d.DeviceProfile.RGBProfile == "static" { + // Reset + var buf = make([]byte, colorPacketLength) + profile := d.GetRgbProfile("static") + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + for _, rows := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, keys := range rows.Keys { + for _, packetIndex := range keys.PacketIndex { + buf[packetIndex] = byte(profile.StartColor.Red) + buf[packetIndex+1] = byte(profile.StartColor.Green) + buf[packetIndex+2] = byte(profile.StartColor.Blue) + } + } + } + d.writeColor(buf) // Write color once + return + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. Unknown keyboard") + return + } + } + + go func(lightChannels int) { + startTime := time.Now() + d.activeRgb = rgb.Exit() + + // Generate random colors + d.activeRgb.RGBStartColor = rgb.GenerateRandomColor(1) + d.activeRgb.RGBEndColor = rgb.GenerateRandomColor(1) + + for { + select { + case <-d.activeRgb.Exit: + return + default: + buff := make([]byte, 0) + + rgbCustomColor := true + profile := d.GetRgbProfile(d.DeviceProfile.RGBProfile) + if profile == nil { + for i := 0; i < d.LEDChannels; i++ { + buff = append(buff, []byte{0, 0, 0}...) + } + logger.Log(logger.Fields{"profile": d.DeviceProfile.RGBProfile, "serial": d.Serial}).Warn("No such RGB profile found") + continue + } + rgbModeSpeed := common.FClamp(profile.Speed, 0.1, 10) + // Check if we have custom colors + if (rgb.Color{}) == profile.StartColor || (rgb.Color{}) == profile.EndColor { + rgbCustomColor = false + } + + r := rgb.New( + d.LEDChannels, + rgbModeSpeed, + nil, + nil, + profile.Brightness, + common.Clamp(profile.Smoothness, 1, 100), + time.Duration(rgbModeSpeed)*time.Second, + rgbCustomColor, + ) + + if rgbCustomColor { + r.RGBStartColor = &profile.StartColor + r.RGBEndColor = &profile.EndColor + } else { + r.RGBStartColor = d.activeRgb.RGBStartColor + r.RGBEndColor = d.activeRgb.RGBEndColor + } + + // Brightness + if d.DeviceProfile.Brightness > 0 { + r.RGBBrightness = rgb.GetBrightnessValue(d.DeviceProfile.Brightness) + r.RGBStartColor.Brightness = r.RGBBrightness + r.RGBEndColor.Brightness = r.RGBBrightness + } + + switch d.DeviceProfile.RGBProfile { + case "off": + { + for n := 0; n < d.LEDChannels; n++ { + buff = append(buff, []byte{0, 0, 0}...) + } + } + case "rainbow": + { + r.Rainbow(startTime) + buff = append(buff, r.Output...) + } + case "watercolor": + { + r.Watercolor(startTime) + buff = append(buff, r.Output...) + } + case "cpu-temperature": + { + r.MinTemp = profile.MinTemp + r.MaxTemp = profile.MaxTemp + r.Temperature(float64(d.CpuTemp)) + buff = append(buff, r.Output...) + } + case "gpu-temperature": + { + r.MinTemp = profile.MinTemp + r.MaxTemp = profile.MaxTemp + r.Temperature(float64(d.GpuTemp)) + buff = append(buff, r.Output...) + } + case "colorpulse": + { + r.Colorpulse(&startTime) + buff = append(buff, r.Output...) + } + case "static": + { + r.Static() + buff = append(buff, r.Output...) + } + case "rotator": + { + r.Rotator(&startTime) + buff = append(buff, r.Output...) + } + case "wave": + { + r.Wave(&startTime) + buff = append(buff, r.Output...) + } + case "storm": + { + r.Storm() + buff = append(buff, r.Output...) + } + case "flickering": + { + r.Flickering(&startTime) + buff = append(buff, r.Output...) + } + case "colorshift": + { + r.Colorshift(&startTime, d.activeRgb) + buff = append(buff, r.Output...) + } + case "circleshift": + { + r.CircleShift(&startTime) + buff = append(buff, r.Output...) + } + case "circle": + { + r.Circle(&startTime) + buff = append(buff, r.Output...) + } + case "spinner": + { + r.Spinner(&startTime) + buff = append(buff, r.Output...) + } + case "colorwarp": + { + r.Colorwarp(&startTime, d.activeRgb) + buff = append(buff, r.Output...) + } + } + + var buf = make([]byte, colorPacketLength) + for _, rows := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, keys := range rows.Keys { + for _, packetIndex := range keys.PacketIndex { + buf[packetIndex] = buff[packetIndex] + buf[packetIndex+1] = buff[packetIndex+1] + buf[packetIndex+2] = buff[packetIndex+2] + } + } + } + + // Send it + d.writeColor(buf) + time.Sleep(20 * time.Millisecond) + } + } + }(d.LEDChannels) +} + +// writeColor will write data to the device with a specific endpoint. +// writeColor does not require endpoint closing and opening like normal Write requires. +// Endpoint is open only once. Once the endpoint is open, color can be sent continuously. +func (d *Device) writeColor(data []byte) { + if d.Exit { + return + } + + buffer := make([]byte, len(dataTypeSetColor)+len(data)+headerWriteSize) + binary.LittleEndian.PutUint16(buffer[0:2], uint16(len(data))) + copy(buffer[headerWriteSize:headerWriteSize+len(dataTypeSetColor)], dataTypeSetColor) + copy(buffer[headerWriteSize+len(dataTypeSetColor):], data) + + // Split packet into chunks + chunks := common.ProcessMultiChunkPacket(buffer, maxBufferSizePerRequest) + for i, chunk := range chunks { + if i == 0 { + // Initial packet is using cmdWriteColor + _, err := d.transfer(cmdWriteColor, chunk) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to color endpoint") + } + } else { + // Chunks don't use cmdWriteColor, they use static dataTypeSubColor + _, err := d.transfer(dataTypeSubColor, chunk) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to endpoint") + } + } + } +} + +func (d *Device) resetDeviceColor() { + if d.DeviceProfile == nil { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. DeviceProfile is null!") + return + } + + if d.DeviceProfile.RGBProfile == "keyboard" { + var buf = make([]byte, colorPacketLength) + if _, ok := d.DeviceProfile.Keyboards[d.DeviceProfile.Profile]; ok { + for _, rows := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, keys := range rows.Keys { + for _, packetIndex := range keys.PacketIndex { + buf[packetIndex] = byte(keys.Color.Red) + buf[packetIndex+1] = byte(keys.Color.Green) + buf[packetIndex+2] = byte(keys.Color.Blue) + } + } + } + d.writeColor(buf) // Write color once + return + } else { + logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. Unknown keyboard") + return + } + } else if d.DeviceProfile.RGBProfile == "static" { + profile := d.GetRgbProfile("static") + if profile == nil { + return + } + + var buf = make([]byte, colorPacketLength) + for _, rows := range d.DeviceProfile.Keyboards[d.DeviceProfile.Profile].Row { + for _, keys := range rows.Keys { + for _, packetIndex := range keys.PacketIndex { + buf[packetIndex] = byte(profile.StartColor.Red) + buf[packetIndex+1] = byte(profile.StartColor.Green) + buf[packetIndex+2] = byte(profile.StartColor.Blue) + } + } + } + d.writeColor(buf) // Write color once + return + } +} + +// setBrightnessLevel will set global brightness level +func (d *Device) setBrightnessLevel() { + if d.Exit { + return + } + + if d.DeviceProfile != nil { + if d.DeviceProfile.BrightnessLevel == 0 { + // RGB reset + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf[0:2], d.DeviceProfile.BrightnessLevel) + _, err := d.transfer(cmdBrightness, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change brightness") + } + } else { + if d.DeviceProfile.RGBProfile == "keyboard" || d.DeviceProfile.RGBProfile == "static" { + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf[0:2], d.DeviceProfile.BrightnessLevel) + _, err := d.transfer(cmdBrightness, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change brightness") + } + d.resetDeviceColor() + } else { + if d.activeRgb == nil { + d.setDeviceColor() + } + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf[0:2], d.DeviceProfile.BrightnessLevel) + _, err := d.transfer(cmdBrightness, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change brightness") + } + } + } + } +} + +// getListenerData will listen for keyboard events and return data on success or nil on failure. +// ReadWithTimeout is mandatory due to the nature of listening for events +func (d *Device) getListenerData() []byte { + data := make([]byte, bufferSize) + n, err := d.listener.ReadWithTimeout(data, 100*time.Millisecond) + if err != nil || n == 0 { + return nil + } + return data +} + +// controlDialListener will listen for events from the control dial +func (d *Device) controlDialListener() { + var brightness uint16 = 0 + brightness = d.DeviceProfile.BrightnessLevel + + go func() { + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == 2 { + listener, err := hid.OpenPath(info.Path) + if err != nil { + return err + } + d.listener = listener + } + return nil + }) + + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to enumerate devices") + } + + for { + select { + default: + if d.Exit { + err = d.listener.Close() + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Failed to close listener") + return + } + return + } + + data := d.getListenerData() + if len(data) == 0 || data == nil { + continue + } + + switch data[1] { + case 0x05: // // Right horizontal spinning wheel + { + switch data[4] { + case 0x01: // Up direction + { + switch d.DeviceProfile.ControlDial { + case 1: + inputmanager.InputControl(inputmanager.VolumeUp, d.Serial) + break + case 2: + if brightness >= 1000 { + brightness = 1000 + break + } + brightness += 200 + if d.DeviceProfile != nil { + d.DeviceProfile.BrightnessLevel = brightness + d.saveDeviceProfile() + d.setBrightnessLevel() + } + break + } + } + break + case 0xff: // Down direction + { + switch d.DeviceProfile.ControlDial { + case 1: + inputmanager.InputControl(inputmanager.VolumeDown, d.Serial) + break + case 2: + if d.DeviceProfile.BrightnessLevel != 0 { + if brightness <= 0 { + brightness = 0 + } else { + brightness -= 200 + } + + if d.DeviceProfile != nil { + d.DeviceProfile.BrightnessLevel = brightness + d.saveDeviceProfile() + d.setBrightnessLevel() + } + } + break + } + } + } + } + case 0x02: + { + if data[18] == 0x02 { + switch d.DeviceProfile.ControlDial { + case 1: + inputmanager.InputControl(inputmanager.VolumeMute, d.Serial) + break + case 2: + if brightness > 0 { + brightness = 0 + } else { + brightness = 1000 + } + if d.DeviceProfile != nil { + d.DeviceProfile.BrightnessLevel = brightness + d.saveDeviceProfile() + d.setBrightnessLevel() + } + break + } + } + } + } + } + } + }() +} + +// transfer will send data to a device and retrieve device output +func (d *Device) transfer(endpoint, buffer []byte) ([]byte, error) { + // Packet control, mandatory for this device + d.mutex.Lock() + defer d.mutex.Unlock() + + // Create write buffer + bufferW := make([]byte, bufferSizeWrite) + bufferW[1] = 0x08 + endpointHeaderPosition := bufferW[headerSize : headerSize+len(endpoint)] + copy(endpointHeaderPosition, endpoint) + if len(buffer) > 0 { + copy(bufferW[headerSize+len(endpoint):headerSize+len(endpoint)+len(buffer)], buffer) + } + + // Create read buffer + bufferR := make([]byte, bufferSize) + + // Send command to a device + if _, err := d.dev.Write(bufferW); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device") + return nil, err + } + + // Get data from a device + if _, err := d.dev.Read(bufferR); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to read data from device") + return nil, err + } + + return bufferR, nil +} diff --git a/src/devices/katarpro/katarpro.go b/src/devices/katarpro/katarpro.go index d774926..a83bb2e 100644 --- a/src/devices/katarpro/katarpro.go +++ b/src/devices/katarpro/katarpro.go @@ -33,6 +33,7 @@ type DeviceProfile struct { RGBProfile string Label string Profile int + PollingRate int Profiles map[int]DPIProfile ZoneColors map[int]ZoneColors BrightnessSlider *uint8 @@ -69,6 +70,7 @@ type Device struct { VendorId uint16 ProductId uint16 Brightness map[int]string + PollingRates map[int]string ChangeableLedChannels int LEDChannels int CpuTemp float32 @@ -95,6 +97,7 @@ var ( cmdCloseEndpoint = []byte{0x05, 0x01, 0x01} cmdWrite = []byte{0x06, 0x01} cmdSetDpi = []byte{0x01} + cmdSetPollingRate = []byte{0x01, 0x01, 0x00} deviceRefreshInterval = 1000 deviceKeepAlive = 20000 bufferSize = 64 @@ -127,6 +130,13 @@ func Init(vendorId, productId uint16, key string) *Device { 2: "66 %", 3: "100 %", }, + PollingRates: map[int]string{ + 0: "Not Set", + 1: "125 Hz / 8 msec", + 2: "250 Hu / 4 msec", + 3: "500 Hz / 2 msec", + 4: "1000 Hz / 1 msec", + }, Product: "KATAR PRO", LEDChannels: 1, ChangeableLedChannels: 1, @@ -160,6 +170,64 @@ func (d *Device) GetRgbProfiles() interface{} { return d.Rgb } +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + + d.dev = dev + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.getDeviceFirmware() // Firmware + d.initButtons() // Init buttons + d.toggleExit() // Remove Exit flag + d.setDeviceColor() // Device color + d.controlListener() // Control listener + d.toggleDPI(false) // Set current DPI +} + // Stop will stop all device operations and switch a device back to hardware mode func (d *Device) Stop() { d.Exit = true @@ -301,7 +369,7 @@ func (d *Device) getSerial() { // setHardwareMode will switch a device to hardware mode func (d *Device) setHardwareMode() { - _, err := d.transfer(cmdHardwareMode, nil) + _, err := d.transfer(cmdHardwareMode, nil, "setHardwareMode") if err != nil { logger.Log(logger.Fields{"error": err}).Fatal("Unable to change device mode") } @@ -309,7 +377,7 @@ func (d *Device) setHardwareMode() { // setSoftwareMode will switch a device to software mode func (d *Device) setSoftwareMode() { - _, err := d.transfer(cmdSoftwareMode, nil) + _, err := d.transfer(cmdSoftwareMode, nil, "setSoftwareMode") if err != nil { logger.Log(logger.Fields{"error": err}).Fatal("Unable to change device mode") } @@ -320,6 +388,7 @@ func (d *Device) getDeviceFirmware() { fw, err := d.transfer( cmdGetFirmware, nil, + "getDeviceFirmware", ) if err != nil { logger.Log(logger.Fields{"error": err}).Fatal("Unable to write to a device") @@ -331,7 +400,7 @@ func (d *Device) getDeviceFirmware() { // initLeds will initialize LED endpoint func (d *Device) initLeds() { - _, err := d.transfer(cmdOpenColorEndpoint, nil) + _, err := d.transfer(cmdOpenColorEndpoint, nil, "initLeds") if err != nil { logger.Log(logger.Fields{"error": err}).Fatal("Unable to change device mode") } @@ -352,19 +421,19 @@ func (d *Device) initButtons() { } func (d *Device) write(data []byte) error { - _, err := d.transfer(cmdOpenWriteEndpoint, nil) + _, err := d.transfer(cmdOpenWriteEndpoint, nil, "initButtons") if err != nil { logger.Log(logger.Fields{"error": err}).Error("Unable to open write endpoint") return err } - _, err = d.transfer(cmdWrite, data) + _, err = d.transfer(cmdWrite, data, "initButtons") if err != nil { logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode") return err } - _, err = d.transfer(cmdCloseEndpoint, nil) + _, err = d.transfer(cmdCloseEndpoint, nil, "initButtons") if err != nil { logger.Log(logger.Fields{"error": err}).Error("Unable to close write endpoint") return err @@ -374,7 +443,10 @@ func (d *Device) write(data []byte) error { // keepAlive will keep a device alive func (d *Device) keepAlive() { - _, err := d.transfer(cmdHeartbeat, nil) + if d.Exit { + return + } + _, err := d.transfer(cmdHeartbeat, nil, "keepAlive") if err != nil { logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device") return @@ -496,6 +568,7 @@ func (d *Device) saveDeviceProfile() { }, } deviceProfile.Profile = 1 + deviceProfile.PollingRate = 4 } else { if d.DeviceProfile.BrightnessSlider == nil { deviceProfile.BrightnessSlider = &defaultBrightness @@ -511,6 +584,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profiles = d.DeviceProfile.Profiles deviceProfile.Profile = d.DeviceProfile.Profile deviceProfile.ZoneColors = d.DeviceProfile.ZoneColors + deviceProfile.PollingRate = d.DeviceProfile.PollingRate if len(d.DeviceProfile.Path) < 1 { deviceProfile.Path = profilePath @@ -872,6 +946,31 @@ func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) u return 1 } +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err := d.transfer(cmdSetPollingRate, buf, "UpdatePollingRate") + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + time.Sleep(5000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + // UpdateRgbProfile will update device RGB profile func (d *Device) UpdateRgbProfile(_ int, profile string) uint8 { if d.GetRgbProfile(profile) == nil { @@ -1208,7 +1307,7 @@ func (d *Device) toggleDPI(set bool) { buf[1] = 0x00 binary.LittleEndian.PutUint16(buf[2:4], value) - _, err := d.transfer(cmdSetDpi, buf) + _, err := d.transfer(cmdSetDpi, buf, "toggleDPI") if err != nil { logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to set dpi") } @@ -1314,14 +1413,14 @@ func (d *Device) writeColor(data []byte) { binary.LittleEndian.PutUint16(buffer[0:2], uint16(len(data))) copy(buffer[headerWriteSize:], data) - _, err := d.transfer(cmdWriteColor, buffer) + _, err := d.transfer(cmdWriteColor, buffer, "writeColor") if err != nil { logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to color endpoint") } } // transfer will send data to a device and retrieve device output -func (d *Device) transfer(endpoint, buffer []byte) ([]byte, error) { +func (d *Device) transfer(endpoint, buffer []byte, caller string) ([]byte, error) { // Packet control, mandatory for this device d.mutex.Lock() defer d.mutex.Unlock() @@ -1340,13 +1439,13 @@ func (d *Device) transfer(endpoint, buffer []byte) ([]byte, error) { // Send command to a device if _, err := d.dev.Write(bufferW); err != nil { - logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device") + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "caller": caller}).Error("Unable to write to a device") return nil, err } // Get data from a device if _, err := d.dev.Read(bufferR); err != nil { - logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to read data from device") + logger.Log(logger.Fields{"error": err, "serial": d.Serial, "caller": caller}).Error("Unable to read data from device") return nil, err } diff --git a/src/devices/katarproW/katarproW.go b/src/devices/katarproW/katarproW.go index 0fd7d74..1a7660f 100644 --- a/src/devices/katarproW/katarproW.go +++ b/src/devices/katarproW/katarproW.go @@ -33,6 +33,7 @@ type DeviceProfile struct { BrightnessSlider *uint8 Label string Profile int + PollingRate int Profiles map[int]DPIProfile SleepMode int } @@ -63,6 +64,7 @@ type Device struct { ProductId uint16 SlipstreamId uint16 Brightness map[int]string + PollingRates map[int]string LEDChannels int ChangeableLedChannels int CpuTemp float32 @@ -93,6 +95,7 @@ var ( cmdMouse = byte(0x09) cmdSetDpi = map[int][]byte{0: {0x01, 0x20, 0x00}} cmdSleep = map[int][]byte{0: {0x01, 0x37, 0x00}, 1: {0x01, 0x0e, 0x00}} + cmdSetPollingRate = []byte{0x01, 0x01, 0x00} bufferSize = 64 bufferSizeWrite = bufferSize + 1 headerSize = 2 @@ -131,6 +134,14 @@ func Init(vendorId, productId uint16, key string) *Device { 2: "66 %", 3: "100 %", }, + PollingRates: map[int]string{ + 0: "Not Set", + 1: "125 Hz / 8 msec", + 2: "250 Hu / 4 msec", + 3: "500 Hz / 2 msec", + 4: "1000 Hz / 1 msec", + 5: "2000 Hz / 0.5 msec", + }, Product: "KATAR PRO WIRELESS", SleepModes: map[int]string{ 1: "1 minute", @@ -286,6 +297,121 @@ func (d *Device) initLeds() { } } +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + d.Connected = false + time.Sleep(500 * time.Millisecond) +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + + d.dev = dev + d.setDongleSoftwareMode() // Switch to software mode + + msg, err := d.transferToDevice(cmdMouse, cmdHeartbeat, nil, "checkIfAlive") + if err != nil { + logger.Log(logger.Fields{"error": err}).Warn("Unable to perform initial mouse init. Device is either offline or in sleep mode") + return + } + + if len(msg) > 0 && msg[1] == 0x12 { + d.Connected = true // Mark as connected + d.setMouseHardwareMode() // Hardware mode + d.setSoftwareMode() // Switch to software mode + d.getDeviceFirmware() // Firmware + d.initLeds() // Init LED ports + d.toggleExit() // Remove Exit flag + d.toggleDPI() // DPI + d.setSleepTimer() // Sleep timer + d.controlListener() // Control listener + } +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if !d.Connected { + return 0 + } + + if _, ok := d.PollingRates[pullingRate]; ok { + // Check if the mouse is alive and connected. If mouse is not alive, block any change. + msg, err := d.transferToDevice(cmdMouse, cmdHeartbeat, nil, "checkIfAlive") + if err != nil { + logger.Log(logger.Fields{"error": err}).Warn("Unable to perform initial mouse check. Device is either offline or in sleep mode") + return 0 + } + if len(msg) > 0 && msg[1] == 0x12 { + // Mouse has to be connected, since polling change is done on dongle and mouse. + // Changing the polling rate either on dongle or mouse only will break the connection. + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err = d.transfer(cmdMouse, cmdSetPollingRate, buf, "UpdatePollingRate") + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + _, err = d.transfer(cmdDongle, cmdSetPollingRate, buf, "UpdatePollingRate") + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse dongle polling rate") + return 0 + } + + time.Sleep(5000 * time.Millisecond) + d.Restart() + return 1 + } + } + return 0 +} + // ChangeDeviceProfile will change device profile func (d *Device) ChangeDeviceProfile(profileName string) uint8 { if !d.Connected { @@ -343,6 +469,22 @@ func (d *Device) setDeviceColor() { } } d.writeColor(buf) + + time.Sleep(1000 * time.Millisecond) + for i := 0; i < len(dpiLeds.ColorIndex); i++ { + dpiColorIndexRange := dpiLeds.ColorIndex[i] + for key, dpiColorIndex := range dpiColorIndexRange { + switch key { + case 0: // Red + buf[dpiColorIndex] = byte(0) + case 1: // Green + buf[dpiColorIndex] = byte(0) + case 2: // Blue + buf[dpiColorIndex] = byte(0) + } + } + } + d.writeColor(buf) return } @@ -476,6 +618,7 @@ func (d *Device) saveDeviceProfile() { } deviceProfile.Profile = 1 deviceProfile.SleepMode = 5 + deviceProfile.PollingRate = 4 } else { if d.DeviceProfile.BrightnessSlider == nil { deviceProfile.BrightnessSlider = &defaultBrightness @@ -490,6 +633,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profiles = d.DeviceProfile.Profiles deviceProfile.Profile = d.DeviceProfile.Profile deviceProfile.SleepMode = d.DeviceProfile.SleepMode + deviceProfile.PollingRate = d.DeviceProfile.PollingRate if len(d.DeviceProfile.Path) < 1 { deviceProfile.Path = profilePath @@ -768,7 +912,6 @@ func (d *Device) setupMouse(init bool) { d.setSoftwareMode() // Switch to software mode d.getDeviceFirmware() // Firmware d.initLeds() // Init LED ports - d.setDeviceColor() // Device color d.toggleDPI() // DPI d.setSleepTimer() // Sleep timer } else { diff --git a/src/devices/m55/m55.go b/src/devices/m55/m55.go index 78c5154..29e8f4d 100644 --- a/src/devices/m55/m55.go +++ b/src/devices/m55/m55.go @@ -34,6 +34,7 @@ type DeviceProfile struct { OriginalBrightness uint8 Label string Profile int + PollingRate int Profiles map[int]DPIProfile SleepMode int } @@ -63,6 +64,7 @@ type Device struct { VendorId uint16 ProductId uint16 Brightness map[int]string + PollingRates map[int]string LEDChannels int ChangeableLedChannels int CpuTemp float32 @@ -77,21 +79,22 @@ type Device struct { } var ( - pwd = "" - cmdSoftwareMode = []byte{0x01, 0x03, 0x00, 0x02} - cmdHardwareMode = []byte{0x01, 0x03, 0x00, 0x01} - cmdGetFirmware = []byte{0x02, 0x13} - cmdWriteColor = []byte{0x06, 0x00} - cmdOpenEndpoint = []byte{0x0d, 0x00, 0x01} - cmdSetDpi = map[int][]byte{0: {0x01, 0x20, 0x00}} - dataTypeSetColor = []byte{0x12, 0x00} - bufferSize = 128 - bufferSizeWrite = bufferSize + 1 - headerSize = 2 - headerWriteSize = 4 - minDpiValue = 100 - maxDpiValue = 16000 - deviceKeepAlive = 20000 + pwd = "" + cmdSoftwareMode = []byte{0x01, 0x03, 0x00, 0x02} + cmdHardwareMode = []byte{0x01, 0x03, 0x00, 0x01} + cmdGetFirmware = []byte{0x02, 0x13} + cmdWriteColor = []byte{0x06, 0x00} + cmdOpenEndpoint = []byte{0x0d, 0x00, 0x01} + cmdSetDpi = map[int][]byte{0: {0x01, 0x20, 0x00}} + dataTypeSetColor = []byte{0x12, 0x00} + cmdSetPollingRate = []byte{0x01, 0x01, 0x00} + bufferSize = 128 + bufferSizeWrite = bufferSize + 1 + headerSize = 2 + headerWriteSize = 4 + minDpiValue = 100 + maxDpiValue = 16000 + deviceKeepAlive = 20000 ) func Init(vendorId, productId uint16, key string) *Device { @@ -130,6 +133,13 @@ func Init(vendorId, productId uint16, key string) *Device { ChangeableLedChannels: 0, keepAliveChan: make(chan struct{}), timerKeepAlive: &time.Ticker{}, + PollingRates: map[int]string{ + 0: "Not Set", + 1: "125 Hz / 8 msec", + 2: "250 Hu / 4 msec", + 3: "500 Hz / 2 msec", + 4: "1000 Hz / 1 msec", + }, } d.getDebugMode() // Debug mode @@ -626,6 +636,7 @@ func (d *Device) saveDeviceProfile() { } deviceProfile.Profile = 1 deviceProfile.SleepMode = 15 + deviceProfile.PollingRate = 4 } else { if d.DeviceProfile.BrightnessSlider == nil { deviceProfile.BrightnessSlider = &defaultBrightness @@ -641,6 +652,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profiles = d.DeviceProfile.Profiles deviceProfile.Profile = d.DeviceProfile.Profile deviceProfile.SleepMode = d.DeviceProfile.SleepMode + deviceProfile.PollingRate = d.DeviceProfile.PollingRate if len(d.DeviceProfile.Path) < 1 { deviceProfile.Path = profilePath @@ -806,6 +818,87 @@ func (d *Device) setDeviceColor() { return } +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + d.dev = dev + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.getDeviceFirmware() // Firmware + d.toggleExit() // Remove Exit flag + d.setDeviceColor() // Device color + d.controlListener() // Control listener + d.toggleDPI() // Set current DPI +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err := d.transfer(cmdSetPollingRate, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + time.Sleep(5000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + func (d *Device) ModifyDpi() { if d.DeviceProfile.Profile >= 4 { d.DeviceProfile.Profile = 0 diff --git a/src/devices/m55rgbpro/m55rgbpro.go b/src/devices/m55rgbpro/m55rgbpro.go index df7ef9c..e091c78 100644 --- a/src/devices/m55rgbpro/m55rgbpro.go +++ b/src/devices/m55rgbpro/m55rgbpro.go @@ -40,6 +40,7 @@ type DeviceProfile struct { OriginalBrightness uint8 Label string Profile int + PollingRate int ZoneColors map[int]ZoneColors Profiles map[int]DPIProfile SleepMode int @@ -70,6 +71,7 @@ type Device struct { VendorId uint16 ProductId uint16 Brightness map[int]string + PollingRates map[int]string LEDChannels int ChangeableLedChannels int CpuTemp float32 @@ -84,20 +86,21 @@ type Device struct { } var ( - pwd = "" - cmdSoftwareMode = []byte{0x01, 0x03, 0x00, 0x02} - cmdHardwareMode = []byte{0x01, 0x03, 0x00, 0x01} - cmdGetFirmware = []byte{0x02, 0x13} - cmdWriteColor = []byte{0x06, 0x00} - cmdOpenEndpoint = []byte{0x0d, 0x00, 0x01} - cmdSetDpi = map[int][]byte{0: {0x01, 0x20, 0x00}} - bufferSize = 128 - bufferSizeWrite = bufferSize + 1 - headerSize = 2 - headerWriteSize = 4 - minDpiValue = 200 - maxDpiValue = 12400 - deviceKeepAlive = 20000 + pwd = "" + cmdSoftwareMode = []byte{0x01, 0x03, 0x00, 0x02} + cmdHardwareMode = []byte{0x01, 0x03, 0x00, 0x01} + cmdGetFirmware = []byte{0x02, 0x13} + cmdWriteColor = []byte{0x06, 0x00} + cmdOpenEndpoint = []byte{0x0d, 0x00, 0x01} + cmdSetDpi = map[int][]byte{0: {0x01, 0x20, 0x00}} + cmdSetPollingRate = []byte{0x01, 0x01, 0x00} + bufferSize = 64 + bufferSizeWrite = bufferSize + 1 + headerSize = 2 + headerWriteSize = 4 + minDpiValue = 200 + maxDpiValue = 12400 + deviceKeepAlive = 20000 ) func Init(vendorId, productId uint16, key string) *Device { @@ -136,6 +139,13 @@ func Init(vendorId, productId uint16, key string) *Device { ChangeableLedChannels: 1, keepAliveChan: make(chan struct{}), timerKeepAlive: &time.Ticker{}, + PollingRates: map[int]string{ + 0: "Not Set", + 1: "125 Hz / 8 msec", + 2: "250 Hu / 4 msec", + 3: "500 Hz / 2 msec", + 4: "1000 Hz / 1 msec", + }, } d.getDebugMode() // Debug mode @@ -340,6 +350,88 @@ func (d *Device) saveRgbProfile() { } } +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + + d.dev = dev + d.getDeviceFirmware() // Firmware + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.toggleExit() // Remove Exit flag + d.setDeviceColor() // Device color + d.toggleDPI() // DPI + d.controlListener() // Control listener +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err := d.transfer(cmdSetPollingRate, buf, false) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + time.Sleep(5000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + // UpdateRgbProfileData will update RGB profile data func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { if d.GetRgbProfile(profileName) == nil { @@ -603,7 +695,7 @@ func (d *Device) getDebugMode() { // setHardwareMode will switch a device to hardware mode func (d *Device) setHardwareMode() { - _, err := d.transfer(cmdHardwareMode, nil) + _, err := d.transfer(cmdHardwareMode, nil, true) if err != nil { logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode") } @@ -611,7 +703,7 @@ func (d *Device) setHardwareMode() { // setSoftwareMode will switch a device to software mode func (d *Device) setSoftwareMode() { - _, err := d.transfer(cmdSoftwareMode, nil) + _, err := d.transfer(cmdSoftwareMode, nil, true) if err != nil { logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode") } @@ -630,6 +722,7 @@ func (d *Device) getDeviceFirmware() { fw, err := d.transfer( cmdGetFirmware, nil, + true, ) if err != nil { logger.Log(logger.Fields{"error": err}).Error("Unable to write to a device") @@ -750,6 +843,7 @@ func (d *Device) saveDeviceProfile() { } deviceProfile.Profile = 1 deviceProfile.SleepMode = 15 + deviceProfile.PollingRate = 4 } else { if d.DeviceProfile.BrightnessSlider == nil { deviceProfile.BrightnessSlider = &defaultBrightness @@ -766,6 +860,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.ZoneColors = d.DeviceProfile.ZoneColors deviceProfile.Profile = d.DeviceProfile.Profile deviceProfile.SleepMode = d.DeviceProfile.SleepMode + deviceProfile.PollingRate = d.DeviceProfile.PollingRate if len(d.DeviceProfile.Path) < 1 { deviceProfile.Path = profilePath @@ -889,7 +984,7 @@ func (d *Device) getDeviceProfile() { // initLeds will initialize LED endpoint func (d *Device) initLeds() { - _, err := d.transfer(cmdOpenEndpoint, nil) + _, err := d.transfer(cmdOpenEndpoint, nil, true) if err != nil { logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode") } @@ -1161,7 +1256,7 @@ func (d *Device) toggleDPI() { buf := make([]byte, 2) binary.LittleEndian.PutUint16(buf[0:2], value) for i := 0; i <= 1; i++ { - _, err := d.transfer(cmdSetDpi[i], buf) + _, err := d.transfer(cmdSetDpi[i], buf, true) if err != nil { logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set dpi") } @@ -1214,14 +1309,14 @@ func (d *Device) writeColor(data []byte) { binary.LittleEndian.PutUint16(buffer[0:2], uint16(len(data))) copy(buffer[headerWriteSize:], data) - _, err := d.transfer(cmdWriteColor, buffer) + _, err := d.transfer(cmdWriteColor, buffer, true) if err != nil { logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to color endpoint") } } // transfer will send data to a device and retrieve device output -func (d *Device) transfer(endpoint, buffer []byte) ([]byte, error) { +func (d *Device) transfer(endpoint, buffer []byte, read bool) ([]byte, error) { // Packet control, mandatory for this device d.mutex.Lock() defer d.mutex.Unlock() @@ -1244,11 +1339,14 @@ func (d *Device) transfer(endpoint, buffer []byte) ([]byte, error) { return nil, err } - // Get data from a device - if _, err := d.dev.Read(bufferR); err != nil { - logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to read data from device") - return nil, err + if read { + // Get data from a device + if _, err := d.dev.Read(bufferR); err != nil { + logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to read data from device") + return nil, err + } } + return bufferR, nil } diff --git a/src/devices/m65rgbultra/m65rgbultra.go b/src/devices/m65rgbultra/m65rgbultra.go index 614fffc..0b73f02 100644 --- a/src/devices/m65rgbultra/m65rgbultra.go +++ b/src/devices/m65rgbultra/m65rgbultra.go @@ -40,6 +40,7 @@ type DeviceProfile struct { OriginalBrightness uint8 Label string Profile int + PollingRate int ZoneColors map[int]ZoneColors Profiles map[int]DPIProfile SleepMode int @@ -70,6 +71,7 @@ type Device struct { VendorId uint16 ProductId uint16 Brightness map[int]string + PollingRates map[int]string LEDChannels int ChangeableLedChannels int CpuTemp float32 @@ -100,13 +102,14 @@ var ( 0: {0x01, 0x21, 0x00}, 1: {0x01, 0x22, 0x00}, } - bufferSize = 64 - bufferSizeWrite = bufferSize + 1 - headerSize = 2 - headerWriteSize = 4 - minDpiValue = 100 - maxDpiValue = 26000 - deviceKeepAlive = 20000 + cmdSetPollingRate = []byte{0x01, 0x01, 0x00} + bufferSize = 64 + bufferSizeWrite = bufferSize + 1 + headerSize = 2 + headerWriteSize = 4 + minDpiValue = 100 + maxDpiValue = 26000 + deviceKeepAlive = 20000 ) func Init(vendorId, productId uint16, key string) *Device { @@ -147,6 +150,16 @@ func Init(vendorId, productId uint16, key string) *Device { timerKeepAlive: &time.Ticker{}, autoRefreshChan: make(chan struct{}), timer: &time.Ticker{}, + PollingRates: map[int]string{ + 0: "Not Set", + 1: "125 Hz / 8 msec", + 2: "250 Hu / 4 msec", + 3: "500 Hz / 2 msec", + 4: "1000 Hz / 1 msec", + 5: "2000 Hz / 0.5 msec", + 6: "4000 Hz / 0.25 msec", + 7: "8000 Hz / 0.125 msec", + }, } d.getDebugMode() // Debug mode @@ -162,11 +175,12 @@ func Init(vendorId, productId uint16, key string) *Device { d.toggleDPI() // DPI d.controlListener() // Control listener d.setKeepAlive() // Keepalive - d.activateButtons() + d.activateButtons() // Activate buttons logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Device successfully initialized") return d } +// activateButtons will activate mouse buttons func (d *Device) activateButtons() { _, err := d.transfer(cmdOpenWriteEndpoint, nil, "activateButtons") if err != nil { @@ -383,6 +397,88 @@ func (d *Device) saveRgbProfile() { } } +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + d.dev = dev + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.getDeviceFirmware() // Firmware + d.toggleExit() // Remove Exit flag + d.setDeviceColor() // Device color + d.controlListener() // Control listener + d.toggleDPI() // Set current DPI + d.activateButtons() // Activate buttons +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err := d.transfer(cmdSetPollingRate, buf, "UpdatePollingRate") + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + time.Sleep(5000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + // UpdateRgbProfileData will update RGB profile data func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { if d.GetRgbProfile(profileName) == nil { @@ -805,6 +901,7 @@ func (d *Device) saveDeviceProfile() { } deviceProfile.Profile = 1 deviceProfile.SleepMode = 15 + deviceProfile.PollingRate = 4 } else { if d.DeviceProfile.BrightnessSlider == nil { deviceProfile.BrightnessSlider = &defaultBrightness @@ -821,6 +918,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profile = d.DeviceProfile.Profile deviceProfile.ZoneColors = d.DeviceProfile.ZoneColors deviceProfile.SleepMode = d.DeviceProfile.SleepMode + deviceProfile.PollingRate = d.DeviceProfile.PollingRate if len(d.DeviceProfile.Path) < 1 { deviceProfile.Path = profilePath diff --git a/src/devices/m75/m75.go b/src/devices/m75/m75.go index d56c595..0b79470 100644 --- a/src/devices/m75/m75.go +++ b/src/devices/m75/m75.go @@ -41,6 +41,7 @@ type DeviceProfile struct { OriginalBrightness uint8 Label string Profile int + PollingRate int DPIColor *rgb.Color ZoneColors map[int]ZoneColors Profiles map[int]DPIProfile @@ -72,6 +73,7 @@ type Device struct { VendorId uint16 ProductId uint16 Brightness map[int]string + PollingRates map[int]string LEDChannels int ChangeableLedChannels int CpuTemp float32 @@ -98,6 +100,7 @@ var ( cmdSetDpi = map[int][]byte{0: {0x01, 0x21, 0x00}, 1: {0x01, 0x22, 0x00}} cmdHeartbeat = []byte{0x12} cmdSleep = map[int][]byte{0: {0x01, 0x37, 0x00}, 1: {0x01, 0x0e, 0x00}} + cmdSetPollingRate = []byte{0x01, 0x01, 0x00} bufferSize = 64 bufferSizeWrite = bufferSize + 1 headerSize = 2 @@ -146,6 +149,16 @@ func Init(vendorId, productId uint16, key string) *Device { timerKeepAlive: &time.Ticker{}, autoRefreshChan: make(chan struct{}), timer: &time.Ticker{}, + PollingRates: map[int]string{ + 0: "Not Set", + 1: "125 Hz / 8 msec", + 2: "250 Hu / 4 msec", + 3: "500 Hz / 2 msec", + 4: "1000 Hz / 1 msec", + 5: "2000 Hz / 0.5 msec", + 6: "4000 Hz / 0.25 msec", + 7: "8000 Hz / 0.125 msec", + }, } d.getDebugMode() // Debug mode @@ -354,6 +367,87 @@ func (d *Device) saveRgbProfile() { } } +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + d.dev = dev + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.getDeviceFirmware() // Firmware + d.toggleExit() // Remove Exit flag + d.setDeviceColor(false) // Device color + d.controlListener() // Control listener + d.toggleDPI(false) // Set current DPI +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err := d.transfer(cmdSetPollingRate, buf) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + time.Sleep(5000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + // UpdateRgbProfileData will update RGB profile data func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { if d.GetRgbProfile(profileName) == nil { @@ -788,6 +882,7 @@ func (d *Device) saveDeviceProfile() { } deviceProfile.Profile = 1 deviceProfile.SleepMode = 15 + deviceProfile.PollingRate = 4 } else { if d.DeviceProfile.BrightnessSlider == nil { deviceProfile.BrightnessSlider = &defaultBrightness @@ -805,6 +900,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.DPIColor = d.DeviceProfile.DPIColor deviceProfile.ZoneColors = d.DeviceProfile.ZoneColors deviceProfile.SleepMode = d.DeviceProfile.SleepMode + deviceProfile.PollingRate = d.DeviceProfile.PollingRate if len(d.DeviceProfile.Path) < 1 { deviceProfile.Path = profilePath diff --git a/src/devices/nightsabreW/nightsabreW.go b/src/devices/nightsabreW/nightsabreW.go index 483e51e..d38da9d 100644 --- a/src/devices/nightsabreW/nightsabreW.go +++ b/src/devices/nightsabreW/nightsabreW.go @@ -1064,9 +1064,6 @@ func (d *Device) setDeviceColor() { // DPI dpiColor := d.DeviceProfile.DPIColor - dpiColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) - dpiColor = rgb.ModifyBrightness(*dpiColor) - dpiLeds := d.DeviceProfile.Profiles[d.DeviceProfile.Profile] for i := 0; i < len(dpiLeds.ColorIndex); i++ { dpiColorIndexRange := dpiLeds.ColorIndex[i] diff --git a/src/devices/nightsabreWU/nightsabreWU.go b/src/devices/nightsabreWU/nightsabreWU.go index f3fceba..5aa7157 100644 --- a/src/devices/nightsabreWU/nightsabreWU.go +++ b/src/devices/nightsabreWU/nightsabreWU.go @@ -1043,9 +1043,6 @@ func (d *Device) setDeviceColor() { // DPI dpiColor := d.DeviceProfile.DPIColor - dpiColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider) - dpiColor = rgb.ModifyBrightness(*dpiColor) - dpiLeds := d.DeviceProfile.Profiles[d.DeviceProfile.Profile] for i := 0; i < len(dpiLeds.ColorIndex); i++ { dpiColorIndexRange := dpiLeds.ColorIndex[i] diff --git a/src/devices/scimitar/scimitar.go b/src/devices/scimitar/scimitar.go index e2bcc7f..450acfd 100644 --- a/src/devices/scimitar/scimitar.go +++ b/src/devices/scimitar/scimitar.go @@ -41,6 +41,7 @@ type DeviceProfile struct { OriginalBrightness uint8 Label string Profile int + PollingRate int ZoneColors map[int]ZoneColors Profiles map[int]DPIProfile SleepMode int @@ -71,6 +72,7 @@ type Device struct { VendorId uint16 ProductId uint16 Brightness map[int]string + PollingRates map[int]string LEDChannels int ChangeableLedChannels int CpuTemp float32 @@ -98,13 +100,14 @@ var ( 0: {0x01, 0x21, 0x00}, 1: {0x01, 0x22, 0x00}, } - bufferSize = 128 - bufferSizeWrite = bufferSize + 1 - headerSize = 2 - headerWriteSize = 4 - minDpiValue = 100 - maxDpiValue = 18000 - deviceKeepAlive = 20000 + cmdSetPollingRate = []byte{0x01, 0x01, 0x00} + bufferSize = 128 + bufferSizeWrite = bufferSize + 1 + headerSize = 2 + headerWriteSize = 4 + minDpiValue = 100 + maxDpiValue = 18000 + deviceKeepAlive = 20000 ) func Init(vendorId, productId uint16, key string) *Device { @@ -145,6 +148,13 @@ func Init(vendorId, productId uint16, key string) *Device { timerKeepAlive: &time.Ticker{}, autoRefreshChan: make(chan struct{}), timer: &time.Ticker{}, + PollingRates: map[int]string{ + 0: "Not Set", + 1: "125 Hz / 8 msec", + 2: "250 Hu / 4 msec", + 3: "500 Hz / 2 msec", + 4: "1000 Hz / 1 msec", + }, } d.getDebugMode() // Debug mode @@ -349,6 +359,88 @@ func (d *Device) saveRgbProfile() { } } +// Close will close all timers and channels before restart +func (d *Device) Close() { + d.Exit = true + logger.Log(logger.Fields{"serial": d.Serial, "product": d.Product}).Info("Stopping device...") + if d.activeRgb != nil { + d.activeRgb.Exit <- true // Exit current RGB mode + d.activeRgb = nil + } + time.Sleep(500 * time.Millisecond) +} + +// toggleExit will change Exit value +func (d *Device) toggleExit() { + if d.Exit { + d.Exit = false + } +} + +// Restart will re-init device +func (d *Device) Restart() { + if d.dev != nil { + err := d.dev.Close() + if err != nil { + logger.Log(logger.Fields{"error": err}).Error("Unable to close HID device") + } + } + d.dev = nil + + interfaceId := 1 + path := "" + enum := hid.EnumFunc(func(info *hid.DeviceInfo) error { + if info.InterfaceNbr == interfaceId { + path = info.Path + } + return nil + }) + err := hid.Enumerate(d.VendorId, d.ProductId, enum) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Fatal("Unable to enumerate devices") + } + + dev, err := hid.OpenPath(path) + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId, "productId": d.ProductId, "caller": "Restart()"}).Error("Unable to open HID device") + return + } + + d.dev = dev + d.setSoftwareMode() // Activate software mode + d.initLeds() // Init LED ports + d.getDeviceFirmware() // Firmware + d.toggleExit() // Remove Exit flag + d.setDeviceColor() // Device color + d.controlListener() // Control listener + d.toggleDPI() // Set current DPI +} + +// UpdatePollingRate will set device polling rate +func (d *Device) UpdatePollingRate(pullingRate int) uint8 { + if _, ok := d.PollingRates[pullingRate]; ok { + if d.DeviceProfile == nil { + return 0 + } + + d.DeviceProfile.PollingRate = pullingRate + d.saveDeviceProfile() + + d.Close() + buf := make([]byte, 1) + buf[0] = byte(pullingRate) + _, err := d.transfer(cmdSetPollingRate, buf, "UpdatePollingRate") + if err != nil { + logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set mouse polling rate") + return 0 + } + time.Sleep(5000 * time.Millisecond) + d.Restart() + return 1 + } + return 0 +} + // UpdateRgbProfileData will update RGB profile data func (d *Device) UpdateRgbProfileData(profileName string, profile rgb.Profile) uint8 { if d.GetRgbProfile(profileName) == nil { @@ -793,6 +885,7 @@ func (d *Device) saveDeviceProfile() { } deviceProfile.Profile = 1 deviceProfile.SleepMode = 15 + deviceProfile.PollingRate = 4 } else { if d.DeviceProfile.BrightnessSlider == nil { deviceProfile.BrightnessSlider = &defaultBrightness @@ -809,6 +902,7 @@ func (d *Device) saveDeviceProfile() { deviceProfile.Profile = d.DeviceProfile.Profile deviceProfile.ZoneColors = d.DeviceProfile.ZoneColors deviceProfile.SleepMode = d.DeviceProfile.SleepMode + deviceProfile.PollingRate = d.DeviceProfile.PollingRate if len(d.DeviceProfile.Path) < 1 { deviceProfile.Path = profilePath diff --git a/src/devices/scimitarW/scimitarW.go b/src/devices/scimitarW/scimitarW.go index 3ec526d..ed1a6e8 100644 --- a/src/devices/scimitarW/scimitarW.go +++ b/src/devices/scimitarW/scimitarW.go @@ -662,11 +662,11 @@ func (d *Device) saveDeviceProfile() { 1: { // Logo ColorIndex: []int{0, 4, 8}, Color: &rgb.Color{ - Red: 0, + Red: 255, Green: 255, - Blue: 255, + Blue: 0, Brightness: 1, - Hex: fmt.Sprintf("#%02x%02x%02x", 0, 255, 255), + Hex: fmt.Sprintf("#%02x%02x%02x", 255, 255, 0), }, Name: "Logo", }, diff --git a/src/devices/slipstream/slipstream.go b/src/devices/slipstream/slipstream.go index 69ed32b..14260c3 100644 --- a/src/devices/slipstream/slipstream.go +++ b/src/devices/slipstream/slipstream.go @@ -8,6 +8,7 @@ import ( "OpenLinkHub/src/devices/harpoonW" "OpenLinkHub/src/devices/ironclawW" "OpenLinkHub/src/devices/k100airW" + "OpenLinkHub/src/devices/k70coretklW" "OpenLinkHub/src/devices/m55W" "OpenLinkHub/src/devices/m75W" "OpenLinkHub/src/devices/nightsabreW" @@ -182,6 +183,11 @@ func (d *Device) Stop() { dev.StopInternal() } } + if dev, found := value.(*k70coretklW.Device); found { + if dev.Connected { + dev.StopInternal() + } + } } d.setHardwareMode() @@ -221,6 +227,7 @@ func (d *Device) getDevices() { if d.Debug { logger.Log(logger.Fields{"serial": d.Serial, "length": len(buff), "data": fmt.Sprintf("% 2x", buff)}).Info("DEBUG") } + channels := buff[5] data := buff[6:] position := 0 @@ -244,6 +251,7 @@ func (d *Device) getDevices() { VendorId: vendorId, ProductId: productId, } + devices[i] = device position += 8 + int(deviceIdLen) } @@ -421,6 +429,11 @@ func (d *Device) setDeviceOnlineByProductId(productId uint16) { device.Connect() } } + if device, found := dev.(*k70coretklW.Device); found { + if !device.Connected { + device.Connect() + } + } } } @@ -477,6 +490,11 @@ func (d *Device) setDevicesOffline() { device.SetConnected(false) } } + if device, found := pairedDevice.(*k70coretklW.Device); found { + if device.Connected { + device.SetConnected(false) + } + } } } @@ -492,6 +510,11 @@ func (d *Device) setDeviceTypeOffline(deviceType int) { device.SetConnected(false) } } + if device, found := pairedDevice.(*k70coretklW.Device); found { + if device.Connected { + device.SetConnected(false) + } + } } break case 1: @@ -560,6 +583,11 @@ func (d *Device) setDeviceOnline(deviceType int) { device.Connect() } } + if device, found := pairedDevice.(*k70coretklW.Device); found { + if !device.Connected { + device.Connect() + } + } } break case 1: @@ -665,6 +693,11 @@ func (d *Device) setDeviceOnline(deviceType int) { device.Connect() } } + if device, found := pairedDevice.(*k70coretklW.Device); found { + if !device.Connected { + device.Connect() + } + } } break } @@ -953,11 +986,19 @@ func (d *Device) controlListener() { } else if data[1] == 0x02 && data[2] == 0x20 { inputmanager.InputControl(inputmanager.Number1, d.Serial) // 1 } else if data[1] == 0x02 && data[2] == 0x40 { - inputmanager.InputControl(inputmanager.Number2, d.Serial) // 1 + inputmanager.InputControl(inputmanager.Number2, d.Serial) // 2 } else if data[1] == 0x02 && data[2] == 0x80 { - inputmanager.InputControl(inputmanager.Number3, d.Serial) // 1 + inputmanager.InputControl(inputmanager.Number3, d.Serial) // 3 } else if data[1] == 0x02 && data[3] == 0x01 { - inputmanager.InputControl(inputmanager.Number4, d.Serial) // 1 + inputmanager.InputControl(inputmanager.Number4, d.Serial) // 4 + } + } + + if dev, found := value.(*k70coretklW.Device); found { + if data[1] == 0x02 && data[2] == 0x04 { + dev.ControlDial(data) + } else if data[1] == 0x05 && (data[4] == 0x01 || data[4] == 0xff) { + dev.ControlDial(data) } } } diff --git a/src/inputmanager/inputmanager.go b/src/inputmanager/inputmanager.go index 87534b1..8eda348 100644 --- a/src/inputmanager/inputmanager.go +++ b/src/inputmanager/inputmanager.go @@ -39,28 +39,102 @@ const ( ) var ( - evKey uint16 = 0x01 - evSyn uint16 = 0x00 - keyVolumeUp uint16 = 0x73 - keyVolumeDown uint16 = 0x72 - keyVolumeMute uint16 = 0x71 - keyMediaStop uint16 = 0xA6 - keyMediaPrev uint16 = 0xA5 - keyMediaPlay uint16 = 0xA4 - keyMediaNext uint16 = 0xA3 - keyNumber1 uint16 = 0x2 - keyNumber2 uint16 = 0x3 - keyNumber3 uint16 = 0x4 - keyNumber4 uint16 = 0x5 - keyNumber5 uint16 = 0x6 - keyNumber6 uint16 = 0x7 - keyNumber7 uint16 = 0x8 - keyNumber8 uint16 = 0x9 - keyNumber9 uint16 = 0xA - keyNumber10 uint16 = 0xB - keyNumber11 uint16 = 0xC - keyNumber12 uint16 = 0xD - devicePath []string + evKey uint16 = 0x01 + evSyn uint16 = 0x00 + keyVolumeUp uint16 = 0x73 + keyVolumeDown uint16 = 0x72 + keyVolumeMute uint16 = 0x71 + keyMediaStop uint16 = 0xA6 + keyMediaPrev uint16 = 0xA5 + keyMediaPlay uint16 = 0xA4 + keyMediaNext uint16 = 0xA3 + keyNumber0 uint16 = 0xB + keyNumber1 uint16 = 0x2 + keyNumber2 uint16 = 0x3 + keyNumber3 uint16 = 0x4 + keyNumber4 uint16 = 0x5 + keyNumber5 uint16 = 0x6 + keyNumber6 uint16 = 0x7 + keyNumber7 uint16 = 0x8 + keyNumber8 uint16 = 0x9 + keyNumber9 uint16 = 0xA + keyNumber10 uint16 = 0xB + keyNumber11 uint16 = 0xC + keyNumber12 uint16 = 0xD + keyEsc uint16 = 0x1 + keyF1 uint16 = 0x3B + keyF2 uint16 = 0x3C + keyF3 uint16 = 0x3D + keyF4 uint16 = 0x3E + keyF5 uint16 = 0x3F + keyF6 uint16 = 0x40 + keyF7 uint16 = 0x41 + keyF8 uint16 = 0x42 + keyF9 uint16 = 0x43 + keyF10 uint16 = 0x44 + keyF11 uint16 = 0x57 + keyF12 uint16 = 0x58 + keyTilde uint16 = 0x29 + keyMinus uint16 = 0xC + keyEquals uint16 = 0xD + keyBack uint16 = 0xE + keyTab uint16 = 0xF + keyQ uint16 = 0x10 + keyW uint16 = 0x11 + keyE uint16 = 0x12 + keyR uint16 = 0x13 + keyT uint16 = 0x14 + keyY uint16 = 0x15 + keyU uint16 = 0x16 + keyI uint16 = 0x17 + keyO uint16 = 0x18 + keyP uint16 = 0x19 + keyLeftSquare uint16 = 0x1A + keyRightSquare uint16 = 0x1B + keyBackslash uint16 = 0x2B + keyCapslock uint16 = 0x3A + keyA uint16 = 0x1E + keyS uint16 = 0x1F + keyD uint16 = 0x20 + keyF uint16 = 0x21 + keyG uint16 = 0x22 + keyH uint16 = 0x23 + keyJ uint16 = 0x24 + keyK uint16 = 0x25 + keyL uint16 = 0x26 + keySemicolon uint16 = 0x27 + keySingleQuote uint16 = 0x28 + keyEnter uint16 = 0x1C + keyLeftShift uint16 = 0x2A + keyZ uint16 = 0x2C + keyX uint16 = 0x2D + keyC uint16 = 0x2E + keyV uint16 = 0x2F + keyB uint16 = 0x30 + keyN uint16 = 0x31 + keyM uint16 = 0x32 + keyComma uint16 = 0x33 + keyDot uint16 = 0x34 + keySlash uint16 = 0x35 + keyRightShift uint16 = 0x36 + keyUp uint16 = 0x67 + keyLeftCtrl uint16 = 0x1D + keyWindowsKey uint16 = 0x7D + keyLeftAlt uint16 = 0x38 + keySpace uint16 = 0x39 + keyRightAlt uint16 = 0x64 + keyMenu uint16 = 0x7F + keyRightCtrl uint16 = 0x61 + keyLeft uint16 = 0x69 + keyDown uint16 = 0x6C + keyRight uint16 = 0x6A + keyIns uint16 = 0x6E + keyHome uint16 = 0x66 + keyPgUp uint16 = 0x68 + keyDel uint16 = 0x6F + keyEnd uint16 = 0x6B + keyPgDn uint16 = 0x6D + devicePath []string ) type inputEvent struct { diff --git a/src/server/requests/requests.go b/src/server/requests/requests.go index ff71c6a..278de8e 100755 --- a/src/server/requests/requests.go +++ b/src/server/requests/requests.go @@ -47,6 +47,7 @@ type Payload struct { KeyboardLayout string `json:"keyboardLayout"` KeyboardControlDial int `json:"keyboardControlDial"` SleepMode int `json:"sleepMode"` + PollingRate int `json:"pollingRate"` MuteIndicator int `json:"muteIndicator"` RgbControl bool `json:"rgbControl"` RgbOff string `json:"rgbOff"` @@ -783,6 +784,46 @@ func ProcessChangeSleepMode(r *http.Request) *Payload { return &Payload{Message: "Unable to change device sleep mode", Code: http.StatusOK, Status: 0} } +// ProcessChangePollingRate will process POST request from a client for device polling rate change +func ProcessChangePollingRate(r *http.Request) *Payload { + req := &Payload{} + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + logger.Log(map[string]interface{}{"error": err}).Error("Unable to decode JSON") + return &Payload{ + Message: "Unable to validate your request. Please try again!", + Code: http.StatusOK, + Status: 0, + } + } + + if req.PollingRate < 1 || req.PollingRate > 10 { + return &Payload{Message: "Invalid polling rate option", Code: http.StatusOK, Status: 0} + } + + if len(req.DeviceId) < 0 { + return &Payload{Message: "Non-existing device", Code: http.StatusOK, Status: 0} + } + + if m, _ := regexp.MatchString("^[a-zA-Z0-9-]+$", req.DeviceId); !m { + return &Payload{Message: "Non-existing device", Code: http.StatusOK, Status: 0} + } + + if devices.GetDevice(req.DeviceId) == nil { + return &Payload{Message: "Non-existing device", Code: http.StatusOK, Status: 0} + } + + // Run it + status := devices.ChangeDevicePollingRate(req.DeviceId, req.PollingRate) + switch status { + case 1: + return &Payload{Message: "Device polling rate successfully changed", Code: http.StatusOK, Status: 1} + case 2: + return &Payload{Message: "Unable to change device polling rate. Please try again", Code: http.StatusOK, Status: 0} + } + return &Payload{Message: "Unable to change device polling rate", Code: http.StatusOK, Status: 0} +} + // ProcessChangeMuteIndicator will process POST request from a client for device mute indicator change func ProcessChangeMuteIndicator(r *http.Request) *Payload { req := &Payload{} diff --git a/src/server/server.go b/src/server/server.go index 0e1cb79..aa98de2 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -538,6 +538,17 @@ func changeSleepMode(w http.ResponseWriter, r *http.Request) { resp.Send(w) } +// changePollingRate handles device USB polling rate +func changePollingRate(w http.ResponseWriter, r *http.Request) { + request := requests.ProcessChangePollingRate(r) + resp := &Response{ + Code: request.Code, + Status: request.Status, + Message: request.Message, + } + resp.Send(w) +} + // changeMuteIndicator handles device mute indicator change func changeMuteIndicator(w http.ResponseWriter, r *http.Request) { request := requests.ProcessChangeMuteIndicator(r) @@ -945,6 +956,8 @@ func setRoutes() *mux.Router { HandlerFunc(changeControlDial) r.Methods(http.MethodPost).Path("/api/keyboard/sleep"). HandlerFunc(changeSleepMode) + r.Methods(http.MethodPost).Path("/api/keyboard/pollingRate"). + HandlerFunc(changePollingRate) r.Methods(http.MethodPost).Path("/api/scheduler/rgb"). HandlerFunc(changeRgbScheduler) r.Methods(http.MethodPost).Path("/api/psu/speed"). @@ -957,6 +970,8 @@ func setRoutes() *mux.Router { HandlerFunc(saveMouseDpiColors) r.Methods(http.MethodPost).Path("/api/mouse/sleep"). HandlerFunc(changeSleepMode) + r.Methods(http.MethodPost).Path("/api/mouse/pollingRate"). + HandlerFunc(changePollingRate) r.Methods(http.MethodPost).Path("/api/headset/zoneColors"). HandlerFunc(saveHeadsetZoneColors) r.Methods(http.MethodPost).Path("/api/headset/sleep"). diff --git a/src/templates/templates.go b/src/templates/templates.go index 0688fbd..859d24a 100644 --- a/src/templates/templates.go +++ b/src/templates/templates.go @@ -56,7 +56,11 @@ func Init() { "web/k65plus.html", "web/k65plusW.html", "web/k70core.html", + "web/k70coretkl.html", + "web/k70coretklWU.html", + "web/k70coretklW.html", "web/k70pro.html", + "web/k70protkl.html", "web/k70mk2.html", "web/k55core.html", "web/k100.html", diff --git a/static/css/custom.css b/static/css/custom.css index c81b7eb..d007452 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -554,4 +554,201 @@ footer { float: left; width: auto; margin-left: 20px; +} + +.keyboard-5 { + display: grid; + grid-template-rows: repeat(5, 60px); + grid-auto-rows: 50px; + grid-gap: 5px; + padding: 5px; + /*background-color: #181818; + border: 1px solid #535353;*/ + justify-content: center; + align-items: center; +} + +.keyboard-6 { + display: grid; + grid-template-rows: repeat(6, 60px); + grid-auto-rows: 50px; + grid-gap: 5px; + padding: 5px; + /*background-color: #181818; + border: 1px solid #535353;*/ + justify-content: center; + align-items: center; +} + +.keyboard-7 { + display: grid; + grid-template-rows: repeat(7, 60px); + grid-auto-rows: 50px; + grid-gap: 5px; + padding: 5px; + /*background-color: #181818; + border: 1px solid #535353;*/ + justify-content: center; + align-items: center; +} + +.keyboard-8 { + display: grid; + grid-template-rows: repeat(8, 60px); + grid-auto-rows: 50px; + grid-gap: 5px; + padding: 5px; + /*background-color: #181818; + border: 1px solid #535353;*/ + justify-content: center; + align-items: center; +} + +.keyboard-row-16 { + display: grid; + grid-template-columns: repeat(16, 60px); + grid-gap: 5px; +} + +.keyboard-row-17 { + display: grid; + grid-template-columns: repeat(17, 60px); + grid-gap: 5px; +} + +.keyboard-row-20 { + display: grid; + grid-template-columns: repeat(20, 60px); + grid-gap: 5px; +} + +.keyboard-row-24 { + display: grid; + grid-template-columns: repeat(24, 60px); + grid-gap: 5px; +} + +.keyboard-row-25 { + display: grid; + grid-template-columns: repeat(25, 60px); + grid-gap: 5px; +} + +.keyboard-key { + width: 60px; + height: 60px; + background-color: #1e1e1e; + border: 1px solid #474747; + border-radius: 5px; + display: flex; + justify-content: center; + align-items: center; + font-size: 14px; + cursor: pointer; + user-select: none; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + color: #bf8d19; +} + +.keyboard-key-125 { + width: 60px; + height: 125px; + background-color: #1e1e1e; + border: 1px solid #474747; + border-radius: 5px; + display: flex; + justify-content: center; + align-items: center; + font-size: 14px; + cursor: pointer; + user-select: none; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + color: #bf8d19; + position: relative; +} + +.keyboard-key-125:active { + background-color: #272727; +} + +.keyboard-key-125-125 { + width: 125px; + height: 125px; + background-color: #1e1e1e; + border: 1px solid #474747; + border-radius: 5px; + display: flex; + justify-content: center; + align-items: center; + font-size: 14px; + cursor: pointer; + user-select: none; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + color: #bf8d19; + position: relative; +} + +.keyboard-key-125-125:active { + background-color: #272727; +} + + +.keyboard-key-dot { + width: 60px; + height: 60px; + background-color: #1e1e1e; + border: 1px solid #474747; + display: flex; + justify-content: center; + align-items: center; + font-size: 14px; + cursor: pointer; + user-select: none; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + color: #bf8d19; + border-radius: 50%; +} + +.keyboard-key-empty { + width: 60px; + height: 60px; +} + +.keyboard-key:active { + background-color: #272727; +} + +.keyboard-key.wide { + grid-column: span 2; + width: auto; +} + +.keyboard-key.wide3 { + grid-column: span 3; + width: auto; +} + +.keyboard-key.wide4 { + grid-column: span 4; + width: auto; +} + +.keyboard-key.wide5 { + grid-column: span 5; + width: auto; +} + +.keyboard-key.wide6 { + grid-column: span 6; + width: auto; +} + +.keyboard-key.wide7 { + grid-column: span 7; + width: auto; +} + +.keyboard-key.wide8 { + grid-column: span 8; + width: auto; } \ No newline at end of file diff --git a/static/img/icons/icon-headphone-stand.svg b/static/img/icons/icon-headphone-stand.svg new file mode 100644 index 0000000..3bc9c20 --- /dev/null +++ b/static/img/icons/icon-headphone-stand.svg @@ -0,0 +1 @@ + diff --git a/static/img/icons/icon-speed.svg b/static/img/icons/icon-speed.svg new file mode 100644 index 0000000..2decda3 --- /dev/null +++ b/static/img/icons/icon-speed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/js/mouse.js b/static/js/mouse.js index db78c5c..c8efdf8 100644 --- a/static/js/mouse.js +++ b/static/js/mouse.js @@ -270,4 +270,32 @@ document.addEventListener("DOMContentLoaded", function () { } }); }); + + $('.mousePollingRate').on('change', function () { + const deviceId = $("#deviceId").val(); + const pf = {}; + pf["deviceId"] = deviceId; + pf["pollingRate"] = parseInt($(this).val()); + const json = JSON.stringify(pf, null, 2); + + $('.mousePollingRate').prop('disabled', true); + $.ajax({ + url: '/api/mouse/pollingRate', + type: 'POST', + data: json, + cache: false, + success: function(response) { + try { + if (response.status === 1) { + toast.success(response.message); + } else { + toast.warning(response.message); + } + } catch (err) { + toast.warning(response.message); + } + $('.mousePollingRate').prop('disabled', false); + } + }); + }); }); \ No newline at end of file diff --git a/static/js/overview.js b/static/js/overview.js index 18b301f..51851f0 100644 --- a/static/js/overview.js +++ b/static/js/overview.js @@ -1329,4 +1329,32 @@ document.addEventListener("DOMContentLoaded", function () { } }); }); + + $('.keyboardPollingRate').on('change', function () { + const deviceId = $("#deviceId").val(); + const pf = {}; + pf["deviceId"] = deviceId; + pf["pollingRate"] = parseInt($(this).val()); + const json = JSON.stringify(pf, null, 2); + + $('.keyboardPollingRate').prop('disabled', true); + $.ajax({ + url: '/api/keyboard/pollingRate', + type: 'POST', + data: json, + cache: false, + success: function(response) { + try { + if (response.status === 1) { + toast.success(response.message); + } else { + toast.warning(response.message); + } + } catch (err) { + toast.warning(response.message); + } + $('.keyboardPollingRate').prop('disabled', false); + } + }); + }); }); \ No newline at end of file diff --git a/web/darkcorergbproW.html b/web/darkcorergbproW.html index 01492e8..8415231 100644 --- a/web/darkcorergbproW.html +++ b/web/darkcorergbproW.html @@ -51,20 +51,13 @@ Firmware: {{ .Device.Firmware }}

- -
- - - - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Sleep

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -73,12 +66,22 @@ {{ end }} {{ end }} - - - - + + + + - {{ range $key, $sleepMode := $device.SleepModes }} {{ if eq $device.DeviceProfile.SleepMode $key }} @@ -115,14 +123,12 @@ {{ end }} {{ end }} - - -
+ + + +
diff --git a/web/darkcorergbproWU.html b/web/darkcorergbproWU.html index 4e38945..cdf8917 100644 --- a/web/darkcorergbproWU.html +++ b/web/darkcorergbproWU.html @@ -33,19 +33,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -54,12 +48,23 @@ {{ end }} {{ end }} - - - - + + + + + - -
+ + + +
diff --git a/web/darkcorergbproseW.html b/web/darkcorergbproseW.html index 01492e8..8415231 100644 --- a/web/darkcorergbproseW.html +++ b/web/darkcorergbproseW.html @@ -51,20 +51,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Sleep

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -73,12 +66,22 @@ {{ end }} {{ end }} - - - - + + + + - {{ range $key, $sleepMode := $device.SleepModes }} {{ if eq $device.DeviceProfile.SleepMode $key }} @@ -115,14 +123,12 @@ {{ end }} {{ end }} - - -
+ + + +
diff --git a/web/darkcorergbproseWU.html b/web/darkcorergbproseWU.html index 4e38945..031388b 100644 --- a/web/darkcorergbproseWU.html +++ b/web/darkcorergbproseWU.html @@ -33,19 +33,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -54,12 +48,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + +
diff --git a/web/darkstarW.html b/web/darkstarW.html index 4e7bf54..2be19a4 100644 --- a/web/darkstarW.html +++ b/web/darkstarW.html @@ -51,20 +51,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Sleep

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -73,12 +66,22 @@ {{ end }} {{ end }} - - - - + + + + - {{ range $key, $sleepMode := $device.SleepModes }} {{ if eq $device.DeviceProfile.SleepMode $key }} @@ -115,14 +123,12 @@ {{ end }} {{ end }} - - -
+ + + +
diff --git a/web/darkstarWU.html b/web/darkstarWU.html index 731b55e..75fff5d 100644 --- a/web/darkstarWU.html +++ b/web/darkstarWU.html @@ -33,19 +33,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -54,12 +48,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + +
diff --git a/web/harpoonW.html b/web/harpoonW.html index 75e7d67..6f38715 100644 --- a/web/harpoonW.html +++ b/web/harpoonW.html @@ -52,20 +52,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Sleep

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -74,12 +67,22 @@ {{ end }} {{ end }} - - - - + + + + - {{ range $key, $sleepMode := $device.SleepModes }} {{ if eq $device.DeviceProfile.SleepMode $key }} @@ -116,14 +124,12 @@ {{ end }} {{ end }} - - -
+ + + +
diff --git a/web/harpoonWU.html b/web/harpoonWU.html index 1a50f58..264ad4a 100644 --- a/web/harpoonWU.html +++ b/web/harpoonWU.html @@ -34,19 +34,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -55,12 +49,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + +
diff --git a/web/harpoonrgbpro.html b/web/harpoonrgbpro.html index abd692f..c9a8b08 100644 --- a/web/harpoonrgbpro.html +++ b/web/harpoonrgbpro.html @@ -34,19 +34,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -55,12 +49,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + + + +
diff --git a/web/ironclaw.html b/web/ironclaw.html index 673d68a..676c707 100644 --- a/web/ironclaw.html +++ b/web/ironclaw.html @@ -9,6 +9,7 @@ {{ $device := .Device }} {{ $rgb := .Rgb }} {{ $profile := $device.DeviceProfile.Profile }} + {{ $deviceProfile := .Device.DeviceProfile }}
@@ -32,19 +33,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -53,20 +48,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + + + + +
diff --git a/web/ironclawW.html b/web/ironclawW.html index 7087ed3..77724be 100644 --- a/web/ironclawW.html +++ b/web/ironclawW.html @@ -9,6 +9,7 @@ {{ $device := .Device }} {{ $rgb := .Rgb }} {{ $profile := $device.DeviceProfile.Profile }} + {{ $deviceProfile := .Device.DeviceProfile }}
@@ -50,20 +51,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Sleep

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -72,20 +66,22 @@ {{ end }} {{ end }} - - - - + + + + - {{ range $key, $sleepMode := $device.SleepModes }} {{ if eq $device.DeviceProfile.SleepMode $key }} @@ -122,14 +123,13 @@ {{ end }} {{ end }} - - -
+ + + + +
diff --git a/web/ironclawWU.html b/web/ironclawWU.html index 30a13f6..95523e7 100644 --- a/web/ironclawWU.html +++ b/web/ironclawWU.html @@ -9,6 +9,7 @@ {{ $device := .Device }} {{ $rgb := .Rgb }} {{ $profile := $device.DeviceProfile.Profile }} + {{ $deviceProfile := .Device.DeviceProfile }}
@@ -32,19 +33,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -53,20 +48,23 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + +
diff --git a/web/k100.html b/web/k100.html index 68fd0af..8813b78 100644 --- a/web/k100.html +++ b/web/k100.html @@ -32,20 +32,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - -

Layout

User Profile

RGB Profile

Control Dial

Save Profile

- {{ range $layout := $device.Layouts }} {{ if eq $device.DeviceProfile.Layout $layout }} @@ -54,9 +47,14 @@ {{ end }} {{ end }} - - {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -65,9 +63,14 @@ {{ end }} {{ end }} - - {{ range $key, $_ := $rgb }} {{ if eq $key "stand" }} {{ continue }} @@ -87,9 +90,14 @@ {{ end }} {{ end }} - - {{ range $key, $dialOptions := $device.ControlDialOptions }} {{ if eq $device.DeviceProfile.ControlDial $key }} @@ -98,14 +106,28 @@ {{ end }} {{ end }} - - -
+ + + + + +
{{ if eq "keyboard" $device.DeviceProfile.RGBProfile }}
diff --git a/web/k100air.html b/web/k100air.html index 16c80f2..eeffda9 100644 --- a/web/k100air.html +++ b/web/k100air.html @@ -32,19 +32,13 @@ Firmware: {{ .Device.Firmware }}

- -
- - - - - - - - - - - - - - -

Layout

User Profile

RGB Profile

Save Profile

- {{ range $layout := $device.Layouts }} {{ if eq $device.DeviceProfile.Layout $layout }} @@ -53,9 +47,14 @@ {{ end }} {{ end }} - - {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -64,9 +63,14 @@ {{ end }} {{ end }} - - {{ range $key, $_ := $rgb }} {{ if eq $key "stand" }} {{ continue }} @@ -86,83 +90,156 @@ {{ end }} {{ end }} - - -
+ + + +
{{ if eq "keyboard" $device.DeviceProfile.RGBProfile }} -
+
{{ range $index, $keys := $keyboard.Row }} - {{ if eq $index 4 }} -
- {{ else if eq $index 6 }} -
- {{ else }} -
- {{ end }} - {{ range $index, $keys := .Keys }} -
-

- {{ if $keys.Svg }} - Icon - {{ else }} - {{ $keys.KeyName }} - {{ end }} -

-
- {{ end }} -
- {{ end }} -
-
-
-
-
- + {{ if eq $index 3 }} +
+ {{ else if eq $index 3 }} +
+ {{ else if eq $index 5 }} +
+ {{ else }} +
+ {{ end }} + {{ range $index, $keys := .Keys }} + {{ if eq $index 1 }} +
+
+ {{ else if eq $index 10 }} +
+ {{ else if eq $index 14 }} +
+ {{ else if eq $index 18 }} +
+ {{ else if eq $index 22 }} +
+ {{ else if eq $index 25 }} +
+ {{ else if eq $index 4 }} +
+
+
+ {{ else if eq $index 5 }} +
+ {{ else if eq $index 8 }} +
+ {{ else if eq $index 43 }} +
+ {{ else if eq $index 46 }} +
+ {{ else if eq $index 64 }} +
+ {{ else if eq $index 67 }} +
+ {{ else if eq $index 84 }} +
+
+
+
+
+ {{ else if eq $index 99 }} +
+
+ {{ else if eq $index 100 }} +
+
+ {{ else if eq $index 112 }} +
+ {{ else if eq $index 115 }} +
+ {{ end }} - - - + {{ if eq $index 4 }} +
+ {{ else if eq $index 70 }} +
+ {{ else if eq $index 42 }} +
+ {{ else if eq $index 50 }} +
+ {{ else if eq $index 63 }} +
+ {{ else if eq $index 71 }} +
+ {{ else if eq $index 83 }} +
+ {{ else if eq $index 87 }} +
+ {{ else if eq $index 98 }} +
+ {{ else if eq $index 103 }} +
+ {{ else if eq $index 104 }} +
+ {{ else if eq $index 106 }} +
+ {{ else if eq $index 107 }} +
+ {{ else if eq $index 115 }} +
+ {{ else }} +
+ {{ end }} -
-
- - - - -
+ {{ if $keys.Svg }} + Icon + {{ else }} + + {{ $keys.KeyName }} + + {{ end }}
{{ end }}
+ {{ end }} +
+
+ + + +
+
+ + + + +
+
+ {{ end }}
+
+
{{ template "footer" . }}
diff --git a/web/k100airW.html b/web/k100airW.html index c4349d9..1840b85 100644 --- a/web/k100airW.html +++ b/web/k100airW.html @@ -50,20 +50,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - -

Layout

User Profile

RGB Profile

Sleep

Save Profile

- {{ range $layout := $device.Layouts }} {{ if eq $device.DeviceProfile.Layout $layout }} @@ -72,9 +65,14 @@ {{ end }} {{ end }} - - {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -83,9 +81,14 @@ {{ end }} {{ end }} - - {{ range $key, $mode := $device.RGBModes }} {{ if eq $key $device.DeviceProfile.RGBProfile }} @@ -94,9 +97,14 @@ {{ end }} {{ end }} - - {{ range $key, $sleepMode := $device.SleepModes }} {{ if eq $device.DeviceProfile.SleepMode $key }} @@ -105,15 +113,151 @@ {{ end }} {{ end }} - - -
+ + + +
+
+ {{ if eq "keyboard" $device.DeviceProfile.RGBProfile }} +
+ {{ range $index, $keys := $keyboard.Row }} + {{ if eq $index 3 }} +
+ {{ else if eq $index 3 }} +
+ {{ else if eq $index 5 }} +
+ {{ else }} +
+ {{ end }} + + {{ range $index, $keys := .Keys }} + {{ if eq $index 1 }} +
+
+ {{ else if eq $index 10 }} +
+ {{ else if eq $index 14 }} +
+ {{ else if eq $index 18 }} +
+ {{ else if eq $index 22 }} +
+ {{ else if eq $index 25 }} +
+ {{ else if eq $index 4 }} +
+
+
+ {{ else if eq $index 5 }} +
+ {{ else if eq $index 8 }} +
+ {{ else if eq $index 43 }} +
+ {{ else if eq $index 46 }} +
+ {{ else if eq $index 64 }} +
+ {{ else if eq $index 67 }} +
+ {{ else if eq $index 84 }} +
+
+
+
+
+ {{ else if eq $index 99 }} +
+
+ {{ else if eq $index 100 }} +
+
+ {{ else if eq $index 112 }} +
+ {{ else if eq $index 115 }} +
+ {{ end }} + + {{ if eq $index 4 }} +
+ {{ else if eq $index 70 }} +
+ {{ else if eq $index 42 }} +
+ {{ else if eq $index 50 }} +
+ {{ else if eq $index 63 }} +
+ {{ else if eq $index 71 }} +
+ {{ else if eq $index 83 }} +
+ {{ else if eq $index 87 }} +
+ {{ else if eq $index 98 }} +
+ {{ else if eq $index 103 }} +
+ {{ else if eq $index 104 }} +
+ {{ else if eq $index 106 }} +
+ {{ else if eq $index 107 }} +
+ {{ else if eq $index 115 }} +
+ {{ else }} +
+ {{ end }} + + {{ if $keys.Svg }} + Icon + {{ else }} + + {{ $keys.KeyName }} + + {{ end }} +
+ {{ end }} +
+ {{ end }} +
+
+ + + +
+
+ + + + +
+
+ {{ end }} +
{{ end }} diff --git a/web/k55core.html b/web/k55core.html index 943a8d4..0b01cfb 100644 --- a/web/k55core.html +++ b/web/k55core.html @@ -33,20 +33,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - -

Layout

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $layout := $device.Layouts }} {{ if eq $device.DeviceProfile.Layout $layout }} @@ -55,9 +48,14 @@ {{ end }} {{ end }} - - {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -66,12 +64,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + + + + +
{{ if eq "keyboard" $device.DeviceProfile.RGBProfile }} -
+
{{ range $index, $keys := $keyboard.Row }} - {{ if eq $index 3 }}
- {{ else if eq $index 5 }}
+ {{ if eq $index 2 }} +
+ {{ else if eq $index 3 }} +
+ {{ else if eq $index 4 }} +
{{ else }} -
- {{ end }} +
+ {{ end }} {{ range $index, $keys := .Keys }} + {{ if eq $index 2 }} +
+ {{ else if eq $index 6 }} +
+ {{ else if eq $index 10 }} +
+ {{ else if eq $index 14 }} +
+ {{ else if eq $index 31 }} +
+ {{ else if eq $index 34 }} +
+ {{ else if eq $index 52 }} +
+ {{ else if eq $index 55 }} +
+ {{ else if eq $index 72 }} +
+
+
+
+
+ {{ else if eq $index 87 }} +
+
+ {{ else if eq $index 88 }} +
+
+ {{ else if eq $index 102 }} +
+ {{ else if eq $index 105 }} +
+ {{ end }} {{ $color := index $keyboard.Zones $keys.Zone }} -
-

{{ $keys.KeyName }}

-

Zone {{ $keys.Zone }}

+ + {{ if eq $index 30 }} +
+ {{ else if eq $index 38 }} +
+ {{ else if eq $index 51 }} +
+ {{ else if eq $index 59 }} +
+ {{ else if eq $index 71 }} +
+ {{ else if eq $index 75 }} +
+ {{ else if eq $index 86 }} +
+ {{ else if eq $index 92 }} +
+ {{ else if eq $index 94 }} +
+ {{ else if eq $index 95 }} +
+ {{ else if eq $index 96 }} +
+ {{ else if eq $index 97 }} +
+ {{ else if eq $index 105 }} +
+ {{ else if eq $index 101 }} +
+ {{ else if eq $index 58 }} +
+ {{ else if eq $index 91 }} +
+ {{ else }} +
+ {{ end }} + + {{ $keys.KeyName }}
Zone {{ $keys.Zone }} +
{{ end }}
{{ end }} -
-
+
+
+ + + +
+
+ + + +
-
-
- - - - - - - -
-
- - - -
{{ end }} diff --git a/web/k65plus.html b/web/k65plus.html index 7c7695f..2e91efa 100644 --- a/web/k65plus.html +++ b/web/k65plus.html @@ -32,21 +32,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - - - -

Layout

User Profile

Brightness

RGB Profile

Control Dial

Save Profile

- {{ range $layout := $device.Layouts }} {{ if eq $device.DeviceProfile.Layout $layout }} @@ -55,9 +47,14 @@ {{ end }} {{ end }} - - {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -66,19 +63,13 @@ {{ end }} {{ end }} - - - + + + + - {{ range $key, $dialOptions := $device.ControlDialOptions }} {{ if eq $device.DeviceProfile.ControlDial $key }} @@ -110,64 +106,96 @@ {{ end }} {{ end }} - - -
+ + + + +
{{ if eq "keyboard" $device.DeviceProfile.RGBProfile }} -
- {{ range $keyboard.Row }} -
+
+ {{ range $index, $keys := $keyboard.Row }} +
{{ range $index, $keys := .Keys }} -
-

{{ $keys.KeyName }}

-
+ {{ if eq $index 2 }} +
+ {{ else if eq $index 6 }} +
+ {{ else if eq $index 10 }} +
{{ end }} -
- {{ end }} -
-
-
-
-
- - - - - + {{ if eq $index 28 }} +
+ {{ else if eq $index 30 }} +
+ {{ else if eq $index 30 }} +
+ {{ else if eq $index 43 }} +
+ {{ else if eq $index 45 }} +
+ {{ else if eq $index 57 }} +
+ {{ else if eq $index 59 }} +
+ {{ else if eq $index 70 }} +
+ {{ else if eq $index 72 }} +
+ {{ else if eq $index 74 }} +
+ {{ else if eq $index 75 }} +
+ {{ else }} +
+ {{ end }} -
-
- - - - +
+ {{ end }} +
+ {{ end }} +
+
+ + + +
+
+ + + + +
{{ end }} diff --git a/web/k65plusW.html b/web/k65plusW.html index be33b7e..9b473cf 100644 --- a/web/k65plusW.html +++ b/web/k65plusW.html @@ -51,21 +51,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - - - -

Layout

User Profile

RGB Profile

Control Dial

Sleep

Save Profile

- {{ range $layout := $device.Layouts }} {{ if eq $device.DeviceProfile.Layout $layout }} @@ -74,9 +66,14 @@ {{ end }} {{ end }} - - {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -85,9 +82,14 @@ {{ end }} {{ end }} - - {{ range $key, $mode := $device.RGBModes }} {{ if eq $key $device.DeviceProfile.RGBProfile }} @@ -96,9 +98,14 @@ {{ end }} {{ end }} - - {{ range $key, $dialOptions := $device.ControlDialOptions }} {{ if eq $device.DeviceProfile.ControlDial $key }} @@ -107,9 +114,15 @@ {{ end }} {{ end }} - - {{ range $key, $sleepMode := $device.SleepModes }} {{ if eq $device.DeviceProfile.SleepMode $key }} @@ -118,62 +131,95 @@ {{ end }} {{ end }} - - -
+ + + + +
+
{{ if eq "keyboard" $device.DeviceProfile.RGBProfile }} -
- {{ range $keyboard.Row }} -
+
+ {{ range $index, $keys := $keyboard.Row }} +
{{ range $index, $keys := .Keys }} -
-

{{ $keys.KeyName }}

-
+ {{ if eq $index 2 }} +
+ {{ else if eq $index 6 }} +
+ {{ else if eq $index 10 }} +
{{ end }} -
- {{ end }} -
-
-
-
-
- - - - - + {{ if eq $index 28 }} +
+ {{ else if eq $index 30 }} +
+ {{ else if eq $index 30 }} +
+ {{ else if eq $index 43 }} +
+ {{ else if eq $index 45 }} +
+ {{ else if eq $index 57 }} +
+ {{ else if eq $index 59 }} +
+ {{ else if eq $index 70 }} +
+ {{ else if eq $index 72 }} +
+ {{ else if eq $index 74 }} +
+ {{ else if eq $index 75 }} +
+ {{ else }} +
+ {{ end }} -
-
- - - - +
+ {{ end }} +
+ {{ end }} +
+
+ + + +
+
+ + + + +
{{ end }} diff --git a/web/k65pm.html b/web/k65pm.html index 651200c..d3e9e16 100644 --- a/web/k65pm.html +++ b/web/k65pm.html @@ -27,26 +27,21 @@
Device
+
{{ .Device.Product }}

Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - -

Layout

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $layout := $device.Layouts }} {{ if eq $device.DeviceProfile.Layout $layout }} @@ -55,9 +50,15 @@ {{ end }} {{ end }} - - {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -66,12 +67,24 @@ {{ end }} {{ end }} - - - - + + + + + - -
+ + + + + + + +
{{ if eq "keyboard" $device.DeviceProfile.RGBProfile }} -
- {{ range $keyboard.Row }} -
+
+ {{ range $index, $keys := $keyboard.Row }} +
{{ range $index, $keys := .Keys }} -
-

{{ $keys.KeyName }}

+ {{ if eq $index 65 }} +
+ {{ end }} + + + {{ if eq $index 14 }} +
+ {{ else if eq $index 16 }} +
+ {{ else if eq $index 31 }} +
+ {{ else if eq $index 29 }} +
+ {{ else if eq $index 43 }} +
+ {{ else if eq $index 45 }} +
+ {{ else if eq $index 56 }} +
+ {{ else if eq $index 59 }} +
+ {{ else if eq $index 61 }} +
+ {{ else if eq $index 62 }} +
+ {{ else }} +
+ {{ end }} + + {{ if $keys.Svg }} + Icon + {{ else }} + + {{ $keys.KeyName }} + + {{ end }}
{{ end }}
{{ end }} -
-
-
-
-
+
+
- - - - -
-
- - - - +
+
+ + + + +
{{ end }} diff --git a/web/k70core.html b/web/k70core.html index a7548d2..fb9d62d 100644 --- a/web/k70core.html +++ b/web/k70core.html @@ -32,20 +32,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - -

Layout

User Profile

RGB Profile

Control Dial

Save Profile

- {{ range $layout := $device.Layouts }} {{ if eq $device.DeviceProfile.Layout $layout }} @@ -54,9 +47,14 @@ {{ end }} {{ end }} - - {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -65,9 +63,14 @@ {{ end }} {{ end }} - - {{ range $key, $_ := $rgb }} {{ if eq $key "stand" }} {{ continue }} @@ -87,9 +90,14 @@ {{ end }} {{ end }} - - {{ range $key, $dialOptions := $device.ControlDialOptions }} {{ if eq $device.DeviceProfile.ControlDial $key }} @@ -98,70 +106,149 @@ {{ end }} {{ end }} - - -
+ + + + + +
{{ if eq "keyboard" $device.DeviceProfile.RGBProfile }} -
+
{{ range $index, $keys := $keyboard.Row }} - {{ if eq $index 3 }} -
- {{ else if eq $index 5 }} -
+ {{ if eq $index 2 }} +
+ {{ else if eq $index 3 }} +
+ {{ else if eq $index 4 }} +
{{ else }} -
+
{{ end }} {{ range $index, $keys := .Keys }} -
-

{{ $keys.KeyName }}

-
+ {{ if eq $index 2 }} +
+ {{ else if eq $index 6 }} +
+ {{ else if eq $index 10 }} +
+ {{ else if eq $index 14 }} +
+ {{ else if eq $index 31 }} +
+ {{ else if eq $index 34 }} +
+ {{ else if eq $index 52 }} +
+ {{ else if eq $index 55 }} +
+ {{ else if eq $index 72 }} +
+
+
+
+
+ {{ else if eq $index 87 }} +
+
+ {{ else if eq $index 88 }} +
+
+ {{ else if eq $index 100 }} +
+ {{ else if eq $index 103 }} +
{{ end }} -
- {{ end }} -
-
-
-
-
- - - - - + {{ if eq $index 30 }} +
+ {{ else if eq $index 38 }} +
+ {{ else if eq $index 51 }} +
+ {{ else if eq $index 59 }} +
+ {{ else if eq $index 71 }} +
+ {{ else if eq $index 75 }} +
+ {{ else if eq $index 86 }} +
+ {{ else if eq $index 92 }} +
+ {{ else if eq $index 94 }} +
+ {{ else if eq $index 95 }} +
+ {{ else if eq $index 103 }} +
+ {{ else if eq $index 58 }} +
+ {{ else if eq $index 91 }} +
+ {{ else }} +
+ {{ end }} -
-
- - - - +
+ {{ end }} +
+ {{ end }} +
+
+ + + +
+
+ + + + +
{{ end }} diff --git a/web/k70coretkl.html b/web/k70coretkl.html new file mode 100644 index 0000000..a5fa512 --- /dev/null +++ b/web/k70coretkl.html @@ -0,0 +1,241 @@ + + +{{ template "header" . }} + +
+
+ {{ $devs := .Devices }} + {{ $temperatures := .Temperatures }} + {{ $device := .Device }} + {{ $rgb := .Rgb }} + {{ $profile := $device.DeviceProfile.Profile }} + {{ $keyboard := index $device.DeviceProfile.Keyboards $profile }} + +
+
+ {{ template "navigation" . }} +
+
+ +
+
+
+
+
+
+
+ Device +
+
+ {{ .Device.Product }}
+

+ Firmware: {{ .Device.Firmware }} +

+
+
+ + + + + + + + + + + + + + +
+
+ {{ if eq "keyboard" $device.DeviceProfile.RGBProfile }} +
+ {{ range $index, $keys := $keyboard.Row }} +
+ {{ range $index, $keys := .Keys }} + {{ if eq $index 2 }} +
+ {{ else if eq $index 6 }} +
+ {{ else if eq $index 10 }} +
+ {{ else if eq $index 14 }} +
+ {{ else if eq $index 29 }} +
+ {{ else if eq $index 46 }} +
+ {{ else if eq $index 74 }} +
+
+ {{ else if eq $index 83 }} +
+ {{ end }} + + {{ if eq $index 28 }} +
+ {{ else if eq $index 32 }} +
+ {{ else if eq $index 45 }} +
+ {{ else if eq $index 61 }} +
+ {{ else if eq $index 49 }} +
+ {{ else if eq $index 62 }} +
+ {{ else if eq $index 73 }} +
+ {{ else if eq $index 78 }} +
+ {{ else if eq $index 81 }} +
+ {{ else if eq $index 82 }} +
+ {{ else }} +
+ {{ end }} + {{ if $keys.Svg }} + Icon + {{ else }} + + {{ $keys.KeyName }} + + {{ end }} +
+ {{ end }} +
+ {{ end }} +
+
+ + + +
+
+ + + + +
+
+
+ {{ end }} +
+
+
+
+
+ {{ template "footer" . }} +
+
+ + + + + + \ No newline at end of file diff --git a/web/k70coretklW.html b/web/k70coretklW.html new file mode 100644 index 0000000..374662e --- /dev/null +++ b/web/k70coretklW.html @@ -0,0 +1,244 @@ + + +{{ template "header" . }} + +
+
+ {{ $devs := .Devices }} + {{ $temperatures := .Temperatures }} + {{ $device := .Device }} + {{ $rgb := .Rgb }} + {{ $profile := $device.DeviceProfile.Profile }} + {{ $keyboard := index $device.DeviceProfile.Keyboards $profile }} + +
+
+ {{ template "navigation" . }} +
+
+ +
+
+
+
+ {{ if eq .Device.Connected false }} +
+
+
+ Device +
+
+ {{ .Device.Product }}
+

+ Firmware: {{ .Device.Firmware }} +

+
+
+ Device is not connected!
+
+
+
+ {{ else }} +
+
+
+ Device +
+
+ {{ .Device.Product }}
+

+ Firmware: {{ .Device.Firmware }} +

+
+
+ + + + + + + + + + + +
+
+ {{ if eq "keyboard" $device.DeviceProfile.RGBProfile }} +
+ {{ range $index, $keys := $keyboard.Row }} +
+ {{ range $index, $keys := .Keys }} + {{ if eq $index 2 }} +
+ {{ else if eq $index 6 }} +
+ {{ else if eq $index 10 }} +
+ {{ else if eq $index 14 }} +
+ {{ else if eq $index 30 }} +
+ {{ else if eq $index 47 }} +
+ {{ else if eq $index 75 }} +
+
+ {{ else if eq $index 84 }} +
+ {{ end }} + + {{ if eq $index 29 }} +
+ {{ else if eq $index 33 }} +
+ {{ else if eq $index 46 }} +
+ {{ else if eq $index 62 }} +
+ {{ else if eq $index 50 }} +
+ {{ else if eq $index 63 }} +
+ {{ else if eq $index 74 }} +
+ {{ else if eq $index 79 }} +
+ {{ else if eq $index 82 }} +
+ {{ else if eq $index 83 }} +
+ {{ else }} +
+ {{ end }} + {{ if $keys.Svg }} + Icon + {{ else }} + + {{ $keys.KeyName }} + + {{ end }} +
+ {{ end }} +
+ {{ end }} +
+
+ + + +
+
+ + + + +
+
+
+ {{ end }} +
+
+ {{ end }} +
+
+
+ {{ template "footer" . }} +
+
+ + + + + + \ No newline at end of file diff --git a/web/k70coretklWU.html b/web/k70coretklWU.html new file mode 100644 index 0000000..05ee7fb --- /dev/null +++ b/web/k70coretklWU.html @@ -0,0 +1,226 @@ + + +{{ template "header" . }} + +
+
+ {{ $devs := .Devices }} + {{ $temperatures := .Temperatures }} + {{ $device := .Device }} + {{ $rgb := .Rgb }} + {{ $profile := $device.DeviceProfile.Profile }} + {{ $keyboard := index $device.DeviceProfile.Keyboards $profile }} + +
+
+ {{ template "navigation" . }} +
+
+ +
+
+
+
+
+
+
+ Device +
+
+ {{ .Device.Product }}
+

+ Firmware: {{ .Device.Firmware }} +

+
+
+ + + + + + + + + +
+
+ {{ if eq "keyboard" $device.DeviceProfile.RGBProfile }} +
+ {{ range $index, $keys := $keyboard.Row }} +
+ {{ range $index, $keys := .Keys }} + {{ if eq $index 2 }} +
+ {{ else if eq $index 6 }} +
+ {{ else if eq $index 10 }} +
+ {{ else if eq $index 14 }} +
+ {{ else if eq $index 30 }} +
+ {{ else if eq $index 47 }} +
+ {{ else if eq $index 75 }} +
+
+ {{ else if eq $index 84 }} +
+ {{ end }} + + {{ if eq $index 29 }} +
+ {{ else if eq $index 33 }} +
+ {{ else if eq $index 46 }} +
+ {{ else if eq $index 62 }} +
+ {{ else if eq $index 50 }} +
+ {{ else if eq $index 63 }} +
+ {{ else if eq $index 74 }} +
+ {{ else if eq $index 79 }} +
+ {{ else if eq $index 82 }} +
+ {{ else if eq $index 83 }} +
+ {{ else }} +
+ {{ end }} + {{ if $keys.Svg }} + Icon + {{ else }} + + {{ $keys.KeyName }} + + {{ end }} +
+ {{ end }} +
+ {{ end }} +
+
+ + + + + + + +
+
+ + + + +
+
+
+ {{ end }} +
+
+
+
+
+ {{ template "footer" . }} +
+
+ + + + + + \ No newline at end of file diff --git a/web/k70mk2.html b/web/k70mk2.html index 9c8a991..4818986 100644 --- a/web/k70mk2.html +++ b/web/k70mk2.html @@ -32,19 +32,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

Layout

User Profile

RGB Profile

Save Profile

- {{ range $layout := $device.Layouts }} {{ if eq $device.DeviceProfile.Layout $layout }} @@ -53,9 +47,14 @@ {{ end }} {{ end }} - - {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -64,9 +63,14 @@ {{ end }} {{ end }} - - {{ range $key, $_ := $rgb }} {{ if eq $key "stand" }} {{ continue }} @@ -92,16 +96,181 @@ {{ end }} {{ end }} - - -
+ + + + + + +
{{ if eq "keyboard" $device.DeviceProfile.RGBProfile }} +
+ {{ range $index, $keys := $keyboard.Row }} + {{ if eq $index 3 }} +
+ {{ else if eq $index 3 }} +
+ {{ else if eq $index 5 }} +
+ {{ else }} +
+ {{ end }} + {{ range $index, $keys := .Keys }} + {{ if eq $index 4 }} +
+
+
+ {{ else if eq $index 1 }} +
+
+
+
+ {{ else if eq $index 8 }} +
+ {{ else if eq $index 6 }} +
+
+
+
+
+
+
+ {{ else if eq $index 12 }} +
+ {{ else if eq $index 16 }} +
+ {{ else if eq $index 20 }} +
+ {{ else if eq $index 23 }} +
+ {{ else if eq $index 41 }} +
+ {{ else if eq $index 44 }} +
+ {{ else if eq $index 62 }} +
+
+ {{ else if eq $index 65 }} +
+ {{ else if eq $index 82 }} +
+
+
+
+
+
+
+ {{ else if eq $index 98 }} +
+
+ {{ else if eq $index 99 }} +
+
+ {{ else if eq $index 111 }} +
+ {{ else if eq $index 114 }} +
+ {{ end }} + + {{ if eq $index 4 }} +
+ {{ else if eq $index 5 }} +
+ {{ else if eq $index 40 }} +
+ {{ else if eq $index 48 }} +
+ {{ else if eq $index 61 }} +
+ {{ else if eq $index 68 }} +
+ {{ else if eq $index 69 }} +
+ {{ else if eq $index 85 }} +
+ {{ else if eq $index 97 }} +
+ {{ else if eq $index 102 }} +
+ {{ else if eq $index 103 }} +
+ {{ else if eq $index 105 }} +
+ {{ else if eq $index 106 }} +
+ {{ else if eq $index 110 }} +
+ {{ else if eq $index 114 }} +
+ {{ else }} +
+ {{ end }} + + {{ if $keys.Svg }} + Icon + {{ else }} + + {{ $keys.KeyName }} + + {{ end }} +
+ {{ end }} +
+ {{ end }} +
+
+ + + +
+
+ + + + +
+
+
+ {{ end }} +
+
+
{{ template "footer" . }}
diff --git a/web/k70pro.html b/web/k70pro.html index 16c80f2..79a747d 100644 --- a/web/k70pro.html +++ b/web/k70pro.html @@ -32,19 +32,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

Layout

User Profile

RGB Profile

Save Profile

- {{ range $layout := $device.Layouts }} {{ if eq $device.DeviceProfile.Layout $layout }} @@ -53,9 +47,14 @@ {{ end }} {{ end }} - - {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -64,9 +63,14 @@ {{ end }} {{ end }} - - {{ range $key, $_ := $rgb }} {{ if eq $key "stand" }} {{ continue }} @@ -86,83 +90,181 @@ {{ end }} {{ end }} - - -
+ + + + + + +
{{ if eq "keyboard" $device.DeviceProfile.RGBProfile }} -
+
{{ range $index, $keys := $keyboard.Row }} - {{ if eq $index 4 }} -
- {{ else if eq $index 6 }} -
- {{ else }} -
- {{ end }} - {{ range $index, $keys := .Keys }} -
-

- {{ if $keys.Svg }} - Icon - {{ else }} - {{ $keys.KeyName }} - {{ end }} -

-
- {{ end }} -
- {{ end }} -
-
-
-
-
- - - - + {{ if eq $index 3 }} +
+ {{ else if eq $index 3 }} +
+ {{ else if eq $index 5 }} +
+ {{ else }} +
+ {{ end }} + {{ range $index, $keys := .Keys }} + {{ if eq $index 4 }} +
+
+
+
+
+ {{ else if eq $index 5 }} +
+
+
+
+
+
+
+ {{ else if eq $index 7 }} +
+ {{ else if eq $index 11 }} +
+ {{ else if eq $index 15 }} +
+ {{ else if eq $index 19 }} +
+ {{ else if eq $index 22 }} +
+ {{ else if eq $index 40 }} +
+ {{ else if eq $index 43 }} +
+ {{ else if eq $index 61 }} +
+ {{ else if eq $index 64 }} +
+ {{ else if eq $index 81 }} +
+
+
+
+
+ {{ else if eq $index 96 }} +
+
+ {{ else if eq $index 97 }} +
+
+ {{ else if eq $index 111 }} +
+ {{ else if eq $index 114 }} +
+ {{ end }} - + {{ if eq $index 4 }} +
+ {{ else if eq $index 39 }} +
+ {{ else if eq $index 60 }} +
+ {{ else if eq $index 47 }} +
+ {{ else if eq $index 68 }} +
+ {{ else if eq $index 80 }} +
+ {{ else if eq $index 84 }} +
+ {{ else if eq $index 95 }} +
+ {{ else if eq $index 67 }} +
+ {{ else if eq $index 100 }} +
+ {{ else if eq $index 101 }} +
+ {{ else if eq $index 103 }} +
+ {{ else if eq $index 104 }} +
+ {{ else if eq $index 105 }} +
+ {{ else if eq $index 106 }} +
+ {{ else if eq $index 110 }} +
+ {{ else if eq $index 114 }} +
+ {{ else }} +
+ {{ end }} -
-
- - - - -
+ {{ if $keys.Svg }} + Icon + {{ else }} + + {{ $keys.KeyName }} + + {{ end }}
{{ end }}
+ {{ end }} +
+
+ + + +
+
+ + + + +
+
+ {{ end }}
+
+
{{ template "footer" . }}
diff --git a/web/k70protkl.html b/web/k70protkl.html new file mode 100644 index 0000000..0f93b67 --- /dev/null +++ b/web/k70protkl.html @@ -0,0 +1,241 @@ + + +{{ template "header" . }} + +
+
+ {{ $devs := .Devices }} + {{ $temperatures := .Temperatures }} + {{ $device := .Device }} + {{ $rgb := .Rgb }} + {{ $profile := $device.DeviceProfile.Profile }} + {{ $keyboard := index $device.DeviceProfile.Keyboards $profile }} + +
+
+ {{ template "navigation" . }} +
+
+ +
+
+
+
+
+
+
+ Device +
+
+ {{ .Device.Product }}
+

+ Firmware: {{ .Device.Firmware }} +

+
+
+ + + + + + + + + + + + + + +
+
+ {{ if eq "keyboard" $device.DeviceProfile.RGBProfile }} +
+ {{ range $index, $keys := $keyboard.Row }} +
+ {{ range $index, $keys := .Keys }} + {{ if eq $index 2 }} +
+ {{ else if eq $index 6 }} +
+ {{ else if eq $index 10 }} +
+ {{ else if eq $index 14 }} +
+ {{ else if eq $index 30 }} +
+ {{ else if eq $index 47 }} +
+ {{ else if eq $index 75 }} +
+
+ {{ else if eq $index 84 }} +
+ {{ end }} + + {{ if eq $index 29 }} +
+ {{ else if eq $index 33 }} +
+ {{ else if eq $index 46 }} +
+ {{ else if eq $index 62 }} +
+ {{ else if eq $index 50 }} +
+ {{ else if eq $index 63 }} +
+ {{ else if eq $index 74 }} +
+ {{ else if eq $index 79 }} +
+ {{ else if eq $index 82 }} +
+ {{ else if eq $index 83 }} +
+ {{ else }} +
+ {{ end }} + {{ if $keys.Svg }} + Icon + {{ else }} + + {{ $keys.KeyName }} + + {{ end }} +
+ {{ end }} +
+ {{ end }} +
+
+ + + +
+
+ + + + +
+
+
+ {{ end }} +
+
+
+
+
+ {{ template "footer" . }} +
+
+ + + + + + \ No newline at end of file diff --git a/web/katarpro.html b/web/katarpro.html index a7d16cd..2983f82 100644 --- a/web/katarpro.html +++ b/web/katarpro.html @@ -35,19 +35,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -56,12 +50,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + + + +
diff --git a/web/katarproW.html b/web/katarproW.html index d0a8224..e47f936 100644 --- a/web/katarproW.html +++ b/web/katarproW.html @@ -52,18 +52,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - -

User Profile

Sleep

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -72,9 +67,14 @@ {{ end }} {{ end }} - - {{ range $key, $sleepMode := $device.SleepModes }} {{ if eq $device.DeviceProfile.SleepMode $key }} @@ -83,14 +83,31 @@ {{ end }} {{ end }} - - -
+ + + + + +
diff --git a/web/katarproxt.html b/web/katarproxt.html index a34bda5..8435940 100644 --- a/web/katarproxt.html +++ b/web/katarproxt.html @@ -34,19 +34,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -55,12 +49,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + +
diff --git a/web/m55.html b/web/m55.html index 9cfe995..afb6fcf 100644 --- a/web/m55.html +++ b/web/m55.html @@ -34,17 +34,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - -

User Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -53,14 +49,30 @@ {{ end }} {{ end }} - - -
+ + + + + + + +
diff --git a/web/m55W.html b/web/m55W.html index 734c9df..957e0d8 100644 --- a/web/m55W.html +++ b/web/m55W.html @@ -52,18 +52,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - -

User Profile

Sleep

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -72,9 +67,14 @@ {{ end }} {{ end }} - - {{ range $key, $sleepMode := $device.SleepModes }} {{ if eq $device.DeviceProfile.SleepMode $key }} @@ -83,14 +83,13 @@ {{ end }} {{ end }} - - -
+ + + + +
diff --git a/web/m55rgbpro.html b/web/m55rgbpro.html index e31062b..c30ef70 100644 --- a/web/m55rgbpro.html +++ b/web/m55rgbpro.html @@ -34,19 +34,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -55,12 +49,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + + + + +
diff --git a/web/m65rgbultra.html b/web/m65rgbultra.html index 37fbcfd..4e3b972 100644 --- a/web/m65rgbultra.html +++ b/web/m65rgbultra.html @@ -34,19 +34,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -55,12 +49,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + + + + +
diff --git a/web/m75.html b/web/m75.html index 4308f68..d7c11d6 100644 --- a/web/m75.html +++ b/web/m75.html @@ -34,19 +34,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -55,12 +49,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + + + + +
diff --git a/web/m75W.html b/web/m75W.html index 7a8524f..d23cf7f 100644 --- a/web/m75W.html +++ b/web/m75W.html @@ -52,18 +52,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - -

User Profile

Sleep

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -72,9 +67,14 @@ {{ end }} {{ end }} - - {{ range $key, $sleepMode := $device.SleepModes }} {{ if eq $device.DeviceProfile.SleepMode $key }} @@ -83,14 +83,12 @@ {{ end }} {{ end }} - - -
+ + + +
diff --git a/web/m75WU.html b/web/m75WU.html index 171a64d..bf85752 100644 --- a/web/m75WU.html +++ b/web/m75WU.html @@ -34,17 +34,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - -

User Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -53,14 +49,12 @@ {{ end }} {{ end }} - - -
+ + + +
diff --git a/web/mm700.html b/web/mm700.html index b91a8fb..5bc9dfb 100644 --- a/web/mm700.html +++ b/web/mm700.html @@ -32,19 +32,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -53,12 +47,23 @@ {{ end }} {{ end }} - - - - + + + + + - -
+ + + +
{{ if eq "mousepad" $device.DeviceProfile.RGBProfile }}
diff --git a/web/nightsabreW.html b/web/nightsabreW.html index f9cd65f..8821a53 100644 --- a/web/nightsabreW.html +++ b/web/nightsabreW.html @@ -51,20 +51,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Sleep

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -73,12 +66,23 @@ {{ end }} {{ end }} - - - - + + + + + - {{ range $key, $sleepMode := $device.SleepModes }} {{ if eq $device.DeviceProfile.SleepMode $key }} @@ -115,14 +124,12 @@ {{ end }} {{ end }} - - -
+ + + +
diff --git a/web/nightsabreWU.html b/web/nightsabreWU.html index f7ffd1b..6c5d2ee 100644 --- a/web/nightsabreWU.html +++ b/web/nightsabreWU.html @@ -33,19 +33,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -54,12 +48,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + +
diff --git a/web/scimitar.html b/web/scimitar.html index d2582aa..c1b1e60 100644 --- a/web/scimitar.html +++ b/web/scimitar.html @@ -34,19 +34,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -55,12 +49,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + + + +
diff --git a/web/scimitarW.html b/web/scimitarW.html index b0dde6a..3a7d6ed 100644 --- a/web/scimitarW.html +++ b/web/scimitarW.html @@ -52,20 +52,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Sleep

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -74,12 +67,22 @@ {{ end }} {{ end }} - - - - + + + + - {{ range $key, $sleepMode := $device.SleepModes }} {{ if eq $device.DeviceProfile.SleepMode $key }} @@ -116,14 +124,12 @@ {{ end }} {{ end }} - - -
+ + + +
diff --git a/web/scimitarWU.html b/web/scimitarWU.html index 37fbcfd..6a83945 100644 --- a/web/scimitarWU.html +++ b/web/scimitarWU.html @@ -34,19 +34,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -55,12 +49,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + +
diff --git a/web/st100.html b/web/st100.html index 718a497..ba8cc73 100644 --- a/web/st100.html +++ b/web/st100.html @@ -24,7 +24,7 @@
- Device + Device
{{ .Device.Product }}
@@ -32,19 +32,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -53,12 +47,22 @@ {{ end }} {{ end }} - - - - + + + + - -
+ + + +
{{ if eq "stand" $device.DeviceProfile.RGBProfile }}
diff --git a/web/virtuosorgbXTW.html b/web/virtuosorgbXTW.html index 43cc5b4..e4726f5 100644 --- a/web/virtuosorgbXTW.html +++ b/web/virtuosorgbXTW.html @@ -51,21 +51,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Sleep

Mute LED

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -74,12 +66,23 @@ {{ end }} {{ end }} - - - - + + + + + - {{ range $key, $sleepMode := $device.SleepModes }} {{ if eq $device.DeviceProfile.SleepMode $key }} @@ -116,9 +124,16 @@ {{ end }} {{ end }} - - {{ range $key, $value := $device.MuteIndicators }} {{ if eq $device.DeviceProfile.MuteIndicator $key }} @@ -127,14 +142,13 @@ {{ end }} {{ end }} - - -
+ + + + +
{{ if eq "headset" $device.DeviceProfile.RGBProfile }} diff --git a/web/virtuosorgbXTWU.html b/web/virtuosorgbXTWU.html index 2dfe97f..bdcd4e6 100644 --- a/web/virtuosorgbXTWU.html +++ b/web/virtuosorgbXTWU.html @@ -32,20 +32,13 @@ Firmware: {{ .Device.Firmware }}

-
-
- - - - - - - - - - - - - - - - -

User Profile

Brightness

RGB Profile

Mute LED

Save Profile

- {{ range $key, $profile := $device.UserProfiles }} {{ if $profile.Active }} @@ -54,12 +47,22 @@ {{ end }} {{ end }} - - - - + + + + - {{ range $key, $value := $device.MuteIndicators }} {{ if eq $device.DeviceProfile.MuteIndicator $key }} @@ -96,14 +105,12 @@ {{ end }} {{ end }} - - -
+ + + +
{{ if eq "headset" $device.DeviceProfile.RGBProfile }}