Take control of your RGB Keyboard's lighting. Developed for the Ducky One2 RGB, expandable to others (as long as they work with the default DuckyRGB software).
NOTE: DuckyRGB cannot be open when running this code - it will restrict connection to the keyboard. Connection to the keyboard must be maintained to enable DuckyLighting to work.
DuckyLighting is a (nearly) Pure-Python 3.8 solution to the problem of Ducky's RGBSeries software being lackluster at best. The software is useful but does not enable users to layer multiple effects. Even the effects provided are limited.
When I think of keyboard RGB control, I think of Razer Synapse 3. Obviously, the customization controls in Synapse only work for Razer RGB products, so my goal with this project was to get as close to the effect options available in Synapse as I could.
Certain effects, like reactive ripples, are still in the works. That being said, Python makes development incredibly easy for things like this. You just have to get creative.
DuckyLighting started as a fork of Latedi's DuckyAPI, which uses a C++ named pipe API to communicate to a Scandinavian Ducky Shine 7, using instructions sent from a Python program. I made several changes to the C++ API and overhauled the Python scripts. I moved the API into Python for easier changing, and DuckyLighting was born. My goal was to create room for expansion to other keyboards and protocols, like the one for the Shine 7.
DuckyLighting doesn't have any fancy UI (yet), but I tried to make creating lighting configurations as easy as possible.
Setting up and running your own instance of the DuckyOne2RGB
class can be done in only a few lines of code.
Disclaimer: Please understand that this code is provided with no liability or warranty. This code could brick your keyboard or expose your computer to security vulnerabilities. Only use code that you understand.
Use at your own risk!
from keyboards.ducky import DuckyOne2RGB
from lighting import Color, SolidColorScheme
import configs
class MyConfig(configs.Config):
@configs.layer()
def solid_blue(self):
"""
Creates a solid blue keyboard.
"""
return SolidColorScheme(Color(0, 0, 255))
if __name__ == '__main__':
my_ducky = DuckyOne2RGB()
my_ducky.set_config(MyConfig())
my_ducky.run()
An example Config is also provided in flamestarlightbluepress.py. This configuration creates a flame effect, a blue reactive effect, and a purple starlight effect (only on the function keys and spacebar).
DuckyLighting is provided under the GNU General Public License, version 3.0. DuckyLighting is packaged with HIDAPI binaries (under the terms of its GPL) and a modified version of the python-easyhid library (under the terms of its MIT License).
This project is not intended to infringe on any existing licenses, trademarks, copyrights, etc. If this is found to be the case, please contact me.
DuckyLighting can be downloaded from GitHub
git clone https://github.com/Russell-Newton/DuckyLighting.git
I recommend creating a unique virtual environment for your copy of this project. I personally use Anaconda to manage such environments. I used Python 3.8 to create this code, but it should work for Python 3.6 (and potentially older versions of Python 3).
Most of the requirements can be installed with pip
, but PyAudio (if you wish to use
a Spectrogram Scheme) has to be installed with another tool like pipwin
. An easy way
to make sure you get all the dependencies correct is to run the following commands in a terminal.
pip install pipwin
pip install numpy
pip install keyboard
pip install noise
pip install cffi
pipwin install pyaudio
This set of commands has worked for me.
Creating a Config with DuckyLighting resembles using the bot commands framework
from discord.py to create
a Cog. Organizing layers on top of layers can be
done with RGBKeyboard::add_layer()
, but having each layer defined in its very own, tidy place can be incredibly handy.
The gist:
- Each Config is a Python class that subclasses
configs.Config
. - Every layer getter is marked with the
configs.layer()
decorator. A layer getter takes no parameters and should return someLightingScheme
. Each layer is added in their getter declaration order, so order matters! The decorator has room to take in aCombineType
and aMask
, which determine how the layer is added on top of the other layers. By default, a layer overlays its non-zero colors for all keys. - An RGBKeyboard is assigned a Config with
RGBKeyboard::set_config()
It's that easy!
This example Config defines two layers: a solid white background with solid red WASD and arrow keys.
import configs
from lighting import SolidColorScheme, Color, Mask
class ExampleConfig(configs.Config):
# This layer is added to the overall scheme first
@configs.layer()
def white_background(self):
return SolidColorScheme(Color(255, 255, 255))
# This layer is added second, but only for the keys in the Mask
@configs.layer(mask=Mask.WASD + Mask(["UpArrow", "DownArrow", "LeftArrow", "RightArrow"]))
def red_wasd_and_arrows(self):
return SolidColorScheme(Color(255, 0, 0))
Once you have your Config defined, all you need to do is tell your keyboard to set it as its config, using the set_config()
method. Then you can start lighting up your keyboard with run()
.
my_keyboard.set_config(ExampleConfig())
my_keyboard.run()
The packet protocol/setup that gives instructions to the Ducky One2 RGB can be modified for and expanded to other keyboards, assuming their key colors can be set through instruction packets. It's also possible that this project could be expanded to Linux, but that is beyond my knowledge and skill set.
Additionally, I tried to make it as simple as possible to create new LightingSchemes and ColorFunctions. Making new ones just takes some creativity and overriding.
If there is a computer software that can tell a keyboard how to be colored, the protocols can be determined with a tool such as WireShark and USBPcap.
I used these tools to determine the packet structure for the color data packets by observing consistencies between
recurring packets. The protocol that I used was the one that the DuckyRGBSeries software uses when connected to Razer
Synapse with Razer Chroma Broadcast. I figured out that this connection sends over 8 packets, each containing some head
metadata followed by r
, g
, and b
bytes corresponding to the various keys (and gaps between keys) on the keyboard.
Additionally, I used the opening and closing packets that Latedi found
using these tools. These are required and tell the keyboard to listen for instructions over USB and then to stop
listening.
Similar analysis could be used to determine how to instruct other keyboards with different firmware how to light up.
Name and Role | Links |
---|---|
And Special Thanks to:
Name and Reference | Links |
---|---|
In case none of the hyperlinks above work,
Hyperlink Name | Link |
---|---|
Ducky | https://www.duckychannel.com.tw |
Razer Synapse 3 | https://www.razer.com/synapse-3 |
Latedi's DuckyAPI | https://github.com/Latedi/DuckyAPI |
flamestarlightbluepress.py | https://www.github.com/Russell-Newton/DuckyLighting/blob/main/configs/flamestarlightbluepress.py |
HIDAPI | https://github.com/libusb/hidapi |
python-easyhid | https://github.com/ahtn/python-easyhid |
Anaconda | https://www.anaconda.com |
Spectrogram Scheme | https://www.github.com/Russell-Newton/DuckyLighting/blob/main/frontend/lighting/spectrogenerator.py |
discord.py | https://discordpy.readthedocs.io/en/latest |
Cog | https://discordpy.readthedocs.io/en/latest/ext/commands/cogs.html |
WireShark | https://www.wireshark.org |
USBPcap | https://desowin.org/usbpcap/ |
Latedi found | https://github.com/Latedi/DuckyAPI#how-to |