├── .gitignore ├── LICENSE.txt ├── LogitechPySDKs.sln ├── README.rst ├── logipy.pyproj ├── logipy ├── __init__.py ├── logi_arx.py └── logi_led.py ├── samples └── logipy_samples.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Python binaries 2 | *.pyc 3 | 4 | # Ignore any Surround SCM files 5 | .MySCMServerInfo 6 | 7 | # Ignore MOC files 8 | *.inl 9 | AutoMocGenerator.mak 10 | 11 | # Ignore Visual Studio files 12 | *.ncb 13 | *.vspscc 14 | *.obj 15 | *.pdb 16 | *.idb 17 | *.embed.manifest 18 | *.qch 19 | *.qm 20 | *.vcproj.*.user 21 | BuildLog.htm* 22 | *.suo 23 | *.opensdf 24 | *.log 25 | *.sdf 26 | *.vcxproj.user 27 | *.cod 28 | *.bak 29 | *.aps 30 | 31 | # Ignoring Win * Thumbnail Files 32 | Thumbs.db 33 | .DS_Store 34 | 35 | # Ignore misc files 36 | .pypirc 37 | PYPI.txt -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Logitech 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /LogitechPySDKs.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "logipy", "logipy.pyproj", "{DF8701A7-975B-44A4-855F-899E95CDE329}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {DF8701A7-975B-44A4-855F-899E95CDE329}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {DF8701A7-975B-44A4-855F-899E95CDE329}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | LogiPy 2 | ====== 3 | 4 | This package is a python wrapper for `Logitech G's LED and Arx 5 | SDKs `__. 6 | 7 | Use the LED SDK to get access to all of the LED backlighting and RGB 8 | capabilities of Logitech G products. Integrate profiles for custom key 9 | configurations, develop in-game effects, or mark keys to keep track of 10 | cool downs on various commands. 11 | 12 | Arx Control introduces second screen capability that allows iOS and 13 | Android mobile devices to display in-game info, vital system statistics 14 | and more. The associated SDK enables integration of your code with the 15 | Arx Control app. 16 | 17 | LED Examples 18 | ------------ 19 | 20 | Set all device lighting to red: 21 | 22 | :: 23 | 24 | from logipy import logi_led 25 | import time 26 | import ctypes 27 | 28 | logi_led.logi_led_init() 29 | time.sleep(1) # Give the SDK a second to initialize 30 | logi_led.logi_led_set_lighting(100, 0, 0) 31 | logi_led.logi_led_shutdown() 32 | 33 | Or if you prefer the c/c++ style you can use the LED DLL directly: 34 | 35 | :: 36 | 37 | from logipy import logi_led 38 | import time 39 | import ctypes 40 | 41 | logi_led.led_dll.LogiLedInit() 42 | time.sleep(1) # Give the SDK a second to initialize 43 | logi_led.led_dll.LogiLedSetLighting(ctypes.c_int(0), ctypes.c_int(100), ctypes.c_int(0)) 44 | logi_led.led_dll.LogiLedShutdown() 45 | 46 | Load user configured settings for your LED effects: 47 | 48 | :: 49 | 50 | from logipy import logi_led 51 | import time 52 | import ctypes 53 | 54 | logi_led.led_dll.LogiLedInit() 55 | time.sleep(1) # Give the SDK a second to initialize 56 | 57 | effect_enabled = logi_led.logi_led_get_config_option_bool('effect/enabled', True) # Use a default value if not found 58 | effect_duration = logi_led.logi_led_get_config_option_number('effect/duration', 5) 59 | effect_color = logi_led.logi_led_get_config_option_color('effect/color', Color(0, 255, 0)) 60 | 61 | logi_led.logi_led_set_config_option_label('effect', 'Effect Settings') 62 | logi_led.logi_led_set_config_option_label('effect/enabled', 'Enabled') 63 | logi_led.logi_led_set_config_option_label('effect/duration', 'Duration in seconds') 64 | logi_led.logi_led_set_config_option_label('effect/color', 'Color') 65 | 66 | if effect_enabled: 67 | logi_led.logi_led_set_lighting(*effect_color.rgb_percent()) 68 | time.sleep(effect_duration) 69 | 70 | logi_led.led_dll.LogiLedShutdown() 71 | 72 | Arx Examples 73 | ------------ 74 | 75 | Show a simple applet with the default callback: 76 | 77 | :: 78 | 79 | from logipy import logi_arx 80 | import time 81 | 82 | index = """ 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | """ 93 | css = """ 94 | body { 95 | background-color: black; 96 | } 97 | img { 98 | position: absolute; 99 | top: 50%; 100 | left: 50%; 101 | width: 118px; 102 | height: 118px; 103 | margin-top: -59px; 104 | margin-left: -59px; 105 | } 106 | """ 107 | logi_arx.logi_arx_init("com.logitech.gaming.logipy", "LogiPy") 108 | time.sleep(1) 109 | logi_arx.logi_arx_add_utf8_string_as(index, "index.html", "text/html") 110 | logi_arx.logi_arx_add_utf8_string_as(css, "style.css", "text/css") 111 | logi_arx.logi_arx_set_index("index.html") 112 | logi_arx.logi_arx_shutdown() 113 | 114 | Show a simple applet with a custom callback: 115 | 116 | :: 117 | 118 | from logipy import logi_arx 119 | import time 120 | import ctypes 121 | 122 | index = """ 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | """ 133 | css = """ 134 | body { 135 | background-color: black; 136 | } 137 | img { 138 | position: absolute; 139 | top: 50%; 140 | left: 50%; 141 | width: 118px; 142 | height: 118px; 143 | margin-top: -59px; 144 | margin-left: -59px; 145 | } 146 | """ 147 | def custom_callback(event_type, event_value, event_arg, context): 148 | if event_arg and event_arg == 'splash-icon': 149 | print "\nNo wonder Logitech is called Logicool in Japan! They are so cool!" 150 | 151 | logi_arx.logi_arx_init("com.logitech.gaming.logipy", "LogiPy", custom_callback) 152 | time.sleep(1) 153 | logi_arx.logi_arx_add_utf8_string_as(index, "index.html", "text/html") 154 | logi_arx.logi_arx_add_utf8_string_as(css, "style.css", "text/css") 155 | logi_arx.logi_arx_set_index("index.html") 156 | logi_arx.logi_arx_shutdown() 157 | -------------------------------------------------------------------------------- /logipy.pyproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | 2.0 6 | {df8701a7-975b-44a4-855f-899e95cde329} 7 | 8 | samples\logipy_samples.py 9 | 10 | . 11 | . 12 | {888888a0-9f3d-457c-b088-3a5042f75d52} 13 | Standard Python launcher 14 | 15 | 16 | logipy 17 | 18 | 19 | 20 | 21 | 10.0 22 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Code 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Code 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /logipy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logitech/logiPy/b9d8da74cefd026445db9bf363d0fab24149a726/logipy/__init__.py -------------------------------------------------------------------------------- /logipy/logi_arx.py: -------------------------------------------------------------------------------- 1 | """ 2 | logi_arx.py : Defines the exported functions for the API 3 | 4 | Logitech Gaming Arx Control SDK 5 | 6 | Copyright (C) 2011-2015 Logitech. All rights reserved. 7 | Author: Tom Lambert 8 | Email: devtechsupport@logitech.com 9 | """ 10 | 11 | import ctypes 12 | import os 13 | import platform 14 | 15 | # DLL Definitions 16 | # 17 | LOGI_ARX_ORIENTATION_PORTRAIT = 0x01 18 | LOGI_ARX_ORIENTATION_LANDSCAPE = 0x10 19 | 20 | LOGI_ARX_EVENT_FOCUS_ACTIVE = 0x01 21 | LOGI_ARX_EVENT_FOCUS_INACTIVE = 0x02 22 | LOGI_ARX_EVENT_TAP_ON_TAG = 0x04 23 | LOGI_ARX_EVENT_MOBILEDEVICE_ARRIVAL = 0x08 24 | LOGI_ARX_EVENT_MOBILEDEVICE_REMOVAL = 0x10 25 | 26 | LOGI_ARX_DEVICETYPE_IPHONE = 0x01 27 | LOGI_ARX_DEVICETYPE_IPAD = 0x02 28 | LOGI_ARX_DEVICETYPE_ANDROID_SMALL = 0x03 29 | LOGI_ARX_DEVICETYPE_ANDROID_NORMAL = 0x04 30 | LOGI_ARX_DEVICETYPE_ANDROID_LARGE = 0x05 31 | LOGI_ARX_DEVICETYPE_ANDROID_XLARGE = 0x06 32 | LOGI_ARX_DEVICETYPE_ANDROID_OTHER = 0x07 33 | 34 | CALLBACK_DEFINITION = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_int, ctypes.c_wchar_p, ctypes.c_void_p) 35 | 36 | class arxAppCallbackMessage(ctypes.Structure): 37 | """ creates a struct to match arxAppCallbackMessage. """ 38 | _fields_ = [ 39 | ('eventType', ctypes.c_int), 40 | ('eventValue', ctypes.c_int), 41 | ('eventArg', ctypes.c_wchar * 120) 42 | ] 43 | 44 | class logiArxCbContext(ctypes.Structure): 45 | """ creates a struct to match logiArxCbContext. """ 46 | _fields_ = [ 47 | ('arxCallBack', CALLBACK_DEFINITION), 48 | ('arxContext', ctypes.c_void_p) 49 | ] 50 | 51 | def callback_wrapper(event_type, event_value, event_arg, context): 52 | on_callback(event_type, event_value, event_arg, context) 53 | 54 | def default_callback(event_type, event_value, event_arg, context): 55 | print '\n[Arx] default_callback called with: event_type = {event_type}, event_value = {event_value}, event_arg = {event_arg}, context = {context}'.format( 56 | event_type = event_type, event_value = event_value, event_arg = event_arg, context = context) 57 | 58 | # Required Globals 59 | # 60 | class SDKNotFoundException: 61 | pass 62 | 63 | def load_dll(path_dll = None): 64 | if not path_dll: 65 | bitness = 'x86' if platform.architecture()[0] == '32bit' else 'x64' 66 | subpath_dll = r'/Logitech Gaming Software/SDK/Arx Control/{}/LogitechGArxControl.dll'.format(bitness) 67 | try: 68 | subpath_lgs = os.environ['ProgramW6432'] 69 | except KeyError: 70 | subpath_lgs = os.environ['ProgramFiles'] 71 | path_dll = subpath_lgs + subpath_dll 72 | if os.path.exists(path_dll): 73 | return ctypes.cdll.LoadLibrary(path_dll) 74 | else: 75 | raise SDKNotFoundException('The SDK DLL was not found.') 76 | 77 | try: 78 | arx_dll = load_dll() 79 | except SDKNotFoundException as exception_sdk: 80 | arx_dll = None 81 | on_callback = None 82 | 83 | 84 | # Wrapped SDK Functions 85 | # 86 | def logi_arx_init(identifier, friendly_name, py_callback_function = None): 87 | """ initializes the applet on the app with the given friendly_name. """ 88 | if arx_dll: 89 | global on_callback 90 | global callback_ref 91 | on_callback = py_callback_function if py_callback_function else default_callback 92 | callback_ref = ctypes.byref(CALLBACK_DEFINITION(callback_wrapper)) 93 | identifier = ctypes.c_wchar_p(identifier) 94 | friendly_name = ctypes.c_wchar_p(friendly_name) 95 | return bool(arx_dll.LogiArxInit(identifier, friendly_name, callback_ref)) 96 | else: 97 | return False 98 | 99 | def logi_arx_add_file_as(file_path, file_name, mime_type = None): 100 | """ sends a file to the device from local a file_path and assigns a file_name to it. mime_type, if assigned, specifies the MIME type of the file. """ 101 | if arx_dll: 102 | file_path = ctypes.c_wchar_p(file_path) 103 | file_name = ctypes.c_wchar_p(file_name) 104 | mime_type = ctypes.c_wchar_p(mime_type) if mime_type else ctypes.c_wchar_p('') 105 | return bool(arx_dll.LogiArxAddFileAs(file_path, file_name, mime_type)) 106 | else: 107 | return False 108 | 109 | def logi_arx_add_content_as(content, size, file_name, mime_type = None): 110 | """ sends content to the device and saves it to a virtual file called file_name. mime_type, if assigned, specifies the MIME type of the file. """ 111 | if arx_dll: 112 | content = ctypes.c_void_p(content) 113 | size = ctypes.c_int(size) 114 | file_name = ctypes.c_wchar_p(file_name) 115 | mime_type = ctypes.c_wchar_p(mime_type) if mime_type else ctypes.c_wchar_p('') 116 | return bool(arx_dll.LogiArxAddContentAs(content, size, file_name, mime_type)) 117 | else: 118 | return False 119 | 120 | def logi_arx_add_utf8_string_as(string_content, file_name, mime_type = None): 121 | """ sends a UTF8 string to the device and saves it to a virtual file called file_name. mime_type, if assigned, specifies the MIME type of the file. """ 122 | if arx_dll: 123 | string_content = ctypes.c_wchar_p(string_content) 124 | file_name = ctypes.c_wchar_p(file_name) 125 | mime_type = ctypes.c_wchar_p(mime_type) if mime_type else ctypes.c_wchar_p('') 126 | return bool(arx_dll.LogiArxAddUTF8StringAs(string_content, file_name, mime_type)) 127 | else: 128 | return False 129 | 130 | def logi_arx_add_image_from_bitmap(bitmap, width, height, file_name): 131 | """ compresses the image specified by the BGRA byte array bitmap (interpretting the array using width and height) into a png file with the name specified by file_name, 132 | then sends it over to the the device. note that the color bit order is BGRA rather than standard RGBA bit order. """ 133 | if arx_dll: 134 | bitmap = ctypes.c_char_p(bitmap) 135 | width = ctypes.c_int(width) 136 | height = ctypes.c_int(height) 137 | file_name = ctypes.c_wchar_p(file_name) 138 | return bool(arx_dll.LogiArxAddImageFromBitmap(bitmap, width, height, file_name)) 139 | else: 140 | return False 141 | 142 | def logi_arx_set_index(file_name): 143 | """ sets which of the sent files is the index. (first one to be displayed in the applet) """ 144 | if arx_dll: 145 | file_name = ctypes.c_wchar_p(file_name) 146 | return bool(arx_dll.LogiArxSetIndex(file_name)) 147 | else: 148 | return False 149 | 150 | def logi_arx_set_tag_property_by_id(tag_id, prop, new_value): 151 | """ change at runtime a prop (property) on the tag with the id tag_id from the old value to the new_value. """ 152 | if arx_dll: 153 | tag_id = ctypes.c_wchar_p(tag_id) 154 | prop = ctypes.c_wchar_p(prop) 155 | new_value = ctypes.c_wchar_p(new_value) 156 | return bool(arx_dll.LogiArxSetTagPropertyById(tag_id, prop, new_value)) 157 | else: 158 | return False 159 | 160 | def logi_arx_set_tags_property_by_class(tag_class, prop, new_value): 161 | """ change at runtime a prop (property) on the tag with the class tag_class from the old value to the new_value. """ 162 | if arx_dll: 163 | tag_class = ctypes.c_wchar_p(tag_class) 164 | prop = ctypes.c_wchar_p(prop) 165 | new_value = ctypes.c_wchar_p(new_value) 166 | return bool(arx_dll.LogiArxSetTagsPropertyByClass(tag_class, prop, new_value)) 167 | else: 168 | return False 169 | 170 | def logi_arx_set_tag_content_by_id(tag_id, new_content): 171 | """ change at runtime the content (innerHTML) of a tag with the id tag_id from the old content to the new_content. """ 172 | if arx_dll: 173 | tag_id = ctypes.c_wchar_p(tag_id) 174 | new_content = ctypes.c_wchar_p(new_content) 175 | return bool(arx_dll.LogiArxSetTagContentById(tag_id, new_content)) 176 | else: 177 | return False 178 | 179 | def logi_arx_set_tags_content_by_class(tag_class, new_content): 180 | """ change at runtime the content (innerHTML) of a tag with the class tag_class from the old content to the new_content. """ 181 | if arx_dll: 182 | tag_class = ctypes.c_wchar_p(tag_class) 183 | new_content = ctypes.c_wchar_p(new_content) 184 | return bool(arx_dll.LogiArxSetTagsPropertyByClass(tag_class, new_content)) 185 | else: 186 | return False 187 | 188 | def logi_arx_get_last_error(): 189 | """ each function returns a bool. to get detailed info on the last error code, call this function. """ 190 | if arx_dll: 191 | return int(arx_dll.LogiArxGetLastError()) 192 | else: 193 | return False 194 | 195 | def logi_arx_shutdown(): 196 | """ shuts down the applet on the app. """ 197 | if arx_dll: 198 | arx_dll.LogiArxShutdown() 199 | return True 200 | else: 201 | return False -------------------------------------------------------------------------------- /logipy/logi_led.py: -------------------------------------------------------------------------------- 1 | """ 2 | logi_led.py : Defines the exported functions for the API 3 | 4 | Logitech Gaming LED SDK 5 | 6 | Copyright (C) 2011-2015 Logitech. All rights reserved. 7 | Author: Tom Lambert 8 | Email: devtechsupport@logitech.com 9 | """ 10 | 11 | import ctypes 12 | import os 13 | import platform 14 | import struct 15 | 16 | # Helpers 17 | # 18 | class Color: 19 | """ an RGBA color object that can be created using RGB, RGBA, color name, or a hex_code. """ 20 | def __init__(self, *args, **kwargs): 21 | red, green, blue, alpha = 0, 0, 0, 255 22 | hex_code = None 23 | if len(args) > 0: 24 | if isinstance(args[0], int): 25 | red, green, blue = args[0], args[1], args[2] 26 | if len(args) > 3: 27 | alpha = args[3] 28 | elif isinstance(args[0], str): 29 | if len(args) > 1: 30 | alpha = args[1] 31 | if args[0] == 'red': 32 | red, green, blue = 255, 0, 0 33 | elif args[0] == 'orange': 34 | red, green, blue = 255, 165, 0 35 | elif args[0] == 'yellow': 36 | red, green, blue = 255, 255, 0 37 | elif args[0] == 'green': 38 | red, green, blue = 0, 255, 0 39 | elif args[0] == 'blue': 40 | red, green, blue = 0, 0, 255 41 | elif args[0] == 'indigo': 42 | red, green, blue = 75, 0, 130 43 | elif args[0] == 'violet': 44 | red, green, blue = 238, 130, 238 45 | elif args[0] == 'cyan': 46 | red, green, blue = 0, 220, 255 47 | elif args[0] == 'pink': 48 | red, green, blue = 255, 0, 255 49 | elif args[0] == 'purple': 50 | red, green, blue = 128, 0, 255 51 | elif args[0] == 'white': 52 | red, green, blue = 255, 255, 255 53 | elif args[0] == 'black': 54 | red, green, blue = 0, 0, 0 55 | else: 56 | hex_code = args[0] 57 | hex_code = kwargs.pop('hex', hex_code) 58 | if hex_code: 59 | hex_code = hex_code.replace('#', '') 60 | self.red, self.green, self.blue = struct.unpack('BBB', hex_code.decode('hex')) 61 | self.alpha = alpha 62 | elif any(x in ['red', 'blue', 'green', 'alpha'] for x in kwargs): 63 | self.red = kwargs.pop('red', red) 64 | self.green = kwargs.pop('green', green) 65 | self.blue = kwargs.pop('blue', blue) 66 | self.alpha = kwargs.pop('alpha', alpha) 67 | else: 68 | self.red = red 69 | self.green = green 70 | self.blue = blue 71 | self.alpha = alpha 72 | self.hex_code = '#{h}'.format(h=struct.pack('BBB', *(self.red, self.green, self.blue)).encode('hex')) 73 | 74 | def rgb_percent(self): 75 | return int((self.red / 255.0) * 100), int((self.green / 255.0) * 100), int((self.blue / 255.0) * 100) 76 | 77 | 78 | # DLL Definitions 79 | # 80 | ESC = 0x01 81 | F1 = 0x3b 82 | F2 = 0x3c 83 | F3 = 0x3d 84 | F4 = 0x3e 85 | F5 = 0x3f 86 | F6 = 0x40 87 | F7 = 0x41 88 | F8 = 0x42 89 | F9 = 0x43 90 | F10 = 0x44 91 | F11 = 0x57 92 | F12 = 0x58 93 | PRINT_SCREEN = 0x137 94 | SCROLL_LOCK = 0x46 95 | PAUSE_BREAK = 0x145 96 | TILDE = 0x29 97 | ONE = 0x02 98 | TWO = 0x03 99 | THREE = 0x04 100 | FOUR = 0x05 101 | FIVE = 0x06 102 | SIX = 0x07 103 | SEVEN = 0x08 104 | EIGHT = 0x09 105 | NINE = 0x0a 106 | ZERO = 0x0b 107 | MINUS = 0x0c 108 | EQUALS = 0x0d 109 | BACKSPACE = 0x0e 110 | INSERT = 0x152 111 | HOME = 0x147 112 | PAGE_UP = 0x149 113 | NUM_LOCK = 0x45 114 | NUM_SLASH = 0x135 115 | NUM_ASTERISK = 0x37 116 | NUM_MINUS = 0x4a 117 | TAB = 0x0f 118 | Q = 0x10 119 | W = 0x11 120 | E = 0x12 121 | R = 0x13 122 | T = 0x14 123 | Y = 0x15 124 | U = 0x16 125 | I = 0x17 126 | O = 0x18 127 | P = 0x19 128 | OPEN_BRACKET = 0x1a 129 | CLOSE_BRACKET = 0x1b 130 | BACKSLASH = 0x2b 131 | KEYBOARD_DELETE = 0x153 132 | END = 0x14f 133 | PAGE_DOWN = 0x151 134 | NUM_SEVEN = 0x47 135 | NUM_EIGHT = 0x48 136 | NUM_NINE = 0x49 137 | NUM_PLUS = 0x4e 138 | CAPS_LOCK = 0x3a 139 | A = 0x1e 140 | S = 0x1f 141 | D = 0x20 142 | F = 0x21 143 | G = 0x22 144 | H = 0x23 145 | J = 0x24 146 | K = 0x25 147 | L = 0x26 148 | SEMICOLON = 0x27 149 | APOSTROPHE = 0x28 150 | ENTER = 0x1c 151 | NUM_FOUR = 0x4b 152 | NUM_FIVE = 0x4c 153 | NUM_SIX = 0x4d 154 | LEFT_SHIFT = 0x2a 155 | Z = 0x2c 156 | X = 0x2d 157 | C = 0x2e 158 | V = 0x2f 159 | B = 0x30 160 | N = 0x31 161 | M = 0x32 162 | COMMA = 0x33 163 | PERIOD = 0x34 164 | FORWARD_SLASH = 0x35 165 | RIGHT_SHIFT = 0x36 166 | ARROW_UP = 0x148 167 | NUM_ONE = 0x4f 168 | NUM_TWO = 0x50 169 | NUM_THREE = 0x51 170 | NUM_ENTER = 0x11c 171 | LEFT_CONTROL = 0x1d 172 | LEFT_WINDOWS = 0x15b 173 | LEFT_ALT = 0x38 174 | SPACE = 0x39 175 | RIGHT_ALT = 0x138 176 | RIGHT_WINDOWS = 0x15c 177 | APPLICATION_SELECT = 0x15d 178 | RIGHT_CONTROL = 0x11d 179 | ARROW_LEFT = 0x14b 180 | ARROW_DOWN = 0x150 181 | ARROW_RIGHT = 0x14d 182 | NUM_ZERO = 0x52 183 | NUM_PERIOD = 0x53 184 | G_1 = 0xFFF1 185 | G_2 = 0xFFF2 186 | G_3 = 0xFFF3 187 | G_4 = 0xFFF4 188 | G_5 = 0xFFF5 189 | G_6 = 0xFFF6 190 | G_7 = 0xFFF7 191 | G_8 = 0xFFF8 192 | G_9 = 0xFFF9 193 | G_LOGO = 0xFFFF1 194 | G_BADGE = 0xFFFF2 195 | 196 | LOGI_LED_BITMAP_WIDTH = 21 197 | LOGI_LED_BITMAP_HEIGHT = 6 198 | LOGI_LED_BITMAP_BYTES_PER_KEY = 4 199 | 200 | LOGI_LED_BITMAP_SIZE = LOGI_LED_BITMAP_WIDTH * LOGI_LED_BITMAP_HEIGHT * LOGI_LED_BITMAP_BYTES_PER_KEY 201 | 202 | LOGI_LED_DURATION_INFINITE = 0 203 | 204 | LOGI_DEVICETYPE_MONOCHROME_ORD = 0 205 | LOGI_DEVICETYPE_RGB_ORD = 1 206 | LOGI_DEVICETYPE_PERKEY_RGB_ORD = 2 207 | 208 | LOGI_DEVICETYPE_MONOCHROME = 1 << LOGI_DEVICETYPE_MONOCHROME_ORD 209 | LOGI_DEVICETYPE_RGB = 1 << LOGI_DEVICETYPE_RGB_ORD 210 | LOGI_DEVICETYPE_PERKEY_RGB = 1 << LOGI_DEVICETYPE_PERKEY_RGB_ORD 211 | 212 | LOGI_DEVICETYPE_ALL = LOGI_DEVICETYPE_MONOCHROME | LOGI_DEVICETYPE_RGB | LOGI_DEVICETYPE_PERKEY_RGB 213 | 214 | 215 | # Required Globals 216 | # 217 | _LOGI_SHARED_SDK_LED = ctypes.c_int(1) 218 | 219 | class SDKNotFoundException: 220 | pass 221 | 222 | def load_dll(path_dll = None): 223 | if not path_dll: 224 | bitness = 'x86' if platform.architecture()[0] == '32bit' else 'x64' 225 | subpath_dll = r'/Logitech Gaming Software/SDK/LED/{}/LogitechLed.dll'.format(bitness) 226 | try: 227 | subpath_lgs = os.environ['ProgramW6432'] 228 | except KeyError: 229 | subpath_lgs = os.environ['ProgramFiles'] 230 | path_dll = subpath_lgs + subpath_dll 231 | if os.path.exists(path_dll): 232 | return ctypes.cdll.LoadLibrary(path_dll) 233 | else: 234 | raise SDKNotFoundException('The SDK DLL was not found.') 235 | 236 | try: 237 | led_dll = load_dll() 238 | except SDKNotFoundException as exception_sdk: 239 | led_dll = None 240 | 241 | 242 | # Wrapped SDK Functions 243 | # 244 | def logi_led_init(): 245 | """ initializes the sdk for the current thread. """ 246 | if led_dll: 247 | return bool(led_dll.LogiLedInit()) 248 | else: 249 | return False 250 | 251 | def logi_led_set_target_device(target_device): 252 | """ sets the target device or device group that is affected by the subsequent lighting calls. """ 253 | if led_dll: 254 | target_device = ctypes.c_int(target_device) 255 | return bool(led_dll.LogiLedSetTargetDevice(target_device)) 256 | else: 257 | return False 258 | 259 | def logi_led_save_current_lighting(): 260 | """ saves the current lighting that can be restored later. """ 261 | if led_dll: 262 | return bool(led_dll.LogiLedSaveCurrentLighting()) 263 | else: 264 | return False 265 | 266 | def logi_led_restore_lighting(): 267 | """ restores the last saved lighting. """ 268 | if led_dll: 269 | return bool(led_dll.LogiLedRestoreLighting()) 270 | else: 271 | return False 272 | 273 | def logi_led_set_lighting(red_percentage, green_percentage, blue_percentage): 274 | """ sets the lighting to the color of the combined RGB percentages. note that RGB ranges from 0-255, but this function ranges from 0-100. """ 275 | if led_dll: 276 | red_percentage = ctypes.c_int(red_percentage) 277 | green_percentage = ctypes.c_int(green_percentage) 278 | blue_percentage = ctypes.c_int(blue_percentage) 279 | return bool(led_dll.LogiLedSetLighting(red_percentage, green_percentage, blue_percentage)) 280 | else: 281 | return False 282 | 283 | def logi_led_flash_lighting(red_percentage, green_percentage, blue_percentage, ms_duration, ms_interval): 284 | """ flashes the lighting color of the combined RGB percentages over the specified millisecond duration and millisecond interval. 285 | specifying a duration of 0 will cause the effect to be infinite until reset. note that RGB ranges from 0-255, but this function ranges from 0-100. """ 286 | if led_dll: 287 | red_percentage = ctypes.c_int(red_percentage) 288 | green_percentage = ctypes.c_int(green_percentage) 289 | blue_percentage = ctypes.c_int(blue_percentage) 290 | ms_duration = ctypes.c_int(ms_duration) 291 | ms_interval = ctypes.c_int(ms_interval) 292 | return bool(led_dll.LogiLedFlashLighting(red_percentage, green_percentage, blue_percentage, ms_duration, ms_interval)) 293 | else: 294 | return False 295 | 296 | def logi_led_pulse_lighting(red_percentage, green_percentage, blue_percentage, ms_duration, ms_interval): 297 | """ pulses the lighting color of the combined RGB percentages over the specified millisecond duration and millisecond interval. 298 | specifying a duration of 0 will cause the effect to be infinite until reset. note that RGB ranges from 0-255, but this function ranges from 0-100. """ 299 | if led_dll: 300 | red_percentage = ctypes.c_int(red_percentage) 301 | green_percentage = ctypes.c_int(green_percentage) 302 | blue_percentage = ctypes.c_int(blue_percentage) 303 | ms_duration = ctypes.c_int(ms_duration) 304 | ms_interval = ctypes.c_int(ms_interval) 305 | return bool(led_dll.LogiLedPulseLighting(red_percentage, green_percentage, blue_percentage, ms_duration, ms_interval)) 306 | else: 307 | return False 308 | 309 | def logi_led_stop_effects(): 310 | """ stops the pulse and flash effects. """ 311 | if led_dll: 312 | return bool(led_dll.LogiLedStopEffects()) 313 | else: 314 | return False 315 | 316 | def logi_led_set_lighting_from_bitmap(bitmap): 317 | """ sets the color of each key in a 21x6 rectangular area specified by the BGRA byte array bitmap. each element corresponds to the physical location of each key. 318 | note that the color bit order is BGRA rather than standard RGBA bit order. this function only applies to LOGI_DEVICETYPE_PERKEY_RGB devices. """ 319 | if led_dll: 320 | bitmap = ctypes.c_char_p(bitmap) 321 | return bool(led_dll.LogiLedSetLightingFromBitmap(bitmap)) 322 | else: 323 | return False 324 | 325 | def logi_led_set_lighting_for_key_with_scan_code(key_code, red_percentage, green_percentage, blue_percentage): 326 | """ sets the lighting to the color of the combined RGB percentages for the specified key code. note that RGB ranges from 0-255, but this function ranges from 0-100. 327 | this function only applies to LOGI_DEVICETYPE_PERKEY_RGB devices. """ 328 | if led_dll: 329 | key_code = ctypes.c_int(key_code) 330 | red_percentage = ctypes.c_int(red_percentage) 331 | green_percentage = ctypes.c_int(green_percentage) 332 | blue_percentage = ctypes.c_int(blue_percentage) 333 | return bool(led_dll.LogiLedSetLightingForKeyWithScanCode(key_code, red_percentage, green_percentage, blue_percentage)) 334 | else: 335 | return False 336 | 337 | def logi_led_set_lighting_for_key_with_hid_code(key_code, red_percentage, green_percentage, blue_percentage): 338 | """ sets the lighting to the color of the combined RGB percentages for the specified key code. note that RGB ranges from 0-255, but this function ranges from 0-100. 339 | this function only applies to LOGI_DEVICETYPE_PERKEY_RGB devices. """ 340 | if led_dll: 341 | key_code = ctypes.c_int(key_code) 342 | red_percentage = ctypes.c_int(red_percentage) 343 | green_percentage = ctypes.c_int(green_percentage) 344 | blue_percentage = ctypes.c_int(blue_percentage) 345 | return bool(led_dll.LogiLedSetLightingForKeyWithHidCode(key_code, red_percentage, green_percentage, blue_percentage)) 346 | else: 347 | return False 348 | 349 | def logi_led_set_lighting_for_key_with_quartz_code(key_code, red_percentage, green_percentage, blue_percentage): 350 | """ sets the lighting to the color of the combined RGB percentages for the specified key code. note that RGB ranges from 0-255, but this function ranges from 0-100. 351 | this function only applies to LOGI_DEVICETYPE_PERKEY_RGB devices. """ 352 | if led_dll: 353 | key_code = ctypes.c_int(key_code) 354 | red_percentage = ctypes.c_int(red_percentage) 355 | green_percentage = ctypes.c_int(green_percentage) 356 | blue_percentage = ctypes.c_int(blue_percentage) 357 | return bool(led_dll.LogiLedSetLightingForKeyWithQuartzCode(key_code, red_percentage, green_percentage, blue_percentage)) 358 | else: 359 | return False 360 | 361 | def logi_led_set_lighting_for_key_with_key_name(key_name, red_percentage, green_percentage, blue_percentage): 362 | """ sets the lighting to the color of the combined RGB percentages for the specified key name. note that RGB ranges from 0-255, but this function ranges from 0-100. 363 | this function only applies to LOGI_DEVICETYPE_PERKEY_RGB devices. """ 364 | if led_dll: 365 | key_name = ctypes.c_int(key_name) 366 | red_percentage = ctypes.c_int(red_percentage) 367 | green_percentage = ctypes.c_int(green_percentage) 368 | blue_percentage = ctypes.c_int(blue_percentage) 369 | return bool(led_dll.LogiLedSetLightingForKeyWithKeyName(key_name, red_percentage, green_percentage, blue_percentage)) 370 | else: 371 | return False 372 | 373 | def logi_led_save_lighting_for_key(key_name): 374 | """ saves the current lighting for the specified key name that can be restored later. this function only applies to LOGI_DEVICETYPE_PERKEY_RGB devices. """ 375 | if led_dll: 376 | key_name = ctypes.c_int(key_name) 377 | return bool(led_dll.LogiLedSaveLightingForKey(key_name)) 378 | else: 379 | return False 380 | 381 | def logi_led_restore_lighting_for_key(key_name): 382 | """ restores the last saved lighting for the specified key name. this function only applies to LOGI_DEVICETYPE_PERKEY_RGB devices. """ 383 | if led_dll: 384 | key_name = ctypes.c_int(key_name) 385 | return bool(led_dll.LogiLedRestoreLightingForKey(key_name)) 386 | else: 387 | return False 388 | 389 | def logi_led_flash_single_key(key_name, red_percentage, green_percentage, blue_percentage, ms_duration, ms_interval): 390 | """ flashes the lighting color of the combined RGB percentages over the specified millisecond duration and millisecond interval for the specified key name. 391 | specifying a duration of 0 will cause the effect to be infinite until reset. note that RGB ranges from 0-255, but this function ranges from 0-100. 392 | this function only applies to LOGI_DEVICETYPE_PERKEY_RGB devices. """ 393 | if led_dll: 394 | key_name = ctypes.c_int(key_name) 395 | red_percentage = ctypes.c_int(red_percentage) 396 | green_percentage = ctypes.c_int(green_percentage) 397 | blue_percentage = ctypes.c_int(blue_percentage) 398 | ms_duration = ctypes.c_int(ms_duration) 399 | ms_interval = ctypes.c_int(ms_interval) 400 | return bool(led_dll.LogiLedFlashSingleKey(key_name, red_percentage, green_percentage, blue_percentage, ms_duration, ms_interval)) 401 | else: 402 | return False 403 | 404 | def logi_led_pulse_single_key(key_name, red_percentage_start, green_percentage_start, blue_percentage_start, ms_duration, is_infinite = False, red_percentage_end = 0, green_percentage_end = 0, blue_percentage_end = 0): 405 | """ pulses the lighting color of the combined RGB percentages over the specified millisecond duration for the specified key name. 406 | the color will gradually change from the starting color to the ending color. if no ending color is specified, the ending color will be black. 407 | the effect will stop after one interval unless is_infinite is set to True. note that RGB ranges from 0-255, but this function ranges from 0-100. 408 | this function only applies to LOGI_DEVICETYPE_PERKEY_RGB devices. """ 409 | if led_dll: 410 | key_name = ctypes.c_int(key_name) 411 | red_percentage_start = ctypes.c_int(red_percentage_start) 412 | green_percentage_start = ctypes.c_int(green_percentage_start) 413 | blue_percentage_start = ctypes.c_int(blue_percentage_start) 414 | red_percentage_end = ctypes.c_int(red_percentage_end) 415 | green_percentage_end = ctypes.c_int(green_percentage_end) 416 | blue_percentage_end = ctypes.c_int(blue_percentage_end) 417 | ms_duration = ctypes.c_int(ms_duration) 418 | is_infinite = ctypes.c_bool(is_infinite) 419 | return bool(led_dll.LogiLedPulseSingleKey(key_name, red_percentage_start, green_percentage_start, blue_percentage_start, red_percentage_end, green_percentage_end, blue_percentage_end, ms_duration, is_infinite)) 420 | else: 421 | return False 422 | 423 | def logi_led_stop_effects_on_key(key_name): 424 | """ stops the pulse and flash effects on a single key. """ 425 | if led_dll: 426 | key_name = ctypes.c_int(key_name) 427 | return bool(led_dll.LogiLedStopEffectsOnKey(key_name)) 428 | else: 429 | return False 430 | 431 | def logi_led_shutdown(): 432 | """ shutdowns the SDK for the thread. """ 433 | if led_dll: 434 | return bool(led_dll.LogiLedShutdown()) 435 | else: 436 | return False 437 | 438 | def logi_led_get_config_option_number(key, default=0): 439 | """ get the default value for the key as a number. if the call fails, the return value is None. 440 | 441 | for example, get the low health threshold: 442 | logi_led_get_config_option_number('health/low_health_threshold', 20.0) """ 443 | if led_dll: 444 | key = ctypes.c_wchar_p(key) 445 | default = ctypes.c_double(default) 446 | if led_dll.LogiGetConfigOptionNumber(key, ctypes.pointer(default), _LOGI_SHARED_SDK_LED): 447 | return default.value 448 | return None 449 | 450 | def logi_led_get_config_option_bool(key, default=False): 451 | """ get the default value for the key as a bool. if the call fails, the return value is None. 452 | 453 | for example, check if the effect is enabled: 454 | logi_led_get_config_option_bool('health/pulse_on_low', True) """ 455 | if led_dll: 456 | key = ctypes.c_wchar_p(key) 457 | default = ctypes.c_bool(default) 458 | if led_dll.LogiGetConfigOptionBool(key, ctypes.pointer(default), _LOGI_SHARED_SDK_LED): 459 | return default.value 460 | return None 461 | 462 | def logi_led_get_config_option_color(key, *args): 463 | """ get the default value for the key as a color. if the call fails, the return value is None. 464 | note this function can either be called with red_percentage, green_percentage, and blue_percentage or with the logi_led Color object. 465 | 466 | for example, get the low health color: 467 | logi_led_get_config_option_color('health/pulse_color', 100, 0, 0) 468 | logi_led_get_config_option_color('health/pulse_color', Color('red')) 469 | logi_led_get_config_option_color('health/pulse_color', Color('#ff0000')) 470 | logi_led_get_config_option_color('health/pulse_color', Color(255, 0, 0)) """ 471 | if led_dll: 472 | key = ctypes.c_wchar_p(key) 473 | default = None 474 | red_percentage = 0 475 | green_percentage = 0 476 | blue_percentage = 0 477 | if isinstance(args[0], Color): 478 | default = args[0] 479 | else: 480 | red_percentage = args[0] 481 | green_percentage = args[1] 482 | blue_percentage = args[2] 483 | if default: 484 | red = ctypes.c_int(default.red) 485 | green = ctypes.c_int(default.green) 486 | blue = ctypes.c_int(default.blue) 487 | else: 488 | red = ctypes.c_int(int((red_percentage / 100.0) * 255)) 489 | green = ctypes.c_int(int((green_percentage / 100.0) * 255)) 490 | blue = ctypes.c_int(int((blue_percentage / 100.0) * 255)) 491 | if led_dll.LogiGetConfigOptionColor(key, ctypes.pointer(red), ctypes.pointer(green), ctypes.pointer(blue), _LOGI_SHARED_SDK_LED): 492 | return Color(red.value, green.value, blue.value) 493 | return None 494 | 495 | def logi_led_get_config_option_key_input(key, default=''): 496 | """ get the default value for the key as a key input. if the call fails, the return value is None. 497 | 498 | for example, get the primary ability key input: 499 | logi_led_get_config_option_key_input('abilities/primary', 'A') """ 500 | if led_dll: 501 | key = ctypes.c_wchar_p(key) 502 | default_key = ctypes.create_string_buffer(256) 503 | default_key.value = default 504 | if led_dll.LogiGetConfigOptionKeyInput(key, default_key, _LOGI_SHARED_SDK_LED): 505 | return str(default_key.value) 506 | return None 507 | 508 | def logi_led_set_config_option_label(key, label): 509 | """ set the label for a key. 510 | 511 | for example, label 'health/pulse_on_low' as 'Health - Pulse on Low': 512 | logi_led_set_config_option_label('health', 'Health') 513 | logi_led_set_config_option_label('health/pulse_on_low', 'Pulse on Low') """ 514 | if led_dll: 515 | key = ctypes.c_wchar_p(key) 516 | label = ctypes.c_wchar_p(label) 517 | return bool(led_dll.LogiSetConfigOptionLabel(key, label, _LOGI_SHARED_SDK_LED)) 518 | else: 519 | return False 520 | -------------------------------------------------------------------------------- /samples/logipy_samples.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 5 | 6 | 7 | # LED snippets 8 | ############## 9 | 10 | # Set all device lighting to red 11 | from logipy import logi_led 12 | import time 13 | import ctypes 14 | 15 | print ('Setting all device lighting to red...') 16 | logi_led.logi_led_init() 17 | time.sleep(1) # Give the SDK a second to initialize 18 | logi_led.logi_led_set_lighting(100, 0, 0) 19 | raw_input('Press enter to shutdown SDK...') 20 | logi_led.logi_led_shutdown() 21 | 22 | # If you prefer the c/c++ style you can use the DLL directly 23 | print ('Setting all device lighting to green...') 24 | logi_led.led_dll.LogiLedInit() 25 | time.sleep(1) # Give the SDK a second to initialize 26 | logi_led.led_dll.LogiLedSetLighting(ctypes.c_int(0), ctypes.c_int(100), ctypes.c_int(0)) 27 | raw_input('Press enter to shutdown SDK...') 28 | logi_led.led_dll.LogiLedShutdown() 29 | 30 | 31 | # Arx snippets 32 | ############## 33 | 34 | # Show a simple applet with the default callback 35 | # note: will not work if a callback has already been defined by the process 36 | from logipy import logi_arx 37 | import time 38 | print 'Setting up a simple applet...' 39 | index = """ 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | """ 50 | css = """ 51 | body { 52 | background-color: black; 53 | } 54 | img { 55 | position: absolute; 56 | top: 50%; 57 | left: 50%; 58 | width: 118px; 59 | height: 118px; 60 | margin-top: -59px; 61 | margin-left: -59px; 62 | } 63 | """ 64 | logi_arx.logi_arx_init('com.logitech.gaming.logipy', 'LogiPy') 65 | time.sleep(1) 66 | logi_arx.logi_arx_add_utf8_string_as(index, 'index.html', 'text/html') 67 | logi_arx.logi_arx_add_utf8_string_as(css, 'style.css', 'text/css') 68 | logi_arx.logi_arx_set_index('index.html') 69 | raw_input('Press enter to shutdown SDK...') 70 | logi_arx.logi_arx_shutdown() 71 | 72 | # Show a simple applet with a custom callback 73 | from logipy import logi_arx 74 | import time 75 | import ctypes 76 | 77 | print 'Setting up a simple applet with custom callback...' 78 | index = """ 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | """ 89 | css = """ 90 | body { 91 | background-color: black; 92 | } 93 | img { 94 | position: absolute; 95 | top: 50%; 96 | left: 50%; 97 | width: 118px; 98 | height: 118px; 99 | margin-top: -59px; 100 | margin-left: -59px; 101 | } 102 | """ 103 | def custom_callback(event_type, event_value, event_arg, context): 104 | if event_arg and event_arg == 'splash-icon': 105 | print '\nNo wonder Logitech is called Logicool in Japan! They are so cool!' 106 | 107 | logi_arx.logi_arx_init('com.logitech.gaming.logipy', 'LogiPy', custom_callback) 108 | time.sleep(1) 109 | logi_arx.logi_arx_add_utf8_string_as(index, 'index.html', 'text/html') 110 | logi_arx.logi_arx_add_utf8_string_as(css, 'style.css', 'text/css') 111 | logi_arx.logi_arx_set_index('index.html') 112 | raw_input('Press enter to shutdown SDK...') 113 | logi_arx.logi_arx_shutdown() -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | 4 | setup( 5 | name = 'logipy', 6 | version = '1.3.1', 7 | author = 'Tom Lambert', 8 | author_email = 'devtechsupport@logitech.com', 9 | description = ('A simple python wrapper for Logitech G\'s LED and Arx SDKs.'), 10 | long_description = open('README.rst').read(), 11 | keywords = ['logi', 'logipy', 'Logitech', 'LogitechG', 'LED', 'Arx', 'LGS', 'Logitech Gaming Software'], 12 | url = 'http://gaming.logitech.com/en-us/developers', 13 | download_url = 'https://github.com/Logitech/logiPy/tarball/master', 14 | packages=['logipy'], 15 | classifiers=[ 16 | 'Development Status :: 5 - Production/Stable', 17 | 'Topic :: Software Development :: Libraries', 18 | ], 19 | license='MIT', 20 | ) --------------------------------------------------------------------------------