├── .gitignore ├── dist ├── getvol.exe ├── nircmdc.exe └── LibusbJava.dll ├── libs └── ch.ntb.usb-0.5.9.jar ├── .project ├── .classpath ├── project.yaml ├── .settings └── org.eclipse.jdt.core.prefs ├── LICENSE ├── README.md └── src └── powermate ├── VolumeControl.java └── PowerMate.java /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | build/PowerMate.exe 3 | dist/PowerMate.exe 4 | dist/powermate.jar -------------------------------------------------------------------------------- /dist/getvol.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EsotericSoftware/powermate/HEAD/dist/getvol.exe -------------------------------------------------------------------------------- /dist/nircmdc.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EsotericSoftware/powermate/HEAD/dist/nircmdc.exe -------------------------------------------------------------------------------- /dist/LibusbJava.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EsotericSoftware/powermate/HEAD/dist/LibusbJava.dll -------------------------------------------------------------------------------- /libs/ch.ntb.usb-0.5.9.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EsotericSoftware/powermate/HEAD/libs/ch.ntb.usb-0.5.9.jar -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | powermate 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /project.yaml: -------------------------------------------------------------------------------- 1 | main: powermate.VolumeControl 2 | --- 3 | Build.build(project); 4 | Build.oneJAR(project); 5 | shell("jsmoothcmd.bat " + project.path("build/PowerMate.jsmooth")); 6 | shell("ResHacker.exe -delete build/PowerMate.exe, build/PowerMate.exe, ICONGROUP, A, 1033"); 7 | shell("ResHacker.exe -delete build/PowerMate.exe, build/PowerMate.exe, ICONGROUP, A2, 1033"); 8 | shell("ResHacker.exe -add build/PowerMate.exe, build/PowerMate.exe, build/icon.ico, ICONGROUP, MAINICON, 0"); 9 | copyFile("build/PowerMate.exe", "dist/PowerMate.exe"); 10 | copyFile(project.path("$target$/dist/onejar/powermate-all.jar"), "dist/powermate.jar"); -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.7 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.source=1.7 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Esoteric Software 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerMate 2 | 3 | This is a small Windows utility that uses [libusbjava](http://libusbjava.sourceforge.net) to communicate with a Griffin PowerMate, which is a USB knob. When the knob is turned, the Windows master volume is adjusted. The LED brightness indicates the volume level, then fades away after a short time. Griffin's software supports controlling volume, but the LED stays on which was unacceptable for me. 4 | 5 | This Java project can easily be extended to have the PowerMate knob perform other actions. 6 | 7 | ## Installation 8 | 9 | Download the latest version [here](https://github.com/EsotericSoftware/powermate/releases). 10 | 11 | A 64-bit version of Java must be installed. 12 | 13 | The libusb-win32 driver must be installed for the PowerMate. [Zadig](http://zadig.akeo.ie/) is the easiest way to do this (click the `Options` menu, then `List All Devices`, choose `Griffin PowerMate` from the select box, change the driver name to `libusb-win32`, then click `Replace Driver`). 14 | 15 | ## Running 16 | 17 | Run `PowerMate.exe` which will find your Java installation and run PowerMate. It will appear that nothing happens because PowerMate runs in the background. It searches for the Griffin USB device,connects to it when found, then responds to events from the device. 18 | 19 | PowerMate may be run from the JAR file to get more insight in case there is a problem: 20 | 21 | ``` 22 | java -jar powermate.jar 23 | ``` 24 | 25 | Be sure to use a 64-bit version of Java. If PowerMate runs without errors, it may help to enable console debug messages: 26 | 27 | ``` 28 | java -jar powermate.jar debug 29 | ``` 30 | 31 | ## License 32 | 33 | PowerMate is released as OSS under the [New BSD license](https://github.com/EsotericSoftware/powermate/blob/master/LICENSE). 34 | -------------------------------------------------------------------------------- /src/powermate/VolumeControl.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014, Esoteric Software 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 5 | * conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 9 | * disclaimer in the documentation and/or other materials provided with the distribution. 10 | * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products 11 | * derived from this software without specific prior written permission. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 14 | * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 15 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 16 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 17 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 18 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 19 | */ 20 | 21 | package powermate; 22 | 23 | import java.io.IOException; 24 | import java.util.Scanner; 25 | 26 | /** @author Nathan Sweet */ 27 | public class VolumeControl extends PowerMate { 28 | float volume = -1; 29 | float sensitivity = 0.04f; 30 | 31 | public VolumeControl (boolean debug) { 32 | this.debug = debug; 33 | run(); 34 | } 35 | 36 | public void connected () { 37 | if (volume == -1) { 38 | try { 39 | volume = Integer 40 | .parseInt(new Scanner(Runtime.getRuntime().exec("getvol.exe").getInputStream()).useDelimiter("\\A").next()) / 100f; 41 | } catch (Exception ex) { 42 | volume = 1; 43 | } 44 | } 45 | setBrightness(volume); 46 | } 47 | 48 | public void knob (int turn) { 49 | volume += turn * sensitivity; 50 | if (volume > 1) 51 | volume = 1; 52 | else if (volume < 0) // 53 | volume = 0; 54 | try { 55 | Runtime.getRuntime().exec("nircmdc.exe setsysvolume " + (int)(0xffff * volume)); 56 | } catch (IOException ex) { 57 | } 58 | setBrightness(volume); 59 | } 60 | 61 | public void button (boolean button) { 62 | if (button) setBrightness(volume); 63 | } 64 | 65 | static public void main (String[] args) throws Exception { 66 | boolean debug = false; 67 | for (int i = 0, n = args.length; i < n; i++) 68 | if (args[i].equalsIgnoreCase("debug")) debug = true; 69 | new VolumeControl(debug); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/powermate/PowerMate.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014, Esoteric Software 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 5 | * conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 9 | * disclaimer in the documentation and/or other materials provided with the distribution. 10 | * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products 11 | * derived from this software without specific prior written permission. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 14 | * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 15 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 16 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 17 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 18 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 19 | */ 20 | 21 | package powermate; 22 | 23 | import ch.ntb.usb.Device; 24 | import ch.ntb.usb.USB; 25 | import ch.ntb.usb.USBException; 26 | 27 | /** @author Nathan Sweet */ 28 | abstract public class PowerMate { 29 | static final short vendorID = 0x077d; 30 | static final short productID = 0x0410; 31 | 32 | boolean debug; 33 | float fadeSpeed = 1f, fadeDelay = 1f; 34 | int searchInterval = 5000; 35 | int monitorInterval = 500; 36 | int monitorTimeout = 5000; 37 | int brightnessTimeout = 2000; 38 | 39 | float brightness, brightnessDelay; 40 | Device dev; 41 | long lastTime; 42 | 43 | public void run () { 44 | while (true) { 45 | if (debug) System.out.println("Searching..."); 46 | 47 | // Open. 48 | try { 49 | dev = USB.getDevice(vendorID, productID); 50 | dev.open(1, 0, -1); 51 | } catch (USBException ex) { 52 | if (debug) { 53 | System.out.println("Device not found:"); 54 | ex.printStackTrace(); 55 | } 56 | try { 57 | Thread.sleep(searchInterval); 58 | } catch (Exception ignored2) { 59 | } 60 | continue; 61 | } 62 | 63 | connected(); 64 | 65 | // Monitor events. 66 | byte[] data = new byte[6]; 67 | boolean buttonDownLast = false; 68 | lastTime = System.nanoTime(); 69 | while (true) { 70 | if (debug) System.out.println("Monitoring..."); 71 | 72 | long time = System.nanoTime(); 73 | float delta = (time - lastTime) / 1000000000f; 74 | lastTime = time; 75 | 76 | int timeout = monitorTimeout; 77 | 78 | if (fadeSpeed > 0 && brightness > 0) { 79 | timeout = 33; 80 | if (brightnessDelay > 0) { 81 | brightnessDelay -= delta; 82 | } else { 83 | brightness -= delta / fadeSpeed; 84 | if (brightness < 0) brightness = 0; 85 | setBrightness(brightness); 86 | brightnessDelay = 0; 87 | } 88 | try { 89 | Thread.sleep(33); 90 | } catch (Exception ignored2) { 91 | } 92 | } else { 93 | try { 94 | Thread.sleep(monitorInterval); 95 | } catch (Exception ignored2) { 96 | } 97 | } 98 | 99 | try { 100 | dev.readInterrupt(0x81, data, data.length, timeout, false); 101 | } catch (USBException ex) { 102 | if (debug) { 103 | System.out.println("Monitoring failed:"); 104 | ex.printStackTrace(); 105 | } 106 | if (ex.getMessage().contains("The device does not recognize the command.")) break; 107 | continue; 108 | } 109 | 110 | lastTime = System.nanoTime(); 111 | 112 | boolean button = data[0] == 1; 113 | if (button != buttonDownLast) { 114 | buttonDownLast = button; 115 | if (debug) System.out.println("Button: " + button); 116 | button(button); 117 | } 118 | 119 | int knob = data[1]; 120 | if (knob != 0) { 121 | if (debug) System.out.println("Knob: " + knob); 122 | knob(knob); 123 | } 124 | } 125 | 126 | // Close. 127 | try { 128 | dev.close(); 129 | } catch (USBException ignored) { 130 | } 131 | dev = null; 132 | } 133 | } 134 | 135 | abstract public void connected (); 136 | 137 | abstract public void knob (int turn); 138 | 139 | abstract public void button (boolean button); 140 | 141 | public void setBrightness (float brightness) { 142 | if (dev == null) return; 143 | this.brightness = brightness; 144 | brightnessDelay = fadeDelay; 145 | try { 146 | dev.writeInterrupt(2, new byte[] {(byte)(0xff * brightness)}, 1, brightnessTimeout, false); 147 | } catch (USBException ex) { 148 | if (debug) { 149 | System.out.println("Error setting brightness:"); 150 | ex.printStackTrace(); 151 | } 152 | } 153 | } 154 | } 155 | --------------------------------------------------------------------------------