├── .gitignore ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── ass └── screenshot_capslock.jpg ├── bin └── maclight ├── ext └── maclight │ ├── Makefile │ ├── extconf.rb │ └── maclight.c ├── lib └── maclight │ ├── cli.rb │ └── version.rb └── maclight.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | *.bundle 4 | *.o 5 | .bundle 6 | .config 7 | .yardoc 8 | Gemfile.lock 9 | InstalledFiles 10 | _yardoc 11 | coverage 12 | doc/ 13 | lib/bundler/man 14 | pkg 15 | rdoc 16 | spec/reports 17 | test/tmp 18 | test/version_tmp 19 | tmp 20 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in maclight.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The file ext/maclight/maclight.c is derived from 2 | http://osxbook.com/book/bonus/chapter10/kbdleds/download/keyboard_leds.c 3 | which is Copyright (c) 2007,2008 Amit Singh. 4 | 5 | All remaining files are Copyright (c) 2012 moe@busyloop.net 6 | and provided under MIT license; 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MacLight [![Gem Version](https://badge.fury.io/rb/maclight.svg)](https://badge.fury.io/rb/maclight) 2 | 3 | MacLight lets you control the keyboard LEDs on your Mac or Macbook. 4 | 5 | ## Screenshot 6 | 7 | ![capslock](https://github.com/busyloop/maclight/raw/master/ass/screenshot_capslock.jpg?raw=true) 8 | 9 | ## Installation 10 | 11 | $ gem install maclight 12 | 13 | ## Usage 14 | 15 | ``` 16 | $ maclight --help 17 | 18 | Usage: maclight 19 | 20 | MacLight v3.0.0 - LED control utility 21 | 22 | Options: 23 | --version: Print version and exit 24 | --help, -h: Show this message 25 | 26 | Commands: 27 | keyboard Control keyboard LEDs 28 | ``` 29 | 30 | 31 | ## API Usage 32 | 33 | ```ruby 34 | #!/usr/bin/env ruby 35 | 36 | require 'maclight' 37 | 38 | # Turn LEDs on 39 | MacLight.all_leds(true) 40 | 41 | sleep 2 42 | 43 | # Turn LEDs off 44 | MacLight.all_leds(false) 45 | ``` 46 | 47 | ## Notice 48 | 49 | MacLight can currently only toggle all LEDs at once 50 | and has only been tested on OSX 10.11.3 (El Capitan). 51 | 52 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /ass/screenshot_capslock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/busyloop/maclight/8b2f8c83fb5d3659ad852fb6945950125f2ef790/ass/screenshot_capslock.jpg -------------------------------------------------------------------------------- /bin/maclight: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'maclight/cli' 4 | 5 | MacLight::Cli.start 6 | 7 | -------------------------------------------------------------------------------- /ext/maclight/Makefile: -------------------------------------------------------------------------------- 1 | 2 | SHELL = /bin/sh 3 | 4 | #### Start of system configuration section. #### 5 | 6 | srcdir = . 7 | topdir = /Users/moe/.rbenv/versions/1.9.2-p290/include/ruby-1.9.1 8 | hdrdir = /Users/moe/.rbenv/versions/1.9.2-p290/include/ruby-1.9.1 9 | arch_hdrdir = /Users/moe/.rbenv/versions/1.9.2-p290/include/ruby-1.9.1/$(arch) 10 | VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby 11 | prefix = $(DESTDIR)/Users/moe/.rbenv/versions/1.9.2-p290 12 | rubylibprefix = $(libdir)/$(RUBY_BASE_NAME) 13 | exec_prefix = $(prefix) 14 | vendorhdrdir = $(rubyhdrdir)/vendor_ruby 15 | sitehdrdir = $(rubyhdrdir)/site_ruby 16 | rubyhdrdir = $(includedir)/$(RUBY_BASE_NAME)-$(ruby_version) 17 | vendordir = $(rubylibprefix)/vendor_ruby 18 | sitedir = $(rubylibprefix)/site_ruby 19 | ridir = $(datarootdir)/$(RI_BASE_NAME) 20 | mandir = $(datarootdir)/man 21 | localedir = $(datarootdir)/locale 22 | libdir = $(exec_prefix)/lib 23 | psdir = $(docdir) 24 | pdfdir = $(docdir) 25 | dvidir = $(docdir) 26 | htmldir = $(docdir) 27 | infodir = $(datarootdir)/info 28 | docdir = $(datarootdir)/doc/$(PACKAGE) 29 | oldincludedir = $(DESTDIR)/usr/include 30 | includedir = $(prefix)/include 31 | localstatedir = $(prefix)/var 32 | sharedstatedir = $(prefix)/com 33 | sysconfdir = $(prefix)/etc 34 | datadir = $(datarootdir) 35 | datarootdir = $(prefix)/share 36 | libexecdir = $(exec_prefix)/libexec 37 | sbindir = $(exec_prefix)/sbin 38 | bindir = $(exec_prefix)/bin 39 | rubylibdir = $(rubylibprefix)/$(ruby_version) 40 | archdir = $(rubylibdir)/$(arch) 41 | sitelibdir = $(sitedir)/$(ruby_version) 42 | sitearchdir = $(sitelibdir)/$(sitearch) 43 | vendorlibdir = $(vendordir)/$(ruby_version) 44 | vendorarchdir = $(vendorlibdir)/$(sitearch) 45 | 46 | CC = gcc 47 | CXX = g++ 48 | LIBRUBY = $(LIBRUBY_A) 49 | LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a 50 | LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME) 51 | LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static 52 | OUTFLAG = -o 53 | COUTFLAG = -o 54 | 55 | RUBY_EXTCONF_H = 56 | cflags = $(optflags) $(debugflags) $(warnflags) 57 | optflags = -O3 58 | debugflags = -ggdb 59 | warnflags = -Wextra -Wno-unused-parameter -Wno-parentheses -Wpointer-arith -Wwrite-strings -Wno-missing-field-initializers -Wshorten-64-to-32 -Wno-long-long 60 | CFLAGS = -fno-common $(cflags) -pipe 61 | INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir) 62 | DEFS = 63 | CPPFLAGS = -I'/Users/moe/.rbenv/versions/1.9.2-p290/include' -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE $(DEFS) $(cppflags) 64 | CXXFLAGS = $(CFLAGS) $(cxxflags) 65 | ldflags = -L. -L'/Users/moe/.rbenv/versions/1.9.2-p290/lib' -L/usr/local/lib -framework IOKit -framework CoreFoundation 66 | dldflags = -Wl,-undefined,dynamic_lookup -Wl,-multiply_defined,suppress -Wl,-flat_namespace 67 | ARCH_FLAG = 68 | DLDFLAGS = $(ldflags) $(dldflags) 69 | LDSHARED = $(CC) -dynamic -bundle 70 | LDSHAREDXX = $(CXX) -dynamic -bundle 71 | AR = ar 72 | EXEEXT = 73 | 74 | RUBY_BASE_NAME = ruby 75 | RUBY_INSTALL_NAME = ruby 76 | RUBY_SO_NAME = ruby 77 | arch = x86_64-darwin11.3.0 78 | sitearch = $(arch) 79 | ruby_version = 1.9.1 80 | ruby = /Users/moe/.rbenv/versions/1.9.2-p290/bin/ruby 81 | RUBY = $(ruby) 82 | RM = rm -f 83 | RM_RF = $(RUBY) -run -e rm -- -rf 84 | RMDIRS = $(RUBY) -run -e rmdir -- -p 85 | MAKEDIRS = mkdir -p 86 | INSTALL = /usr/bin/install -c 87 | INSTALL_PROG = $(INSTALL) -m 0755 88 | INSTALL_DATA = $(INSTALL) -m 644 89 | COPY = cp 90 | 91 | #### End of system configuration section. #### 92 | 93 | preload = 94 | 95 | libpath = . $(libdir) 96 | LIBPATH = -L. -L$(libdir) 97 | DEFFILE = 98 | 99 | CLEANFILES = mkmf.log 100 | DISTCLEANFILES = 101 | DISTCLEANDIRS = 102 | 103 | extout = 104 | extout_prefix = 105 | target_prefix = 106 | LOCAL_LIBS = 107 | LIBS = -lpthread -ldl -lobjc 108 | SRCS = maclight.c 109 | OBJS = maclight.o 110 | TARGET = maclight 111 | DLLIB = $(TARGET).bundle 112 | EXTSTATIC = 113 | STATIC_LIB = 114 | 115 | BINDIR = $(bindir) 116 | RUBYCOMMONDIR = $(sitedir)$(target_prefix) 117 | RUBYLIBDIR = $(sitelibdir)$(target_prefix) 118 | RUBYARCHDIR = $(sitearchdir)$(target_prefix) 119 | HDRDIR = $(rubyhdrdir)/ruby$(target_prefix) 120 | ARCHHDRDIR = $(rubyhdrdir)/$(arch)/ruby$(target_prefix) 121 | 122 | TARGET_SO = $(DLLIB) 123 | CLEANLIBS = $(TARGET).bundle 124 | CLEANOBJS = *.o *.bak 125 | 126 | all: $(DLLIB) 127 | static: $(STATIC_LIB) 128 | .PHONY: all install static install-so install-rb 129 | .PHONY: clean clean-so clean-rb 130 | 131 | clean-rb-default:: 132 | clean-rb:: 133 | clean-so:: 134 | clean: clean-so clean-rb-default clean-rb 135 | @-$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) 136 | 137 | distclean-rb-default:: 138 | distclean-rb:: 139 | distclean-so:: 140 | distclean: clean distclean-so distclean-rb-default distclean-rb 141 | @-$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log 142 | @-$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES) 143 | @-$(RMDIRS) $(DISTCLEANDIRS) 144 | 145 | realclean: distclean 146 | install: install-so install-rb 147 | 148 | install-so: $(RUBYARCHDIR) 149 | install-so: $(RUBYARCHDIR)/$(DLLIB) 150 | $(RUBYARCHDIR)/$(DLLIB): $(DLLIB) 151 | @-$(MAKEDIRS) $(@D) 152 | $(INSTALL_PROG) $(DLLIB) $(@D) 153 | install-rb: pre-install-rb install-rb-default 154 | install-rb-default: pre-install-rb-default 155 | pre-install-rb: Makefile 156 | pre-install-rb-default: Makefile 157 | pre-install-rb-default: $(RUBYLIBDIR) 158 | install-rb-default: $(RUBYLIBDIR)/extconf.rb 159 | $(RUBYLIBDIR)/extconf.rb: $(srcdir)/lib/extconf.rb 160 | @-$(MAKEDIRS) $(@D) 161 | $(INSTALL_DATA) $(srcdir)/lib/extconf.rb $(@D) 162 | install-rb-default: $(RUBYLIBDIR)/test.rb 163 | $(RUBYLIBDIR)/test.rb: $(srcdir)/lib/test.rb 164 | @-$(MAKEDIRS) $(@D) 165 | $(INSTALL_DATA) $(srcdir)/lib/test.rb $(@D) 166 | $(RUBYARCHDIR): 167 | $(MAKEDIRS) $@ 168 | $(RUBYLIBDIR): 169 | $(MAKEDIRS) $@ 170 | 171 | site-install: site-install-so site-install-rb 172 | site-install-so: install-so 173 | site-install-rb: install-rb 174 | 175 | .SUFFIXES: .c .m .cc .cxx .cpp .C .o 176 | 177 | .cc.o: 178 | $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $< 179 | 180 | .cxx.o: 181 | $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $< 182 | 183 | .cpp.o: 184 | $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $< 185 | 186 | .C.o: 187 | $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $< 188 | 189 | .c.o: 190 | $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $< 191 | 192 | $(DLLIB): $(OBJS) Makefile 193 | @-$(RM) $(@) 194 | $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS) 195 | 196 | 197 | 198 | $(OBJS): $(hdrdir)/ruby.h $(hdrdir)/ruby/defines.h $(arch_hdrdir)/ruby/config.h 199 | -------------------------------------------------------------------------------- /ext/maclight/extconf.rb: -------------------------------------------------------------------------------- 1 | # Loads mkmf which is used to make makefiles for Ruby extensions 2 | require 'mkmf' 3 | 4 | # Give it a name 5 | extension_name = 'maclight' 6 | 7 | # The destination 8 | dir_config(extension_name) 9 | 10 | $LDFLAGS += ' -framework IOKit -framework CoreFoundation' 11 | 12 | # Do the work 13 | create_makefile(extension_name) 14 | 15 | -------------------------------------------------------------------------------- /ext/maclight/maclight.c: -------------------------------------------------------------------------------- 1 | // File: main.c 2 | // Abstract: source code for HID LED test tool 3 | // Version: 1.3 4 | // 5 | // Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 6 | // Inc. ("Apple") in consideration of your agreement to the following 7 | // terms, and your use, installation, modification or redistribution of 8 | // this Apple software constitutes acceptance of these terms. If you do 9 | // not agree with these terms, please do not use, install, modify or 10 | // redistribute this Apple software. 11 | // 12 | // In consideration of your agreement to abide by the following terms, and 13 | // subject to these terms, Apple grants you a personal, non-exclusive 14 | // license, under Apple's copyrights in this original Apple software (the 15 | // "Apple Software"), to use, reproduce, modify and redistribute the Apple 16 | // Software, with or without modifications, in source and/or binary forms; 17 | // provided that if you redistribute the Apple Software in its entirety and 18 | // without modifications, you must retain this notice and the following 19 | // text and disclaimers in all such redistributions of the Apple Software. 20 | // Neither the name, trademarks, service marks or logos of Apple Inc. may 21 | // be used to endorse or promote products derived from the Apple Software 22 | // without specific prior written permission from Apple. Except as 23 | // expressly stated in this notice, no other rights or licenses, express or 24 | // implied, are granted by Apple herein, including but not limited to any 25 | // patent rights that may be infringed by your derivative works or by other 26 | // works in which the Apple Software may be incorporated. 27 | // 28 | // The Apple Software is provided by Apple on an "AS IS" basis. APPLE 29 | // MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 30 | // THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 31 | // FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 32 | // OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 33 | // 34 | // IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 35 | // OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 36 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 37 | // INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 38 | // MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 39 | // AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 40 | // STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 41 | // POSSIBILITY OF SUCH DAMAGE. 42 | // 43 | // Copyright (C) 2015 Apple Inc. All Rights Reserved. 44 | // 45 | // **************************************************** 46 | 47 | /* rubyfied by moe@busyloop.net */ 48 | #pragma mark - 49 | #pragma mark * complation directives * 50 | // ---------------------------------------------------- 51 | /* rubyfied by moe@busyloop.net */ 52 | 53 | #ifndef FALSE 54 | #define FALSE 0 55 | #define TRUE !FALSE 56 | #endif 57 | 58 | // **************************************************** 59 | #pragma mark - 60 | #pragma mark * includes & imports * 61 | // ---------------------------------------------------- 62 | 63 | #include 64 | #include 65 | #include 66 | 67 | #include "ruby.h" 68 | 69 | // **************************************************** 70 | #pragma mark - 71 | #pragma mark * typedef's, struct's, enums, defines, etc. * 72 | // ---------------------------------------------------- 73 | // function to create a matching dictionary for usage page & usage 74 | static CFMutableDictionaryRef hu_CreateMatchingDictionaryUsagePageUsage( Boolean isDeviceNotElement, 75 | UInt32 inUsagePage, 76 | UInt32 inUsage ) 77 | { 78 | // create a dictionary to add usage page / usages to 79 | CFMutableDictionaryRef result = CFDictionaryCreateMutable( kCFAllocatorDefault, 80 | 0, 81 | &kCFTypeDictionaryKeyCallBacks, 82 | &kCFTypeDictionaryValueCallBacks ); 83 | 84 | if ( result ) { 85 | if ( inUsagePage ) { 86 | // Add key for device type to refine the matching dictionary. 87 | CFNumberRef pageCFNumberRef = CFNumberCreate( kCFAllocatorDefault, kCFNumberIntType, &inUsagePage ); 88 | 89 | if ( pageCFNumberRef ) { 90 | if ( isDeviceNotElement ) { 91 | CFDictionarySetValue( result, CFSTR( kIOHIDDeviceUsagePageKey ), pageCFNumberRef ); 92 | } else { 93 | CFDictionarySetValue( result, CFSTR( kIOHIDElementUsagePageKey ), pageCFNumberRef ); 94 | } 95 | CFRelease( pageCFNumberRef ); 96 | 97 | // note: the usage is only valid if the usage page is also defined 98 | if ( inUsage ) { 99 | CFNumberRef usageCFNumberRef = CFNumberCreate( kCFAllocatorDefault, kCFNumberIntType, &inUsage ); 100 | 101 | if ( usageCFNumberRef ) { 102 | if ( isDeviceNotElement ) { 103 | CFDictionarySetValue( result, CFSTR( kIOHIDDeviceUsageKey ), usageCFNumberRef ); 104 | } else { 105 | CFDictionarySetValue( result, CFSTR( kIOHIDElementUsageKey ), usageCFNumberRef ); 106 | } 107 | CFRelease( usageCFNumberRef ); 108 | } else { 109 | fprintf( stderr, "%s: CFNumberCreate( usage ) failed.", __PRETTY_FUNCTION__ ); 110 | } 111 | } 112 | } else { 113 | fprintf( stderr, "%s: CFNumberCreate( usage page ) failed.", __PRETTY_FUNCTION__ ); 114 | } 115 | } 116 | } else { 117 | fprintf( stderr, "%s: CFDictionaryCreateMutable failed.", __PRETTY_FUNCTION__ ); 118 | } 119 | return result; 120 | } // hu_CreateMatchingDictionaryUsagePageUsage 121 | 122 | //int main( int argc, const char * argv[] ) 123 | int manipulate_led( int which_led, int led_value ) 124 | { 125 | #pragma unused ( argc, argv ) 126 | IOHIDDeviceRef * tIOHIDDeviceRefs = nil; 127 | 128 | // create a IO HID Manager reference 129 | IOHIDManagerRef tIOHIDManagerRef = IOHIDManagerCreate( kCFAllocatorDefault, kIOHIDOptionsTypeNone ); 130 | __Require( tIOHIDManagerRef, Oops ); 131 | 132 | // Create a device matching dictionary 133 | CFDictionaryRef matchingCFDictRef = hu_CreateMatchingDictionaryUsagePageUsage( TRUE, 134 | kHIDPage_GenericDesktop, 135 | kHIDUsage_GD_Keyboard ); 136 | __Require( matchingCFDictRef, Oops ); 137 | 138 | // set the HID device matching dictionary 139 | IOHIDManagerSetDeviceMatching( tIOHIDManagerRef, matchingCFDictRef ); 140 | 141 | if ( matchingCFDictRef ) { 142 | CFRelease( matchingCFDictRef ); 143 | } 144 | 145 | // Now open the IO HID Manager reference 146 | IOReturn tIOReturn = IOHIDManagerOpen( tIOHIDManagerRef, kIOHIDOptionsTypeNone ); 147 | __Require_noErr( tIOReturn, Oops ); 148 | 149 | // and copy out its devices 150 | CFSetRef deviceCFSetRef = IOHIDManagerCopyDevices( tIOHIDManagerRef ); 151 | __Require( deviceCFSetRef, Oops ); 152 | 153 | // how many devices in the set? 154 | CFIndex deviceIndex, deviceCount = CFSetGetCount( deviceCFSetRef ); 155 | 156 | // allocate a block of memory to extact the device ref's from the set into 157 | tIOHIDDeviceRefs = malloc( sizeof( IOHIDDeviceRef ) * deviceCount ); 158 | if (!tIOHIDDeviceRefs) { 159 | CFRelease(deviceCFSetRef); 160 | deviceCFSetRef = NULL; 161 | goto Oops; 162 | } 163 | 164 | // now extract the device ref's from the set 165 | CFSetGetValues( deviceCFSetRef, (const void **) tIOHIDDeviceRefs ); 166 | CFRelease(deviceCFSetRef); 167 | deviceCFSetRef = NULL; 168 | 169 | // before we get into the device loop we'll setup our element matching dictionary 170 | matchingCFDictRef = hu_CreateMatchingDictionaryUsagePageUsage( FALSE, kHIDPage_LEDs, 0 ); 171 | __Require( matchingCFDictRef, Oops ); 172 | 173 | int pass; // do 256 passes 174 | //for ( pass = 0; pass < 256; pass++ ) { 175 | Boolean delayFlag = FALSE; // if we find an LED element we'll set this to TRUE 176 | 177 | //printf( "pass = %d.\n", pass ); 178 | for ( deviceIndex = 0; deviceIndex < deviceCount; deviceIndex++ ) { 179 | 180 | // if this isn't a keyboard device... 181 | if ( !IOHIDDeviceConformsTo( tIOHIDDeviceRefs[deviceIndex], kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard ) ) { 182 | continue; // ...skip it 183 | } 184 | 185 | //printf( " device = %p.\n", tIOHIDDeviceRefs[deviceIndex] ); 186 | 187 | // copy all the elements 188 | CFArrayRef elementCFArrayRef = IOHIDDeviceCopyMatchingElements( tIOHIDDeviceRefs[deviceIndex], 189 | matchingCFDictRef, 190 | kIOHIDOptionsTypeNone ); 191 | __Require( elementCFArrayRef, next_device ); 192 | 193 | // for each device on the system these values are divided by the value ranges of all LED elements found 194 | // for example, if the first four LED element have a range of 0-1 then the four least significant bits of 195 | // this value will be sent to these first four LED elements, etc. 196 | int device_value = pass; 197 | 198 | // iterate over all the elements 199 | CFIndex elementIndex, elementCount = CFArrayGetCount( elementCFArrayRef ); 200 | for ( elementIndex = 0; elementIndex < elementCount; elementIndex++ ) { 201 | IOHIDElementRef tIOHIDElementRef = ( IOHIDElementRef ) CFArrayGetValueAtIndex( elementCFArrayRef, elementIndex ); 202 | __Require( tIOHIDElementRef, next_element ); 203 | 204 | uint32_t usagePage = IOHIDElementGetUsagePage( tIOHIDElementRef ); 205 | 206 | // if this isn't an LED element... 207 | if ( kHIDPage_LEDs != usagePage ) { 208 | continue; // ...skip it 209 | } 210 | 211 | uint32_t usage = IOHIDElementGetUsage( tIOHIDElementRef ); 212 | IOHIDElementType tIOHIDElementType = IOHIDElementGetType( tIOHIDElementRef ); 213 | 214 | //printf( " element = %p (page: %d, usage: %d, type: %d ).\n", 215 | // tIOHIDElementRef, usagePage, usage, tIOHIDElementType ); 216 | 217 | // get the logical mix/max for this LED element 218 | CFIndex minCFIndex = IOHIDElementGetLogicalMin( tIOHIDElementRef ); 219 | CFIndex maxCFIndex = IOHIDElementGetLogicalMax( tIOHIDElementRef ); 220 | 221 | // calculate the range 222 | CFIndex modCFIndex = maxCFIndex - minCFIndex + 1; 223 | 224 | // compute the value for this LED element 225 | //CFIndex tCFIndex = minCFIndex + ( device_value % modCFIndex ); 226 | CFIndex tCFIndex = led_value; 227 | device_value /= modCFIndex; 228 | 229 | //printf( " value = 0x%08lX.\n", tCFIndex ); 230 | 231 | uint64_t timestamp = 0; // create the IO HID Value to be sent to this LED element 232 | IOHIDValueRef tIOHIDValueRef = IOHIDValueCreateWithIntegerValue( kCFAllocatorDefault, tIOHIDElementRef, timestamp, tCFIndex ); 233 | if ( tIOHIDValueRef ) { 234 | // now set it on the device 235 | tIOReturn = IOHIDDeviceSetValue( tIOHIDDeviceRefs[deviceIndex], tIOHIDElementRef, tIOHIDValueRef ); 236 | CFRelease( tIOHIDValueRef ); 237 | __Require_noErr( tIOReturn, next_element ); 238 | delayFlag = TRUE; // set this TRUE so we'll delay before changing our LED values again 239 | } 240 | next_element: ; 241 | continue; 242 | } 243 | CFRelease( elementCFArrayRef ); 244 | next_device: ; 245 | continue; 246 | } 247 | 248 | // if we found an LED we'll delay before continuing 249 | //if ( delayFlag ) { 250 | // usleep( 500000 ); // sleep one half second 251 | //} 252 | 253 | // if the mouse is down… 254 | //if (GetCurrentButtonState()) { 255 | // break; // abort pass loop 256 | //} 257 | //} // next pass 258 | 259 | if ( matchingCFDictRef ) { 260 | CFRelease( matchingCFDictRef ); 261 | } 262 | Oops: ; 263 | if ( tIOHIDDeviceRefs ) { 264 | free(tIOHIDDeviceRefs); 265 | } 266 | 267 | if ( tIOHIDManagerRef ) { 268 | CFRelease( tIOHIDManagerRef ); 269 | } 270 | return 0; 271 | } /* main */ 272 | 273 | VALUE MacLight = Qnil; 274 | 275 | void Init_maclight(); 276 | 277 | //VALUE method_capslock(int argc, VALUE *argv, VALUE self); 278 | //VALUE method_numlock(int argc, VALUE *argv, VALUE self); 279 | VALUE method_all_leds(int argc, VALUE *argv, VALUE self); 280 | 281 | void Init_maclight() { 282 | MacLight = rb_define_module("MacLight"); 283 | //rb_define_singleton_method(MacLight, "capslock", method_capslock, -1); 284 | //rb_define_singleton_method(MacLight, "numlock", method_numlock, -1); 285 | rb_define_singleton_method(MacLight, "all_leds", method_all_leds, -1); 286 | } 287 | 288 | VALUE kbd_led(UInt32 whichLED, int argc, VALUE *argv, VALUE klass) { 289 | VALUE flag; 290 | rb_scan_args(argc, argv, "01", &flag); 291 | 292 | int set_to = -1; 293 | switch (flag) { 294 | case Qtrue: 295 | set_to = 1; 296 | break; 297 | case Qfalse: 298 | set_to = 0; 299 | break; 300 | } 301 | 302 | return manipulate_led(whichLED, set_to) ? Qtrue : Qfalse; 303 | } 304 | 305 | VALUE method_all_leds(int argc, VALUE *argv, VALUE klass) { 306 | return kbd_led(-1, argc, argv, klass); 307 | } 308 | 309 | //VALUE method_capslock(int argc, VALUE *argv, VALUE klass) { 310 | // return kbd_led(kHIDUsage_LED_CapsLock, argc, argv, klass); 311 | //} 312 | // 313 | //VALUE method_numlock(int argc, VALUE *argv, VALUE klass) { 314 | // return kbd_led(kHIDUsage_LED_NumLock, argc, argv, klass); 315 | //} 316 | 317 | -------------------------------------------------------------------------------- /lib/maclight/cli.rb: -------------------------------------------------------------------------------- 1 | require 'maclight' 2 | require 'maclight/version' 3 | require 'optix' 4 | 5 | module MacLight 6 | class Cli < Optix::Cli 7 | 8 | cli_root do 9 | text "MacLight v#{MacLight::VERSION} - LED control utility" 10 | 11 | opt :version, "Print version and exit", :short => :none 12 | trigger :version do 13 | puts "MacLight v#{MacLight::VERSION}" 14 | end 15 | end 16 | 17 | # Commenting out most functionality 18 | # until we get around to restore it. 19 | 20 | # For now MacLight can only toggle all 21 | # LEDs at once on and off. 22 | 23 | #desc "Toggle keyboard LEDs" 24 | #text "Toggle keyboard LEDs (capslock, numlock)" 25 | #opt :capslock, "Toggle capslock LED", :default => false 26 | #opt :numlock, "Toggle numlock LED", :default => false 27 | #opt :verbose, "Print current state of capslock, numlock" 28 | #parent "keyboard", "Control keyboard LEDs" 29 | #def toggle(cmd, opts, argv) 30 | # raise Optix::HelpNeeded unless opts.values_at(:capslock, :numlock, :verbose).any? 31 | # MacLight.capslock(!MacLight.capslock) if opts[:capslock] 32 | # MacLight.numlock(!MacLight.numlock) if opts[:numlock] 33 | # puts "#{MacLight.capslock ? 1:0} #{MacLight.numlock ? 1:0}" if opts[:verbose] 34 | #end 35 | 36 | desc "Set keyboard LEDs state" 37 | text "Set keyboard LEDs (capslock, numlock) state (on, off)" 38 | opt :all, "Set all LEDs (0|1)", :type => Integer 39 | #opt :capslock, "Set capslock LED (0|1)", :type => Integer 40 | #opt :numlock, "Set numlock LED (0|1)", :type => Integer 41 | #opt :verbose, "Print current state of capslock, numlock" 42 | parent "keyboard" 43 | def set(cmd, opts, argv) 44 | raise Optix::HelpNeeded unless opts.values_at(:all).any? 45 | MacLight.all_leds(1 == opts[:all]) 46 | #MacLight.capslock(1 == opts[:capslock]) unless opts[:capslock].nil? 47 | #MacLight.numlock(1 == opts[:numlock]) unless opts[:numlock].nil? 48 | #puts "#{MacLight.capslock ? 1:0} #{MacLight.numlock ? 1:0}" if opts[:verbose] 49 | end 50 | 51 | desc "Blink keyboard LEDs" 52 | text "Blink keyboard LEDs" 53 | text '' 54 | text "Examples:" 55 | text " #{File.basename($0)} keyboard blink -r 3 -f 0 1:0.3 0:0.3" 56 | text " #{File.basename($0)} keyboard blink -r 3 1:0.3 0:0.2 0:0.1 1:0.2 0:0.07 1:0.07 0:0.07" 57 | text '' 58 | text 'Parameters:' 59 | text ' - Space-delimited sequence; V:T V:T ..' 60 | text ' V = led value, T = time in seconds' 61 | opt :repeat, "Repetitions", :default => 0 62 | opt :fin, "Set this state after sequence has finished (CN)", :type => String 63 | params "" 64 | parent "keyboard" 65 | def blink(cmd, opts, argv) 66 | raise Optix::HelpNeeded if argv.empty? 67 | seq = argv.map {|e| ['1'==e[0],e[2..-1].to_f] } 68 | (0..opts[:repeat]).each do |i| 69 | seq.each_with_index do |mode, step| 70 | MacLight.all_leds(mode[0]) 71 | sleep mode[1] 72 | end 73 | end 74 | if opts[:fin] 75 | MacLight.all_leds('1'==opts[:fin][0]) 76 | end 77 | end 78 | 79 | desc "Play a short test sequence" 80 | text "Play a short test sequence" 81 | parent "keyboard" 82 | def test(cmd, opts, argv) 83 | blink(nil, { :repeat => 3 }, %w(1:0.3 0:0.2 0:0.1 1:0.2 0:0.07 1:0.07 0:0.07)) 84 | end 85 | 86 | #desc "Blink keyboard LEDs" 87 | #text "Blink keyboard LEDs (capslock, numlock)" 88 | #text '' 89 | #text "Examples:" 90 | #text " #{File.basename($0)} keyboard blink -r 3 -f 00 10:0.3 01:0.3" 91 | #text " #{File.basename($0)} keyboard blink -r 3 10:0.3 01:0.2 00:0.1 11:0.2 00:0.07 11:0.07 00:0.07" 92 | #text '' 93 | #text 'Parameters:' 94 | #text ' - Space-delimited sequence; CN:T CN:T ..' 95 | #text ' C = capslock value, N = numlock value, T = time in seconds' 96 | #opt :repeat, "Repetitions", :default => 0 97 | #opt :fin, "Set this state after sequence has finished (CN)", :type => String 98 | #opt :verbose, "Print state of capslock, numlock" 99 | #params "" 100 | #parent "keyboard" 101 | #def blink(cmd, opts, argv) 102 | # raise Optix::HelpNeeded if argv.empty? 103 | # puts "#{MacLight.capslock ? 1:0} #{MacLight.numlock ? 1:0}" if opts[:verbose] 104 | # seq = argv.map {|e| ['1'==e[0],'1'==e[1],e[3..-1].to_f] } 105 | # (0..opts[:repeat]).each do |i| 106 | # seq.each_with_index do |mode, step| 107 | # MacLight.capslock(mode[0]) 108 | # MacLight.numlock(mode[1]) 109 | # puts "#{MacLight.capslock ? 1:0} #{MacLight.numlock ? 1:0}" if opts[:verbose] 110 | # sleep mode[2] 111 | # end 112 | # end 113 | # if opts[:fin] 114 | # MacLight.capslock('1'==opts[:fin][0]) 115 | # MacLight.numlock('1'==opts[:fin][1]) 116 | # puts "#{MacLight.capslock ? 1:0} #{MacLight.numlock ? 1:0}" if opts[:verbose] 117 | # end 118 | #end 119 | 120 | def self.start 121 | Optix.invoke! 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /lib/maclight/version.rb: -------------------------------------------------------------------------------- 1 | module MacLight 2 | VERSION = "3.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /maclight.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'maclight/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "maclight" 8 | gem.version = MacLight::VERSION 9 | gem.authors = ["Moe"] 10 | gem.email = ["moe@busyloop.net"] 11 | gem.description = %q{Control your Mac keyboard LEDs (capslock, numlock)} 12 | gem.summary = %q{Control your Mac keyboard LEDs (capslock, numlock)} 13 | gem.homepage = "https://github.com/busyloop/maclight" 14 | 15 | gem.files = `git ls-files`.split($/) 16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 18 | gem.require_paths = ["lib"] 19 | gem.extensions = ['ext/maclight/extconf.rb'] 20 | 21 | gem.add_dependency 'optix', '>= 1.2.2' 22 | gem.add_development_dependency 'rake', '~> 10.1.0' 23 | end 24 | --------------------------------------------------------------------------------