├── .github ├── FUNDING.yml └── workflows │ ├── lint.yml │ └── build.yml ├── .gitignore ├── .golangci.yml ├── go.mod ├── LICENSE ├── cmd └── lantern │ ├── .goreleaser.yml │ └── main.go ├── README.md ├── go.sum ├── razer.go ├── effects.go └── keys.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: muesli 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | # Binaries 17 | cmd/lantern/lantern 18 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | tests: false 3 | 4 | issues: 5 | max-issues-per-linter: 0 6 | max-same-issues: 0 7 | 8 | linters: 9 | enable: 10 | - bodyclose 11 | - dupl 12 | - exportloopref 13 | - goconst 14 | - godot 15 | - godox 16 | - goimports 17 | - golint 18 | - goprintffuncname 19 | - gosec 20 | - ifshort 21 | - misspell 22 | - prealloc 23 | - rowserrcheck 24 | - sqlclosecheck 25 | - unconvert 26 | - unparam 27 | - whitespace 28 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/muesli/go-razer 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/godbus/dbus v4.1.0+incompatible 7 | github.com/lucasb-eyer/go-colorful v1.0.2 8 | github.com/muesli/clusters v0.0.0-20190807044042-ba9c57dd9228 // indirect 9 | github.com/muesli/gamut v0.0.0-20190807050624-0d3f7d26a44e 10 | github.com/muesli/kmeans v0.0.0-20190917235210-80dfc71e6c5a // indirect 11 | github.com/shirou/gopsutil v2.18.12+incompatible 12 | github.com/xrash/smetrics v0.0.0-20170218160415-a3153f7040e9 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | on: 3 | push: 4 | pull_request: 5 | 6 | jobs: 7 | golangci: 8 | name: lint 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: golangci-lint 13 | uses: golangci/golangci-lint-action@v2 14 | with: 15 | # Optional: golangci-lint command line arguments. 16 | args: --issues-exit-code=0 17 | # Optional: show only new issues if it's a pull request. The default value is `false`. 18 | only-new-issues: true 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | strategy: 7 | matrix: 8 | go-version: [~1.13, ^1] 9 | os: [ubuntu-latest] 10 | runs-on: ${{ matrix.os }} 11 | env: 12 | GO111MODULE: "on" 13 | steps: 14 | - name: Install Go 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: ${{ matrix.go-version }} 18 | 19 | - name: Checkout code 20 | uses: actions/checkout@v2 21 | 22 | - name: Download Go modules 23 | run: go mod download 24 | 25 | - name: Build 26 | run: go build -v ./... 27 | 28 | - name: Test 29 | run: go test ./... 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Christian Muehlhaeuser 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. 22 | -------------------------------------------------------------------------------- /cmd/lantern/.goreleaser.yml: -------------------------------------------------------------------------------- 1 | env: 2 | - GO111MODULE=on 3 | before: 4 | hooks: 5 | - go mod download 6 | builds: 7 | - id: "lantern" 8 | binary: lantern 9 | ldflags: -s -w -X main.Version={{ .Version }} -X main.CommitSHA={{ .Commit }} 10 | goos: 11 | - linux 12 | - freebsd 13 | - windows 14 | goarch: 15 | - amd64 16 | - arm64 17 | - 386 18 | - arm 19 | goarm: 20 | - 6 21 | - 7 22 | - id: "darwin" 23 | binary: "lantern" 24 | ldflags: -s -w -X main.Version={{ .Version }} -X main.CommitSHA={{ .Commit }} 25 | goos: 26 | - darwin 27 | goarch: 28 | - amd64 29 | 30 | archives: 31 | - id: default 32 | builds: 33 | - lantern 34 | format_overrides: 35 | - goos: windows 36 | format: zip 37 | replacements: 38 | windows: Windows 39 | 386: i386 40 | amd64: x86_64 41 | - id: darwin 42 | builds: 43 | - darwin 44 | replacements: 45 | darwin: Darwin 46 | amd64: x86_64 47 | 48 | nfpms: 49 | - builds: 50 | - lantern 51 | 52 | vendor: muesli 53 | homepage: "https://fribbledom.com/" 54 | maintainer: "Christian Muehlhaeuser " 55 | description: "A CLI tool to control your Razer Chroma devices" 56 | license: MIT 57 | formats: 58 | - deb 59 | - rpm 60 | bindir: /usr/bin 61 | 62 | brews: 63 | - ids: 64 | - darwin 65 | github: 66 | owner: muesli 67 | name: homebrew-tap 68 | commit_author: 69 | name: "Christian Muehlhaeuser" 70 | email: "muesli@gmail.com" 71 | homepage: "https://fribbledom.com/" 72 | description: "A CLI tool to control your Razer Chroma devices" 73 | # skip_upload: true 74 | 75 | signs: 76 | - artifacts: checksum 77 | 78 | checksum: 79 | name_template: "checksums.txt" 80 | snapshot: 81 | name_template: "{{ .Tag }}-next" 82 | changelog: 83 | sort: asc 84 | filters: 85 | exclude: 86 | - "^docs:" 87 | - "^test:" 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-razer 2 | 3 | [![Latest Release](https://img.shields.io/github/release/muesli/go-razer.svg)](https://github.com/muesli/go-razer/releases) 4 | [![Build Status](https://github.com/muesli/go-razer/workflows/build/badge.svg)](https://github.com/muesli/go-razer/actions) 5 | [![Go ReportCard](https://goreportcard.com/badge/muesli/go-razer)](https://goreportcard.com/report/muesli/go-razer) 6 | [![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://pkg.go.dev/github.com/muesli/go-razer) 7 | 8 | Control Razer (Chroma) devices from the CLI or your Go apps 9 | 10 | ## Installation 11 | 12 | Make sure you have a working Go environment (Go 1.11 or higher is required). 13 | See the [install instructions](http://golang.org/doc/install.html). 14 | 15 | To install go-razer, simply run: 16 | 17 | go get github.com/muesli/go-razer 18 | 19 | ## Lantern CLI 20 | 21 | Lantern is a CLI tool (using go-razer) to control your Razer devices. Run the 22 | following command to install it: 23 | 24 | go get github.com/muesli/go-razer/cmd/lantern 25 | 26 | ### Hardware Effects 27 | 28 | Available effects are `wave`, `reactive`, `spectrum`, `breath`, `breathdual`, `breathrandom`, `starlight`, `starlightdual`, `starlightrandom`, `ripple` and `ripplerandom`. 29 | 30 | ``` 31 | $ lantern -effect wave 32 | $ lantern -effect starlightdual -color "#00ff00" -secondary "#aa00aa" 33 | ``` 34 | 35 | ### Plain Background Color 36 | 37 | ``` 38 | $ lantern -color "#6b6b00" 39 | ``` 40 | 41 | ### top Mode 42 | 43 | Monitor your system's CPU usage by turning your keyboard into a gauge: 44 | 45 | ``` 46 | $ lantern -top 47 | ``` 48 | 49 | ### Themes 50 | 51 | There are currently only a few available themes (feel free to submit more!) 52 | named `happy`, `soft`, `warm`, `rainbow` and `random`. To try them out, run: 53 | 54 | ``` 55 | $ lantern -theme happy 56 | ``` 57 | 58 | ### Brightness 59 | 60 | You can change the brightness (value in percent) by running: 61 | 62 | ``` 63 | $ lantern -brightness 80 64 | ``` 65 | 66 | The `brightness` parameter can also be used in combination with any of the 67 | modes described above. 68 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= 2 | github.com/blend/go-sdk v2.0.0+incompatible/go.mod h1:3GUb0YsHFNTJ6hsJTpzdmCUl05o8HisKjx5OAlzYKdw= 3 | github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= 4 | github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= 5 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 6 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 7 | github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4= 8 | github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= 9 | github.com/muesli/clusters v0.0.0-20180605185049-a07a36e67d36/go.mod h1:mw5KDqUj0eLj/6DUNINLVJNoPTFkEuGMHtJsXLviLkY= 10 | github.com/muesli/clusters v0.0.0-20190807044042-ba9c57dd9228 h1:JNZ3Ev2fzCgkhtRp+OaKgCWy3ZXx4W0n4wmvNj6/jdU= 11 | github.com/muesli/clusters v0.0.0-20190807044042-ba9c57dd9228/go.mod h1:mw5KDqUj0eLj/6DUNINLVJNoPTFkEuGMHtJsXLviLkY= 12 | github.com/muesli/gamut v0.0.0-20190807050624-0d3f7d26a44e h1:039bFbhxSzy9DDVLPT36bz5hG3PG74Y8qrGMXea0iS8= 13 | github.com/muesli/gamut v0.0.0-20190807050624-0d3f7d26a44e/go.mod h1:qOhbxUcDlplFAT1ULq47hrHOCw1CJoqvbsMeEu6/4mU= 14 | github.com/muesli/kmeans v0.0.0-20190917235210-80dfc71e6c5a h1:TfqSfRTR/uTo2h3AfuXaeWm+a6DH7mYSlfQkNhxnxUk= 15 | github.com/muesli/kmeans v0.0.0-20190917235210-80dfc71e6c5a/go.mod h1:DchXqcxIXcInFMlUlAAcmjNYiDEPoIQQdDxlWlELyxM= 16 | github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM= 17 | github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 18 | github.com/wcharczuk/go-chart v2.0.1+incompatible h1:0pz39ZAycJFF7ju/1mepnk26RLVLBCWz1STcD3doU0A= 19 | github.com/wcharczuk/go-chart v2.0.1+incompatible/go.mod h1:PF5tmL4EIx/7Wf+hEkpCqYi5He4u90sw+0+6FhrryuE= 20 | github.com/xrash/smetrics v0.0.0-20170218160415-a3153f7040e9 h1:w8V9v0qVympSF6GjdjIyeqR7+EVhAF9CBQmkmW7Zw0w= 21 | github.com/xrash/smetrics v0.0.0-20170218160415-a3153f7040e9/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 22 | golang.org/x/image v0.0.0-20190501045829-6d32002ffd75 h1:TbGuee8sSq15Iguxu4deQ7+Bqq/d2rsQejGcEtADAMQ= 23 | golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 24 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 25 | -------------------------------------------------------------------------------- /razer.go: -------------------------------------------------------------------------------- 1 | package razer 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strconv" 7 | 8 | "github.com/godbus/dbus" 9 | ) 10 | 11 | // Device represents a single Razer device 12 | type Device struct { 13 | Name string 14 | keys Keys 15 | dbusObject dbus.BusObject 16 | } 17 | 18 | // Devices returns the available Razer devices 19 | func Devices() ([]Device, error) { 20 | d := []Device{} 21 | dbusConn, err := dbus.SessionBus() 22 | if err != nil { 23 | return d, err 24 | } 25 | 26 | var s []string 27 | err = dbusConn.Object("org.razer", dbus.ObjectPath("/org/razer")). 28 | Call("razer.devices.getDevices", 0).Store(&s) 29 | if err != nil { 30 | return d, err 31 | } 32 | 33 | for _, vs := range s { 34 | d = append(d, Device{ 35 | Name: vs, 36 | dbusObject: dbusConn.Object("org.razer", 37 | dbus.ObjectPath(fmt.Sprintf("/org/razer/device/%s", vs))), 38 | }) 39 | } 40 | 41 | return d, nil 42 | } 43 | 44 | func (d *Device) String() string { 45 | rows, columns := d.MatrixDimensions() 46 | 47 | return fmt.Sprintf( 48 | "%s (type %s, serial: %s)\n\t"+ 49 | "Dimensions: %dx%d\n\t"+ 50 | "Brightness: %.2f%%\n\t"+ 51 | "Firmware: %s\n\t"+ 52 | "GameMode: %s", 53 | d.Name, d.Type(), d.Serial(), 54 | rows, columns, d.Brightness(), 55 | d.Firmware(), strconv.FormatBool(d.GameMode())) 56 | } 57 | 58 | // Type returns the device type (e.g. "keyboard") 59 | func (d *Device) Type() string { 60 | var s string 61 | err := dbusCall(d.dbusObject.Call("razer.device.misc.getDeviceType", 0)).Store(&s) 62 | if err != nil { 63 | log.Printf("reading device type failed: %s", err) 64 | } 65 | 66 | return s 67 | } 68 | 69 | // Serial returns the serial no of the device 70 | func (d *Device) Serial() string { 71 | var s string 72 | err := dbusCall(d.dbusObject.Call("razer.device.misc.getSerial", 0)).Store(&s) 73 | if err != nil { 74 | log.Printf("reading device serial failed: %s", err) 75 | } 76 | 77 | return s 78 | } 79 | 80 | // Firmware returns the firmware version of the device 81 | func (d *Device) Firmware() string { 82 | var s string 83 | err := dbusCall(d.dbusObject.Call("razer.device.misc.getFirmware", 0)).Store(&s) 84 | if err != nil { 85 | log.Printf("reading firmware version failed: %s", err) 86 | } 87 | 88 | return s 89 | } 90 | 91 | // MatrixDimensions returns the matrix dimensions of the device (rows & columns) 92 | func (d *Device) MatrixDimensions() (int, int) { 93 | var i []int 94 | err := dbusCall(d.dbusObject.Call("razer.device.misc.getMatrixDimensions", 0)).Store(&i) 95 | if err != nil { 96 | log.Printf("reading matrixDimensions failed: %s", err) 97 | } 98 | 99 | return i[0], i[1] 100 | } 101 | 102 | // GameMode returns whether the device is currently in "game-mode" 103 | func (d *Device) GameMode() bool { 104 | var b bool 105 | err := dbusCall(d.dbusObject.Call("razer.device.led.gamemode.getGameMode", 0)).Store(&b) 106 | if err != nil { 107 | log.Printf("reading gameMode failed: %s", err) 108 | } 109 | 110 | return b 111 | } 112 | 113 | // HasDedicatedMacroKeys returns whether the device has dedicated macro keys 114 | func (d *Device) HasDedicatedMacroKeys() bool { 115 | var b bool 116 | err := dbusCall(d.dbusObject.Call("razer.device.misc.hasDedicatedMacroKeys", 0)).Store(&b) 117 | if err != nil { 118 | log.Printf("reading hasDedicatedMacroKeys failed: %s", err) 119 | } 120 | 121 | return b 122 | } 123 | 124 | // Brightness returns the device's current brightness 125 | func (d *Device) Brightness() float64 { 126 | var b float64 127 | err := dbusCall(d.dbusObject.Call("razer.device.lighting.brightness.getBrightness", 0)).Store(&b) 128 | if err != nil { 129 | log.Printf("reading brightness failed: %s", err) 130 | } 131 | 132 | return b 133 | } 134 | 135 | // SetBrightness sets the brightness (between 0 and 100, in percent) 136 | func (d *Device) SetBrightness(b float64) { 137 | dbusCall(d.dbusObject.Call("razer.device.lighting.brightness.setBrightness", 0, b)) 138 | } 139 | 140 | // activateCustomSettings activates the custom color settings 141 | func (d *Device) activateCustomSettings() { 142 | dbusCall(d.dbusObject.Call("razer.device.lighting.chroma.setCustom", 0)) 143 | } 144 | 145 | func dbusCall(call *dbus.Call) *dbus.Call { 146 | if call.Err != nil { 147 | log.Printf("dbus call failed: %s", call.Err) 148 | } 149 | 150 | return call 151 | } 152 | -------------------------------------------------------------------------------- /cmd/lantern/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "image/color" 7 | "log" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/lucasb-eyer/go-colorful" 12 | "github.com/muesli/gamut/palette" 13 | "github.com/shirou/gopsutil/cpu" 14 | 15 | "github.com/muesli/go-razer" 16 | ) 17 | 18 | var ( 19 | brightness = flag.Float64("brightness", -1, "brightness (between 0 and 100)") 20 | effect = flag.String("effect", "", "effect mode (reactive, wave, spectrum, breath[dual,random], starlight[dual,random], ripple[random])") 21 | primary = flag.String("color", "#ff0000", "Sets the primary keyboard color") 22 | secondary = flag.String("secondary", "#00ff00", "secondary color (for 'dual' effect modes)") 23 | theme = flag.String("theme", "", "theme mode (happy, soft, warm, rainbow, random)") 24 | top = flag.Bool("top", false, "top mode") 25 | ) 26 | 27 | func topMode(d razer.Device) { 28 | rows, cols := d.MatrixDimensions() 29 | k := d.Keys() 30 | 31 | base := 2.5 32 | for { 33 | cpuUsage, err := cpu.Percent(0, false) 34 | if err != nil { 35 | panic(err) 36 | } 37 | // fmt.Println("CPU usage:", cpuUsage[0]) 38 | 39 | for x := 0; x < cols; x++ { 40 | var hue = ((base - (float64(x) / float64(cols-1))) * 120) 41 | c := colorful.Hsl(hue, 1.0, 0.5) 42 | 43 | if x > int(float64(cols-1)*(cpuUsage[0]/100)) { 44 | c = colorful.Hsl(hue, 1.0, 0.02) 45 | } 46 | 47 | for y := 0; y < rows; y++ { 48 | k.Key(y, x).Color = c 49 | } 50 | } 51 | d.SetKeys(k) 52 | 53 | time.Sleep(300 * time.Millisecond) 54 | base -= 0.015 55 | if base < 0 { 56 | base = 3 57 | } 58 | } 59 | } 60 | 61 | func rainbowTheme(d razer.Device) { 62 | rows, cols := d.MatrixDimensions() 63 | k := d.Keys() 64 | 65 | pal := colorful.FastHappyPalette(rows) 66 | for y := 0; y < rows; y++ { 67 | for x := 0; x < cols; x++ { 68 | k.Key(y, x).Color = pal[y] 69 | d.SetKeys(k) 70 | time.Sleep(5 * time.Millisecond) 71 | } 72 | } 73 | } 74 | 75 | func happyTheme(d razer.Device, theme string) { 76 | k := d.Keys() 77 | k.SetColor(color.RGBA{255, 0, 0, 0}) 78 | 79 | var pal []colorful.Color 80 | switch theme { 81 | case "happy": 82 | pal = colorful.FastHappyPalette(9) 83 | case "warm": 84 | pal = colorful.FastWarmPalette(9) 85 | case "soft": 86 | pal, _ = colorful.SoftPalette(9) 87 | case "random": 88 | rand.Seed(time.Now().UTC().UnixNano()) 89 | pal, _ = colorful.HappyPalette(9) 90 | case "monokai": 91 | for _, c := range palette.Monokai.Colors() { 92 | cf, _ := colorful.MakeColor(c.Color) 93 | fmt.Println("Color:", c.Name, cf.Hex()) 94 | pal = append(pal, cf) 95 | } 96 | default: 97 | p := palette.AllPalettes().Filter(theme) 98 | if len(p) == 0 { 99 | log.Fatalf("Could not find colors by that name") 100 | } 101 | for _, c := range p { 102 | cf, _ := colorful.MakeColor(c.Color) 103 | fmt.Println("Color:", c.Name, cf.Hex()) 104 | pal = append(pal, cf) 105 | } 106 | } 107 | 108 | k.FnKeys.SetColor(pal[0%len(pal)]) 109 | k.Numerics.SetColor(pal[1%len(pal)]) 110 | k.Cursor.SetColor(pal[2%len(pal)]) 111 | k.Symbols.SetColor(pal[3%len(pal)]) 112 | k.Commandos.SetColor(pal[4%len(pal)]) 113 | k.Actions.SetColor(pal[5%len(pal)]) 114 | k.Letters.SetColor(pal[6%len(pal)]) 115 | k.Arrows.SetColor(pal[7%len(pal)]) 116 | k.Special.SetColor(pal[8%len(pal)]) 117 | d.SetKeys(k) 118 | } 119 | 120 | func main() { 121 | flag.Parse() 122 | 123 | primaryColor, err := colorful.Hex(*primary) 124 | if err != nil { 125 | panic(err) 126 | } 127 | secondaryColor, err := colorful.Hex(*secondary) 128 | if err != nil { 129 | panic(err) 130 | } 131 | 132 | devs, err := razer.Devices() 133 | if err != nil { 134 | panic(err) 135 | } 136 | if len(devs) == 0 { 137 | log.Fatalln("No Razer devices found.") 138 | } 139 | d := devs[0] 140 | 141 | fmt.Printf("Found %s\n", d.String()) 142 | if d.Brightness() == 0 && *brightness == -1 { 143 | *brightness = 100 144 | } 145 | if *brightness >= 0 { 146 | d.SetBrightness(*brightness) 147 | } 148 | 149 | if *theme == "rainbow" { 150 | rainbowTheme(d) 151 | } else if *theme != "" { 152 | happyTheme(d, *theme) 153 | } else if *effect != "" { 154 | d.SetEffect(razer.NewEffect(razer.StringToEffectType(*effect), primaryColor, secondaryColor)) 155 | } else if *top { 156 | topMode(d) 157 | } else { 158 | d.SetEffect(razer.NewEffect(razer.EffectStatic, primaryColor, secondaryColor)) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /effects.go: -------------------------------------------------------------------------------- 1 | package razer 2 | 3 | import ( 4 | "fmt" 5 | "image/color" 6 | "strings" 7 | 8 | "github.com/godbus/dbus" 9 | ) 10 | 11 | // Effects known by the keyboard hardware itself 12 | const ( 13 | EffectNone = iota 14 | EffectStatic 15 | EffectReactive 16 | EffectWave 17 | EffectSpectrum 18 | EffectBreath 19 | EffectBreathDual 20 | EffectBreathRandom 21 | EffectStarlight 22 | EffectStarlightDual 23 | EffectStarlightRandom 24 | EffectRipple 25 | EffectRippleRandom 26 | ) 27 | 28 | // Effect represents a hardware effect 29 | type Effect struct { 30 | Name string 31 | Type EffectType 32 | 33 | primary color.Color 34 | secondary color.Color 35 | } 36 | 37 | // EffectType for type safety 38 | type EffectType int 39 | 40 | // NewEffect returns a new Effect 41 | func NewEffect(e EffectType, primary, secondary color.Color) Effect { 42 | return Effect{ 43 | Name: effectToDBusMethod(e), 44 | Type: e, 45 | primary: primary, 46 | secondary: secondary, 47 | } 48 | } 49 | 50 | // StringToEffectType converts a string to an Effect 51 | func StringToEffectType(s string) EffectType { 52 | switch strings.ToLower(s) { 53 | case "static": 54 | return EffectStatic 55 | case "reactive": 56 | return EffectReactive 57 | case "spectrum": 58 | return EffectSpectrum 59 | case "breath": 60 | return EffectBreath 61 | case "breathdual": 62 | return EffectBreathDual 63 | case "breathrandom": 64 | return EffectBreathRandom 65 | case "starlight": 66 | return EffectStarlight 67 | case "starlightdual": 68 | return EffectStarlightDual 69 | case "starlightrandom": 70 | return EffectStarlightRandom 71 | case "ripple": 72 | return EffectRipple 73 | case "ripplerandom": 74 | return EffectRippleRandom 75 | case "wave": 76 | return EffectWave 77 | } 78 | 79 | return EffectNone 80 | } 81 | 82 | // effectToDBusMethod returns the name of the DBus method matching the Effect 83 | func effectToDBusMethod(e EffectType) string { 84 | switch e { 85 | case EffectStatic: 86 | return "Static" 87 | case EffectReactive: 88 | return "Reactive" 89 | case EffectSpectrum: 90 | return "Spectrum" 91 | case EffectBreath: 92 | return "BreathSingle" 93 | case EffectBreathDual: 94 | return "BreathDual" 95 | case EffectBreathRandom: 96 | return "BreathRandom" 97 | case EffectStarlight: 98 | return "StarlightSingle" 99 | case EffectStarlightDual: 100 | return "StarlightDual" 101 | case EffectStarlightRandom: 102 | return "StarlightRandom" 103 | case EffectRipple: 104 | return "Ripple" 105 | case EffectRippleRandom: 106 | return "RippleRandomColour" 107 | case EffectWave: 108 | return "Wave" 109 | } 110 | 111 | return "None" 112 | } 113 | 114 | func (e Effect) arguments() []interface{} { 115 | var a []interface{} 116 | 117 | switch e.Type { 118 | case EffectStatic: 119 | fallthrough 120 | case EffectBreath: 121 | a = append(a, colorToEffectArg(e.primary)...) 122 | case EffectReactive: 123 | a = append(a, colorToEffectArg(e.primary)...) 124 | a = append(a, 1) 125 | case EffectBreathDual: 126 | a = append(a, colorToEffectArg(e.primary)...) 127 | a = append(a, colorToEffectArg(e.secondary)...) 128 | case EffectStarlight: 129 | a = append(a, 100) 130 | a = append(a, colorToEffectArg(e.primary)...) 131 | case EffectStarlightDual: 132 | a = append(a, 100) 133 | a = append(a, colorToEffectArg(e.primary)...) 134 | a = append(a, colorToEffectArg(e.secondary)...) 135 | case EffectStarlightRandom: 136 | a = append(a, 100) 137 | case EffectRipple: 138 | a = append(a, colorToEffectArg(e.primary)...) 139 | a = append(a, 0.0) 140 | case EffectRippleRandom: 141 | a = append(a, 0.0) 142 | case EffectWave: 143 | a = append(a, 1) 144 | } 145 | 146 | return a 147 | } 148 | 149 | // SetEffect activates an Effect 150 | func (d *Device) SetEffect(effect Effect) { 151 | var call *dbus.Call 152 | 153 | switch effect.Type { 154 | case EffectRipple: 155 | fallthrough 156 | case EffectRippleRandom: 157 | call = d.dbusObject.Call(fmt.Sprintf("razer.device.lighting.custom.set%s", effect.Name), 0, effect.arguments()...) 158 | default: 159 | call = d.dbusObject.Call(fmt.Sprintf("razer.device.lighting.chroma.set%s", effect.Name), 0, effect.arguments()...) 160 | } 161 | 162 | dbusCall(call) 163 | } 164 | 165 | // colorToEffectArg converts a color to a valid effect argument 166 | func colorToEffectArg(c color.Color) []interface{} { 167 | var a []interface{} 168 | r, g, b, _ := c.RGBA() 169 | a = append(a, uint8(r>>8)) 170 | a = append(a, uint8(g>>8)) 171 | a = append(a, uint8(b>>8)) 172 | 173 | return a 174 | } 175 | -------------------------------------------------------------------------------- /keys.go: -------------------------------------------------------------------------------- 1 | package razer 2 | 3 | import ( 4 | "image/color" 5 | ) 6 | 7 | // Key represents a single key 8 | type Key struct { 9 | Row int 10 | Col int 11 | Color color.Color 12 | } 13 | 14 | // A KeySet is a set of keys 15 | type KeySet []*Key 16 | 17 | // Keys represents all keys (divided in rows) on the keyboard 18 | type Keys struct { 19 | Letters KeySet 20 | FnKeys KeySet 21 | Numerics KeySet 22 | Symbols KeySet 23 | Commandos KeySet 24 | Actions KeySet 25 | Cursor KeySet 26 | Arrows KeySet 27 | Special KeySet 28 | 29 | rows []KeySet 30 | } 31 | 32 | // SetColor sets a single color on all keys in the KeySet 33 | func (k KeySet) SetColor(c color.Color) { 34 | for _, key := range k { 35 | key.Color = c 36 | } 37 | } 38 | 39 | // SetColor sets a single color on all keys 40 | func (k *Keys) SetColor(c color.Color) { 41 | for _, r := range k.rows { 42 | r.SetColor(c) 43 | } 44 | } 45 | 46 | // Key returns a single key 47 | func (k *Keys) Key(row, col int) *Key { 48 | return k.rows[row][col] 49 | } 50 | 51 | // KeySpan returns a span of keys 52 | func (k *Keys) KeySpan(row, startCol, endCol int) KeySet { 53 | var kk KeySet 54 | 55 | for x := startCol; x <= endCol; x++ { 56 | kk = append(kk, k.rows[row][x]) 57 | } 58 | 59 | return kk 60 | } 61 | 62 | // Keys returns the key-matrix for this device 63 | func (d *Device) Keys() Keys { 64 | if len(d.keys.rows) > 0 { 65 | return d.keys 66 | } 67 | 68 | k := Keys{} 69 | rows, cols := d.MatrixDimensions() 70 | for y := 0; y < rows; y++ { 71 | r := KeySet{} 72 | for x := 0; x < cols; x++ { 73 | r = append(r, &Key{ 74 | Row: y, 75 | Col: x, 76 | }) 77 | } 78 | k.rows = append(k.rows, r) 79 | } 80 | 81 | if rows == 6 && cols == 22 { 82 | // Ornata 83 | k.FnKeys = k.KeySpan(0, 2, 14) 84 | k.Letters = append(k.Letters, k.KeySpan(2, 2, 11)...) 85 | k.Letters = append(k.Letters, k.KeySpan(3, 2, 10)...) 86 | k.Letters = append(k.Letters, k.KeySpan(4, 3, 9)...) 87 | k.Numerics = append(k.Numerics, k.KeySpan(1, 2, 11)...) 88 | k.Numerics = append(k.Numerics, k.KeySpan(2, 18, 20)...) 89 | k.Numerics = append(k.Numerics, k.KeySpan(3, 18, 20)...) 90 | k.Numerics = append(k.Numerics, k.KeySpan(4, 18, 20)...) 91 | k.Numerics = append(k.Numerics, k.KeySpan(5, 18, 19)...) 92 | k.Symbols = append(k.Symbols, k.KeySpan(1, 1, 1)...) 93 | k.Symbols = append(k.Symbols, k.KeySpan(1, 12, 13)...) 94 | k.Symbols = append(k.Symbols, k.KeySpan(2, 12, 13)...) 95 | k.Symbols = append(k.Symbols, k.KeySpan(3, 11, 13)...) 96 | k.Symbols = append(k.Symbols, k.KeySpan(4, 2, 2)...) 97 | k.Symbols = append(k.Symbols, k.KeySpan(4, 10, 12)...) 98 | k.Symbols = append(k.Symbols, k.KeySpan(1, 19, 21)...) 99 | k.Symbols = append(k.Symbols, k.KeySpan(2, 21, 21)...) 100 | k.Symbols = append(k.Symbols, k.KeySpan(5, 20, 20)...) 101 | k.Commandos = append(k.Commandos, k.KeySpan(2, 1, 1)...) 102 | k.Commandos = append(k.Commandos, k.KeySpan(3, 1, 1)...) 103 | k.Commandos = append(k.Commandos, k.KeySpan(4, 1, 1)...) 104 | k.Commandos = append(k.Commandos, k.KeySpan(4, 14, 14)...) 105 | k.Commandos = append(k.Commandos, k.KeySpan(5, 1, 3)...) 106 | k.Commandos = append(k.Commandos, k.KeySpan(5, 11, 14)...) 107 | k.Actions = append(k.Actions, k.KeySpan(0, 1, 1)...) 108 | k.Actions = append(k.Actions, k.KeySpan(1, 14, 14)...) 109 | k.Actions = append(k.Actions, k.KeySpan(3, 14, 14)...) 110 | k.Actions = append(k.Actions, k.KeySpan(5, 4, 10)...) 111 | k.Actions = append(k.Actions, k.KeySpan(4, 21, 21)...) 112 | k.Cursor = append(k.Cursor, k.KeySpan(1, 15, 17)...) 113 | k.Cursor = append(k.Cursor, k.KeySpan(2, 15, 17)...) 114 | k.Arrows = append(k.Arrows, k.KeySpan(4, 16, 16)...) 115 | k.Arrows = append(k.Arrows, k.KeySpan(5, 15, 17)...) 116 | k.Special = append(k.Special, k.KeySpan(0, 15, 17)...) 117 | k.Special = append(k.Special, k.KeySpan(1, 18, 18)...) 118 | } 119 | 120 | d.keys = k 121 | return k 122 | } 123 | 124 | // SetKeys sets the colors for the entire keyboard 125 | func (d *Device) SetKeys(k Keys) { 126 | for i, r := range k.rows { 127 | var m []byte 128 | m = append(m, byte(i)) 129 | m = append(m, 0) 130 | m = append(m, byte(len(r)-1)) 131 | 132 | m = append(m, r.message()...) 133 | d.setKeyRow(m) 134 | } 135 | 136 | d.keys = k 137 | d.activateCustomSettings() 138 | } 139 | 140 | // setKeyRow sets the colors for an entire row of keys 141 | func (d *Device) setKeyRow(row []byte) { 142 | dbusCall(d.dbusObject.Call("razer.device.lighting.chroma.setKeyRow", 0, row)) 143 | } 144 | 145 | func (k KeySet) message() []byte { 146 | var m []byte 147 | 148 | for _, v := range k { 149 | var r, g, b uint32 150 | if v.Color != nil { 151 | r, g, b, _ = v.Color.RGBA() 152 | } 153 | m = append(m, uint8(r>>8)) 154 | m = append(m, uint8(g>>8)) 155 | m = append(m, uint8(b>>8)) 156 | } 157 | 158 | return m 159 | } 160 | --------------------------------------------------------------------------------