kd104a 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Datasource local storage ignored files
7
+ /dataSources/
8
+ /dataSources.local.xml
@@ -0,0 +1,60 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="DotEnvExtraBlankLineInspection" enabled="false" level="WARNING" enabled_by_default="false" />
5
+ <inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
6
+ <inspection_tool class="HttpUrlsUsage" enabled="true" level="WEAK WARNING" enabled_by_default="true">
7
+ <option name="ignoredUrls">
8
+ <list>
9
+ <option value="http://" />
10
+ <option value="http://0.0.0.0" />
11
+ <option value="http://127.0.0.1" />
12
+ <option value="http://activemq.apache.org/schema/" />
13
+ <option value="http://cxf.apache.org/schemas/" />
14
+ <option value="http://java.sun.com/" />
15
+ <option value="http://javafx.com/fxml" />
16
+ <option value="http://javafx.com/javafx/" />
17
+ <option value="http://json-schema.org/draft" />
18
+ <option value="http://localhost" />
19
+ <option value="http://maven.apache.org/POM/" />
20
+ <option value="http://maven.apache.org/xsd/" />
21
+ <option value="http://primefaces.org/ui" />
22
+ <option value="http://schema.cloudfoundry.org/spring/" />
23
+ <option value="http://schemas.xmlsoap.org/" />
24
+ <option value="http://tiles.apache.org/" />
25
+ <option value="http://www.ibm.com/webservices/xsd" />
26
+ <option value="http://www.jboss.com/xml/ns/" />
27
+ <option value="http://www.jboss.org/j2ee/schema/" />
28
+ <option value="http://www.springframework.org/schema/" />
29
+ <option value="http://www.springframework.org/security/tags" />
30
+ <option value="http://www.springframework.org/tags" />
31
+ <option value="http://www.thymeleaf.org" />
32
+ <option value="http://www.w3.org/" />
33
+ <option value="http://xmlns.jcp.org/" />
34
+ </list>
35
+ </option>
36
+ </inspection_tool>
37
+ <inspection_tool class="PyAttributeOutsideInitInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
38
+ <inspection_tool class="PyBroadExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
39
+ <inspection_tool class="PyCallingNonCallableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
40
+ <inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
41
+ <inspection_tool class="PyPep8Inspection" enabled="false" level="INFORMATION" enabled_by_default="false">
42
+ <option name="ignoredErrors">
43
+ <list>
44
+ <option value="E302" />
45
+ </list>
46
+ </option>
47
+ </inspection_tool>
48
+ <inspection_tool class="PyPep8NamingInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false">
49
+ <option name="ignoredErrors">
50
+ <list>
51
+ <option value="N802" />
52
+ </list>
53
+ </option>
54
+ </inspection_tool>
55
+ <inspection_tool class="PyProtectedMemberInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
56
+ <inspection_tool class="PyShadowingBuiltinsInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
57
+ <inspection_tool class="PyTypeCheckerInspection" enabled="false" level="WARNING" enabled_by_default="false" />
58
+ <inspection_tool class="PyUnresolvedReferencesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
59
+ </profile>
60
+ </component>
@@ -0,0 +1,6 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
6
+ <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
7
+ <excludeFolder url="file://$MODULE_DIR$/.venv" />
8
+ </content>
9
+ <orderEntry type="jdk" jdkName="Python 3.13 (kd104a)" jdkType="Python SDK" />
10
+ <orderEntry type="sourceFolder" forTests="false" />
11
+ </component>
12
+ </module>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Black">
4
+ <option name="sdkName" value="Python 3.13 (kd104a)" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/kd104a.iml" filepath="$PROJECT_DIR$/.idea/kd104a.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,126 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="AutoImportSettings">
4
+ <option name="autoReloadType" value="SELECTIVE" />
5
+ </component>
6
+ <component name="ChangeListManager">
7
+ <list default="true" id="2912c955-f467-4d38-9168-e0543a01d4c4" name="Changes" comment="alpha" />
8
+ <option name="SHOW_DIALOG" value="false" />
9
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
10
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
11
+ <option name="LAST_RESOLUTION" value="IGNORE" />
12
+ </component>
13
+ <component name="FileTemplateManagerImpl">
14
+ <option name="RECENT_TEMPLATES">
15
+ <list>
16
+ <option value="Python Script" />
17
+ </list>
18
+ </option>
19
+ </component>
20
+ <component name="Git.Settings">
21
+ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
22
+ </component>
23
+ <component name="GitHubPullRequestSearchHistory"><![CDATA[{
24
+ "lastFilter": {
25
+ "state": "OPEN",
26
+ "assignee": "yakcom"
27
+ }
28
+ }]]></component>
29
+ <component name="GithubPullRequestsUISettings"><![CDATA[{
30
+ "selectedUrlAndAccountId": {
31
+ "url": "https://github.com/yakcom/kd104a.git",
32
+ "accountId": "3bdfb013-94bd-46e6-a6b8-d56bb6fbf396"
33
+ }
34
+ }]]></component>
35
+ <component name="ProjectColorInfo"><![CDATA[{
36
+ "associatedIndex": 6
37
+ }]]></component>
38
+ <component name="ProjectId" id="3CA84dLd63yQllfjm4y0vhNCmdK" />
39
+ <component name="ProjectLevelVcsManager" settingsEditedManually="true">
40
+ <ConfirmationsSetting value="2" id="Add" />
41
+ </component>
42
+ <component name="ProjectViewState">
43
+ <option name="hideEmptyMiddlePackages" value="true" />
44
+ <option name="showLibraryContents" value="true" />
45
+ </component>
46
+ <component name="PropertiesComponent"><![CDATA[{
47
+ "keyToString": {
48
+ "Python.api.executor": "Debug",
49
+ "Python.controller.executor": "Debug",
50
+ "Python.device.executor": "Debug",
51
+ "Python.effects.executor": "Debug",
52
+ "Python.enums.executor": "Debug",
53
+ "Python.test_basic.executor": "Debug",
54
+ "RunOnceActivity.ShowReadmeOnStart": "true",
55
+ "git-widget-placeholder": "master",
56
+ "last_opened_file_path": "C:/Users/yax/Documents/Python/kd104a",
57
+ "node.js.detected.package.eslint": "true",
58
+ "node.js.detected.package.tslint": "true",
59
+ "node.js.selected.package.eslint": "(autodetect)",
60
+ "node.js.selected.package.tslint": "(autodetect)",
61
+ "nodejs_package_manager_path": "npm",
62
+ "vue.rearranger.settings.migration": "true"
63
+ }
64
+ }]]></component>
65
+ <component name="SharedIndexes">
66
+ <attachedChunks>
67
+ <set>
68
+ <option value="bundled-js-predefined-d6986cc7102b-76f8388c3a79-JavaScript-PY-243.24978.54" />
69
+ <option value="bundled-python-sdk-91e3b7efe1d4-466328ff949b-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-243.24978.54" />
70
+ </set>
71
+ </attachedChunks>
72
+ </component>
73
+ <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
74
+ <component name="TaskManager">
75
+ <task active="true" id="Default" summary="Default task">
76
+ <changelist id="2912c955-f467-4d38-9168-e0543a01d4c4" name="Changes" comment="" />
77
+ <created>1775816824248</created>
78
+ <option name="number" value="Default" />
79
+ <option name="presentableId" value="Default" />
80
+ <updated>1775816824248</updated>
81
+ <workItem from="1775816825332" duration="18759000" />
82
+ </task>
83
+ <task id="LOCAL-00001" summary="init">
84
+ <option name="closed" value="true" />
85
+ <created>1775900791506</created>
86
+ <option name="number" value="00001" />
87
+ <option name="presentableId" value="LOCAL-00001" />
88
+ <option name="project" value="LOCAL" />
89
+ <updated>1775900791506</updated>
90
+ </task>
91
+ <task id="LOCAL-00002" summary="alpha">
92
+ <option name="closed" value="true" />
93
+ <created>1775909177743</created>
94
+ <option name="number" value="00002" />
95
+ <option name="presentableId" value="LOCAL-00002" />
96
+ <option name="project" value="LOCAL" />
97
+ <updated>1775909177743</updated>
98
+ </task>
99
+ <task id="LOCAL-00003" summary="alpha">
100
+ <option name="closed" value="true" />
101
+ <created>1775909318425</created>
102
+ <option name="number" value="00003" />
103
+ <option name="presentableId" value="LOCAL-00003" />
104
+ <option name="project" value="LOCAL" />
105
+ <updated>1775909318425</updated>
106
+ </task>
107
+ <option name="localTasksCounter" value="4" />
108
+ <servers />
109
+ </component>
110
+ <component name="TypeScriptGeneratedFilesManager">
111
+ <option name="version" value="3" />
112
+ </component>
113
+ <component name="VcsManagerConfiguration">
114
+ <MESSAGE value="init" />
115
+ <MESSAGE value="alpha" />
116
+ <option name="LAST_COMMIT_MESSAGE" value="alpha" />
117
+ </component>
118
+ <component name="com.intellij.coverage.CoverageDataManagerImpl">
119
+ <SUITE FILE_PATH="coverage/kd104a$test_basic.coverage" NAME="test_basic Coverage Results" MODIFIED="1775899333166" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" />
120
+ <SUITE FILE_PATH="coverage/kd104a$enums.coverage" NAME="enums Coverage Results" MODIFIED="1775819700210" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/kd104a" />
121
+ <SUITE FILE_PATH="coverage/kd104a$device.coverage" NAME="device Coverage Results" MODIFIED="1775818675389" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/kd104a" />
122
+ <SUITE FILE_PATH="coverage/kd104a$api.coverage" NAME="api Coverage Results" MODIFIED="1775857158989" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/kd104a" />
123
+ <SUITE FILE_PATH="coverage/kd104a$controller.coverage" NAME="controller Coverage Results" MODIFIED="1775898860349" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/kd104a" />
124
+ <SUITE FILE_PATH="coverage/kd104a$effects.coverage" NAME="effects Coverage Results" MODIFIED="1775898630792" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/kd104a" />
125
+ </component>
126
+ </project>
kd104a-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
kd104a-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: kd104a
3
+ Version: 0.1.0
4
+ Summary: KD104A keyboard lighting control over HID, with a small, straightforward API for building and applying effects.
5
+ Project-URL: Homepage, https://github.com/yakcom/kd104a
6
+ Project-URL: Repository, https://github.com/yakcom/kd104a
7
+ Author: Ilya Miller
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: dark,hid,kd104a,keyboard,lighting,rgb
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: hidapi>=0.14.0
17
+ Requires-Dist: webcolors>=1.13
18
+ Description-Content-Type: text/markdown
19
+
20
+ # kd104a
21
+ Simple HID control for KD104A keyboard lighting.
22
+
23
+ # Install
24
+
25
+ ```bash
26
+ pip install kd104a
27
+ ```
28
+
29
+ # Quick Start
30
+
31
+ ```python
32
+ from kd104a import Device, Lighting, Direction, effects
33
+
34
+ keyboard = Device(product="Gaming Keyboard", interface=2)
35
+ lighting = Lighting(keyboard)
36
+
37
+ lighting.set_mode(
38
+ effects.wave,
39
+ brightness=1,
40
+ speed=20,
41
+ direction=Direction.RIGHT_LEFT,
42
+ color1="red",
43
+ color2="blue",
44
+ )
45
+ ```
46
+
47
+ # Usage
48
+
49
+ ### Power
50
+
51
+ ```python
52
+ lighting.off()
53
+ lighting.on()
54
+ ```
55
+
56
+ ### Change mode
57
+
58
+ ```python
59
+ lighting.set_mode(effects.static, color="yellow")
60
+ ```
61
+
62
+ ### Update parameters
63
+
64
+ ```python
65
+ lighting.set_brightness(100)
66
+ lighting.set_speed(10)
67
+ lighting.set_direction(Direction.LEFT_RIGHT)
68
+ ```
69
+
70
+ # Parameters
71
+
72
+ ### Brightness / Speed
73
+
74
+ ```python
75
+ 0-100 # percent
76
+ ```
77
+
78
+ # Colors
79
+
80
+ ```python
81
+ "red"
82
+ "#ff0000"
83
+ (255, 0, 0)
84
+ ```
85
+
86
+ # Direction
87
+
88
+ ```python
89
+ Direction.LEFT_RIGHT
90
+ Direction.RIGHT_LEFT
91
+ Direction.TOP_BOTTOM
92
+ Direction.BOTTOM_TOP
93
+ Direction.CENTER_OUT
94
+ Direction.OUT_CENTER
95
+ Direction.CLOCKWISE
96
+ Direction.COUNTER_CLOCKWISE
97
+ ```
98
+
99
+ # Effects
100
+
101
+ ```python
102
+ effects.static
103
+ effects.neon
104
+ effects.breathing
105
+ effects.wave
106
+ effects.blink
107
+ effects.radar
108
+ effects.ripple
109
+ effects.marquee
110
+ effects.shine
111
+ effects.ripple2
112
+ effects.interactive
113
+ effects.crossing
114
+ effects.firework
115
+ effects.reactive
116
+ effects.equalizer
117
+ ```
kd104a-0.1.0/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # kd104a
2
+ Simple HID control for KD104A keyboard lighting.
3
+
4
+ # Install
5
+
6
+ ```bash
7
+ pip install kd104a
8
+ ```
9
+
10
+ # Quick Start
11
+
12
+ ```python
13
+ from kd104a import Device, Lighting, Direction, effects
14
+
15
+ keyboard = Device(product="Gaming Keyboard", interface=2)
16
+ lighting = Lighting(keyboard)
17
+
18
+ lighting.set_mode(
19
+ effects.wave,
20
+ brightness=1,
21
+ speed=20,
22
+ direction=Direction.RIGHT_LEFT,
23
+ color1="red",
24
+ color2="blue",
25
+ )
26
+ ```
27
+
28
+ # Usage
29
+
30
+ ### Power
31
+
32
+ ```python
33
+ lighting.off()
34
+ lighting.on()
35
+ ```
36
+
37
+ ### Change mode
38
+
39
+ ```python
40
+ lighting.set_mode(effects.static, color="yellow")
41
+ ```
42
+
43
+ ### Update parameters
44
+
45
+ ```python
46
+ lighting.set_brightness(100)
47
+ lighting.set_speed(10)
48
+ lighting.set_direction(Direction.LEFT_RIGHT)
49
+ ```
50
+
51
+ # Parameters
52
+
53
+ ### Brightness / Speed
54
+
55
+ ```python
56
+ 0-100 # percent
57
+ ```
58
+
59
+ # Colors
60
+
61
+ ```python
62
+ "red"
63
+ "#ff0000"
64
+ (255, 0, 0)
65
+ ```
66
+
67
+ # Direction
68
+
69
+ ```python
70
+ Direction.LEFT_RIGHT
71
+ Direction.RIGHT_LEFT
72
+ Direction.TOP_BOTTOM
73
+ Direction.BOTTOM_TOP
74
+ Direction.CENTER_OUT
75
+ Direction.OUT_CENTER
76
+ Direction.CLOCKWISE
77
+ Direction.COUNTER_CLOCKWISE
78
+ ```
79
+
80
+ # Effects
81
+
82
+ ```python
83
+ effects.static
84
+ effects.neon
85
+ effects.breathing
86
+ effects.wave
87
+ effects.blink
88
+ effects.radar
89
+ effects.ripple
90
+ effects.marquee
91
+ effects.shine
92
+ effects.ripple2
93
+ effects.interactive
94
+ effects.crossing
95
+ effects.firework
96
+ effects.reactive
97
+ effects.equalizer
98
+ ```
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "kd104a"
7
+ version = "0.1.0"
8
+ description = "KD104A keyboard lighting control over HID, with a small, straightforward API for building and applying effects."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "Ilya Miller" }
14
+ ]
15
+ dependencies = [
16
+ "hidapi>=0.14.0",
17
+ "webcolors>=1.13"
18
+ ]
19
+
20
+ keywords = ["hid", "keyboard", "rgb", "lighting", "kd104a", "dark", "dark"]
21
+ classifiers = [
22
+ "Programming Language :: Python :: 3",
23
+ "Operating System :: OS Independent",
24
+ "License :: OSI Approved :: MIT License",
25
+ "Topic :: Software Development :: Libraries",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/yakcom/kd104a"
30
+ Repository = "https://github.com/yakcom/kd104a"
31
+
32
+ [tool.hatch.build.targets.wheel]
33
+ packages = ["kd104a"]
@@ -0,0 +1,11 @@
1
+ from .device import Device
2
+ from .enums import Direction
3
+ from .controller import Lighting
4
+ from . import effects
5
+
6
+ __all__ = [
7
+ "Device",
8
+ "Direction",
9
+ "Lighting",
10
+ "effects",
11
+ ]
@@ -0,0 +1,73 @@
1
+ from . import effects
2
+ from .enums import Direction
3
+
4
+
5
+ class Lighting:
6
+ def __init__(self, device):
7
+ self.device = device
8
+
9
+ self._brightness = 50
10
+ self._prev_brightness = 50
11
+ self._speed = 50
12
+ self._direction = Direction.LEFT_RIGHT
13
+
14
+ self._mode = effects.static
15
+ self._kwargs = {"color": (0, 0, 0)}
16
+
17
+ def _send(self):
18
+ kwargs = {
19
+ "brightness": self._brightness,
20
+ **self._kwargs,
21
+ }
22
+
23
+ varnames = self._mode.__code__.co_varnames
24
+
25
+ if "speed" in varnames:
26
+ kwargs["speed"] = self._speed
27
+
28
+ if "direction" in varnames:
29
+ kwargs["direction"] = self._direction
30
+
31
+ self.device.send(self._mode(**kwargs))
32
+
33
+ def on(self):
34
+ self._brightness = self._prev_brightness
35
+ self._send()
36
+
37
+ def off(self):
38
+ self._prev_brightness = self._brightness
39
+ self._brightness = 0
40
+ self._send()
41
+
42
+ def set_mode(
43
+ self,
44
+ mode,
45
+ *,
46
+ brightness=None,
47
+ speed=None,
48
+ direction=None,
49
+ **kwargs,
50
+ ):
51
+ self._mode = mode
52
+
53
+ if brightness is not None:
54
+ self._brightness = brightness
55
+ if speed is not None:
56
+ self._speed = speed
57
+ if direction is not None:
58
+ self._direction = direction
59
+
60
+ self._kwargs = kwargs
61
+ self._send()
62
+
63
+ def set_brightness(self, value: int):
64
+ self._brightness = value
65
+ self._send()
66
+
67
+ def set_speed(self, value: int):
68
+ self._speed = value
69
+ self._send()
70
+
71
+ def set_direction(self, value: Direction):
72
+ self._direction = value
73
+ self._send()
@@ -0,0 +1,41 @@
1
+ import hid
2
+
3
+
4
+ class Device:
5
+ def __init__(
6
+ self,
7
+ product: str | None = None,
8
+ manufacturer: str | None = None,
9
+ vid: int | None = None,
10
+ pid: int | None = None,
11
+ interface: int | None = None,
12
+ usage_page: int | None = None,
13
+ ):
14
+ dev = next(
15
+ (
16
+ d
17
+ for d in hid.enumerate()
18
+ if (product is None or (d["product_string"] or "") == product)
19
+ and (manufacturer is None or (d["manufacturer_string"] or "") == manufacturer)
20
+ and (vid is None or d["vendor_id"] == vid)
21
+ and (pid is None or d["product_id"] == pid)
22
+ and (interface is None or d["interface_number"] == interface)
23
+ and (usage_page is None or d["usage_page"] == usage_page)
24
+ ),
25
+ None,
26
+ )
27
+
28
+ if dev is None:
29
+ raise RuntimeError("Device not found")
30
+
31
+ self.path = dev["path"]
32
+
33
+ def send(self, packet: bytes):
34
+ device = hid.device()
35
+ try:
36
+ device.open_path(self.path)
37
+ written = device.write(packet)
38
+ if written != len(packet):
39
+ raise RuntimeError(f"Failed to write full packet: {written}/{len(packet)} bytes")
40
+ finally:
41
+ device.close()
@@ -0,0 +1,229 @@
1
+ from .packet import build_packet
2
+ from .enums import Mode, Direction
3
+ import webcolors
4
+
5
+
6
+ def _color(value):
7
+ if isinstance(value, tuple):
8
+ if len(value) != 3:
9
+ raise ValueError("Color tuple must have 3 elements")
10
+ r, g, b = value
11
+ return int(r), int(g), int(b)
12
+
13
+ if isinstance(value, str):
14
+ if value.startswith("#"):
15
+ rgb = webcolors.hex_to_rgb(value)
16
+ else:
17
+ rgb = webcolors.name_to_rgb(value)
18
+ return rgb.red, rgb.green, rgb.blue
19
+
20
+ raise TypeError("Invalid color")
21
+
22
+
23
+ def _level(value: int):
24
+ value = max(0, min(100, int(value)))
25
+ if value == 0:
26
+ return 0
27
+ return max(1, int(value * 4 / 100))
28
+
29
+
30
+ def static(brightness=50, color=(0, 0, 0)):
31
+ return build_packet(
32
+ mode=Mode.STATIC,
33
+ brightness=_level(brightness),
34
+ color1=_color(color),
35
+ )
36
+
37
+
38
+ def neon(brightness=50, speed=50, color1=(0, 0, 0), color2=(0, 0, 0), palette=False):
39
+ return build_packet(
40
+ mode=Mode.NEON,
41
+ brightness=_level(brightness),
42
+ speed=_level(speed),
43
+ color1=_color(color1),
44
+ color2=_color(color2),
45
+ palette=int(palette),
46
+ )
47
+
48
+
49
+ def breathing(brightness=50, speed=50, color1=(0, 0, 0), color2=(0, 0, 0), palette=False):
50
+ return build_packet(
51
+ mode=Mode.BREATHING,
52
+ brightness=_level(brightness),
53
+ speed=_level(speed),
54
+ color1=_color(color1),
55
+ color2=_color(color2),
56
+ palette=int(palette),
57
+ )
58
+
59
+
60
+ def wave(
61
+ brightness=50,
62
+ speed=50,
63
+ color1=(0, 0, 0),
64
+ color2=(0, 0, 0),
65
+ palette=False,
66
+ direction=Direction.LEFT_RIGHT,
67
+ ):
68
+ return build_packet(
69
+ mode=Mode.WAVE,
70
+ brightness=_level(brightness),
71
+ speed=_level(speed),
72
+ color1=_color(color1),
73
+ color2=_color(color2),
74
+ direction=direction,
75
+ palette=int(palette),
76
+ )
77
+
78
+
79
+ def blink(brightness=50, speed=50, color1=(0, 0, 0), color2=(0, 0, 0), palette=False):
80
+ return build_packet(
81
+ mode=Mode.BLINK,
82
+ brightness=_level(brightness),
83
+ speed=_level(speed),
84
+ color1=_color(color1),
85
+ color2=_color(color2),
86
+ palette=int(palette),
87
+ )
88
+
89
+
90
+ def radar(
91
+ brightness=50,
92
+ speed=50,
93
+ color1=(0, 0, 0),
94
+ color2=(0, 0, 0),
95
+ palette=False,
96
+ direction=Direction.CLOCKWISE,
97
+ ):
98
+ return build_packet(
99
+ mode=Mode.RADAR,
100
+ brightness=_level(brightness),
101
+ speed=_level(speed),
102
+ color1=_color(color1),
103
+ color2=_color(color2),
104
+ direction=direction,
105
+ palette=int(palette),
106
+ )
107
+
108
+
109
+ def ripple(
110
+ brightness=50,
111
+ speed=50,
112
+ color1=(0, 0, 0),
113
+ color2=(0, 0, 0),
114
+ palette=False,
115
+ direction=Direction.CENTER_OUT,
116
+ ):
117
+ return build_packet(
118
+ mode=Mode.RIPPLE,
119
+ brightness=_level(brightness),
120
+ speed=_level(speed),
121
+ color1=_color(color1),
122
+ color2=_color(color2),
123
+ direction=direction,
124
+ palette=int(palette),
125
+ )
126
+
127
+
128
+ def marquee(
129
+ brightness=50,
130
+ speed=50,
131
+ color1=(0, 0, 0),
132
+ color2=(0, 0, 0),
133
+ palette=False,
134
+ direction=Direction.LEFT_RIGHT,
135
+ ):
136
+ return build_packet(
137
+ mode=Mode.MARQUEE,
138
+ brightness=_level(brightness),
139
+ speed=_level(speed),
140
+ color1=_color(color1),
141
+ color2=_color(color2),
142
+ direction=direction,
143
+ palette=int(palette),
144
+ )
145
+
146
+
147
+ def shine(brightness=50, speed=50, color1=(0, 0, 0), color2=(0, 0, 0), palette=False):
148
+ return build_packet(
149
+ mode=Mode.SHINE,
150
+ brightness=_level(brightness),
151
+ speed=_level(speed),
152
+ color1=_color(color1),
153
+ color2=_color(color2),
154
+ palette=int(palette),
155
+ )
156
+
157
+
158
+ def ripple2(
159
+ brightness=50,
160
+ speed=50,
161
+ color1=(0, 0, 0),
162
+ color2=(0, 0, 0),
163
+ palette=False,
164
+ direction=Direction.CENTER_OUT,
165
+ ):
166
+ return build_packet(
167
+ mode=Mode.RIPPLE2,
168
+ brightness=_level(brightness),
169
+ speed=_level(speed),
170
+ color1=_color(color1),
171
+ color2=_color(color2),
172
+ direction=direction,
173
+ palette=int(palette),
174
+ )
175
+
176
+
177
+ def interactive(brightness=50, speed=50, color1=(0, 0, 0), color2=(0, 0, 0), palette=False):
178
+ return build_packet(
179
+ mode=Mode.INTERACTIVE,
180
+ brightness=_level(brightness),
181
+ speed=_level(speed),
182
+ color1=_color(color1),
183
+ color2=_color(color2),
184
+ palette=int(palette),
185
+ )
186
+
187
+
188
+ def crossing(brightness=50, speed=50, color1=(0, 0, 0), color2=(0, 0, 0), palette=False):
189
+ return build_packet(
190
+ mode=Mode.CROSSING,
191
+ brightness=_level(brightness),
192
+ speed=_level(speed),
193
+ color1=_color(color1),
194
+ color2=_color(color2),
195
+ palette=int(palette),
196
+ )
197
+
198
+
199
+ def firework(brightness=50, speed=50, color1=(0, 0, 0), color2=(0, 0, 0), palette=False):
200
+ return build_packet(
201
+ mode=Mode.FIREWORK,
202
+ brightness=_level(brightness),
203
+ speed=_level(speed),
204
+ color1=_color(color1),
205
+ color2=_color(color2),
206
+ palette=int(palette),
207
+ )
208
+
209
+
210
+ def reactive(brightness=50, speed=50, color1=(0, 0, 0), color2=(0, 0, 0), palette=False):
211
+ return build_packet(
212
+ mode=Mode.REACTIVE,
213
+ brightness=_level(brightness),
214
+ speed=_level(speed),
215
+ color1=_color(color1),
216
+ color2=_color(color2),
217
+ palette=int(palette),
218
+ )
219
+
220
+
221
+ def equalizer(brightness=50, speed=50, color1=(0, 0, 0), color2=(0, 0, 0), palette=False):
222
+ return build_packet(
223
+ mode=Mode.EQUALIZER,
224
+ brightness=_level(brightness),
225
+ speed=_level(speed),
226
+ color1=_color(color1),
227
+ color2=_color(color2),
228
+ palette=int(palette),
229
+ )
@@ -0,0 +1,28 @@
1
+ from enum import IntEnum
2
+
3
+ class Mode(IntEnum):
4
+ STATIC = 0x00
5
+ NEON = 0x03
6
+ BREATHING = 0x01
7
+ WAVE = 0x02
8
+ BLINK = 0x04
9
+ RADAR = 0x05
10
+ RIPPLE = 0x06
11
+ MARQUEE = 0x07
12
+ SHINE = 0x08
13
+ RIPPLE2 = 0x09
14
+ INTERACTIVE = 0x0A
15
+ CROSSING = 0x0B
16
+ FIREWORK = 0x0C
17
+ REACTIVE = 0x0D
18
+ EQUALIZER = 0x0E
19
+
20
+ class Direction(IntEnum):
21
+ RIGHT_LEFT = 0
22
+ LEFT_RIGHT = 1
23
+ BOTTOM_TOP = 2
24
+ TOP_BOTTOM = 3
25
+ CENTER_OUT = 4
26
+ OUT_CENTER = 5
27
+ CLOCKWISE = 6
28
+ COUNTER_CLOCKWISE = 7
@@ -0,0 +1,23 @@
1
+ HEADER = (0x01, 0x07, 0x00, 0x00, 0x00, 0x0E)
2
+ PACKET_SIZE = 64
3
+
4
+
5
+ def build_packet(
6
+ mode: int,
7
+ brightness: int = 0,
8
+ speed: int = 0,
9
+ color1=(0, 0, 0),
10
+ color2=(0, 0, 0),
11
+ direction: int = 0,
12
+ palette: int = 0,
13
+ ) -> bytes:
14
+ buf = [0] * PACKET_SIZE
15
+ buf[0:6] = HEADER
16
+ buf[6] = int(mode)
17
+ buf[7] = brightness
18
+ buf[8] = speed
19
+ buf[9:12] = color1
20
+ buf[12:15] = color2
21
+ buf[15] = direction
22
+ buf[16] = palette
23
+ return bytes(buf)
@@ -0,0 +1,14 @@
1
+ from kd104a import Device, Lighting, Direction, effects
2
+ import time
3
+
4
+ keyboard = Device(product="Gaming Keyboard", interface=2)
5
+ lighting = Lighting(keyboard)
6
+
7
+ lighting.set_mode(
8
+ effects.wave,
9
+ brightness=1,
10
+ speed=20,
11
+ direction=Direction.RIGHT_LEFT,
12
+ color1="red",
13
+ color2="blue",
14
+ )