From 6179fa20b0b47628a645579a45b6f0cb327cf8c3 Mon Sep 17 00:00:00 2001
From: Nikola <1388673+jurkovic-nikola@users.noreply.github.com>
Date: Thu, 12 Dec 2024 21:32:28 +0100
Subject: [PATCH] scimitar rgb elite
---
README.md | 1 +
database/devices.json | 4 +
src/devices/devices.go | 102 +-
src/devices/elite/elite.go | 2 +-
src/devices/nightsabreWU/nightsabreWU.go | 4 +-
src/devices/scimitar/scimitar.go | 1406 ++++++++++++++++++++++
src/server/requests/requests.go | 28 +
src/server/server.go | 13 +
src/templates/templates.go | 1 +
static/js/mouse.js | 42 +-
web/scimitar.html | 185 +++
11 files changed, 1752 insertions(+), 36 deletions(-)
create mode 100644 src/devices/scimitar/scimitar.go
create mode 100644 web/scimitar.html
diff --git a/README.md b/README.md
index d3820ec..59d295d 100644
--- a/README.md
+++ b/README.md
@@ -55,6 +55,7 @@ Open source Linux interface for iCUE LINK Hub and other Corsair AIOs, Hubs.
| IRONCLAW RGB | `1b1c` | `1b5d` | DPI Control
RGB Control |
| IRONCLAW RGB WIRELESS | `1b1c` | `1b4c` | DPI Control
RGB Control |
| NIGHTSABRE WIRELESS | `1b1c` | `1bb8` | DPI Control
RGB Control |
+| SCIMITAR RGB ELITE | `1b1c` | `1be3` | DPI Control
RGB Control |
| ST100 RGB | `1b1c` | `0a34` | RGB |
| MM700 RGB | `1b1c` | `1b9b` | RGB |
| LT100 Smart Lighting Tower | `1b1c` | `0c23` | RGB |
diff --git a/database/devices.json b/database/devices.json
index f5db943..4cccca7 100644
--- a/database/devices.json
+++ b/database/devices.json
@@ -199,6 +199,10 @@
{
"productId": "1bb8",
"kernel": "hidraw*"
+ },
+ {
+ "productId": "1be3",
+ "kernel": "hidraw*"
}
]
}
diff --git a/src/devices/devices.go b/src/devices/devices.go
index 7c5cfd3..78ff3c1 100644
--- a/src/devices/devices.go
+++ b/src/devices/devices.go
@@ -28,6 +28,7 @@ import (
"OpenLinkHub/src/devices/nightsabreW"
"OpenLinkHub/src/devices/nightsabreWU"
"OpenLinkHub/src/devices/psuhid"
+ "OpenLinkHub/src/devices/scimitar"
"OpenLinkHub/src/devices/slipstream"
"OpenLinkHub/src/devices/st100"
"OpenLinkHub/src/devices/xc7"
@@ -43,34 +44,35 @@ import (
)
const (
- productTypeLinkHub = 0
- productTypeCC = 1
- productTypeCCXT = 2
- productTypeElite = 3
- productTypeLNCore = 4
- productTypeLnPro = 5
- productTypeCPro = 6
- productTypeXC7 = 7
- productTypeMemory = 8
- productTypeK65PM = 101
- productTypeK70Core = 102
- productTypeK55Core = 103
- productTypeK70Pro = 104
- productTypeK65Plus = 105
- productTypeK65PlusW = 106
- productTypeK100Air = 107
- productTypeK100AirW = 108
- productTypeK100 = 109
- productTypeKatarPro = 201
- productTypeIronClawRgb = 202
- productTypeIronClawRgbW = 203
- productTypeIronClawRgbWU = 204
- productTypeNightsabreW = 205
- productTypeNightsabreWU = 206
- productTypeST100 = 401
- productTypeMM700 = 402
- productTypeLT100 = 403
- productTypePSUHid = 501
+ productTypeLinkHub = 0
+ productTypeCC = 1
+ productTypeCCXT = 2
+ productTypeElite = 3
+ productTypeLNCore = 4
+ productTypeLnPro = 5
+ productTypeCPro = 6
+ productTypeXC7 = 7
+ productTypeMemory = 8
+ productTypeK65PM = 101
+ productTypeK70Core = 102
+ productTypeK55Core = 103
+ productTypeK70Pro = 104
+ productTypeK65Plus = 105
+ productTypeK65PlusW = 106
+ productTypeK100Air = 107
+ productTypeK100AirW = 108
+ productTypeK100 = 109
+ productTypeKatarPro = 201
+ productTypeIronClawRgb = 202
+ productTypeIronClawRgbW = 203
+ productTypeIronClawRgbWU = 204
+ productTypeNightsabreW = 205
+ productTypeNightsabreWU = 206
+ productTypeScimitarRgbElite = 207
+ productTypeST100 = 401
+ productTypeMM700 = 402
+ productTypeLT100 = 403
+ productTypePSUHid = 501
)
type AIOData struct {
@@ -101,7 +103,7 @@ var (
devices = make(map[string]*Device, 0)
products = make(map[string]Product, 0)
keyboards = []uint16{7127, 7165, 7166, 7110, 7083, 11024, 11015, 7109, 7091}
- mouses = []uint16{7059, 7005, 6988, 7096}
+ mouses = []uint16{7059, 7005, 6988, 7096, 7139}
pads = []uint16{7067}
dongles = []uint16{7132, 7078}
)
@@ -280,7 +282,7 @@ func SaveMouseDPI(deviceId string, stages map[int]uint16) uint8 {
return 0
}
-// SaveMouseZoneColors will save mouse DPI values
+// SaveMouseZoneColors will save mouse zone colors
func SaveMouseZoneColors(deviceId string, dpi rgb.Color, zones map[int]rgb.Color) uint8 {
if device, ok := devices[deviceId]; ok {
methodName := "SaveMouseZoneColors"
@@ -303,6 +305,29 @@ func SaveMouseZoneColors(deviceId string, dpi rgb.Color, zones map[int]rgb.Color
return 0
}
+// SaveMouseDpiColors will save mouse DPI colors
+func SaveMouseDpiColors(deviceId string, dpi rgb.Color, zones map[int]rgb.Color) uint8 {
+ if device, ok := devices[deviceId]; ok {
+ methodName := "SaveMouseDpiColors"
+ method := reflect.ValueOf(GetDevice(device.Serial)).MethodByName(methodName)
+ if !method.IsValid() {
+ logger.Log(logger.Fields{"method": methodName}).Warn("Method not found or method is not supported for this device type")
+ return 0
+ } else {
+ var reflectArgs []reflect.Value
+ reflectArgs = append(reflectArgs, reflect.ValueOf(dpi))
+ reflectArgs = append(reflectArgs, reflect.ValueOf(zones))
+ results := method.Call(reflectArgs)
+ if len(results) > 0 {
+ val := results[0]
+ uintResult := val.Uint()
+ return uint8(uintResult)
+ }
+ }
+ }
+ return 0
+}
+
// UpdateExternalHubDeviceAmount will update a device amount connected to an external-LED hub
func UpdateExternalHubDeviceAmount(deviceId string, portId, deviceType int) uint8 {
if device, ok := devices[deviceId]; ok {
@@ -1459,6 +1484,23 @@ func Init() {
}
}(vendorId, productId, key)
}
+ case 7139:
+ {
+ go func(vendorId, productId uint16, key string) {
+ dev := scimitar.Init(vendorId, productId, key)
+ if dev == nil {
+ return
+ }
+ devices[dev.Serial] = &Device{
+ ProductType: productTypeScimitarRgbElite,
+ Product: dev.Product,
+ Serial: dev.Serial,
+ Firmware: dev.Firmware,
+ Image: "icon-mouse.svg",
+ Instance: dev,
+ }
+ }(vendorId, productId, key)
+ }
case 0: // Memory
{
go func(serialId string) {
diff --git a/src/devices/elite/elite.go b/src/devices/elite/elite.go
index fb427a7..457bce4 100644
--- a/src/devices/elite/elite.go
+++ b/src/devices/elite/elite.go
@@ -701,7 +701,7 @@ func (d *Device) setDeviceColor() {
r.RGBBrightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider)
r.RGBStartColor.Brightness = r.RGBBrightness
r.RGBEndColor.Brightness = r.RGBBrightness
-
+
r.Inverted = d.InvertRgb
switch d.Devices[k].RGB {
case "off":
diff --git a/src/devices/nightsabreWU/nightsabreWU.go b/src/devices/nightsabreWU/nightsabreWU.go
index 0dd9e5d..b7cd0b6 100644
--- a/src/devices/nightsabreWU/nightsabreWU.go
+++ b/src/devices/nightsabreWU/nightsabreWU.go
@@ -700,7 +700,7 @@ func (d *Device) saveDeviceProfile() {
3: {
Name: "Stage 4",
Value: 1600,
- PackerIndex: 3,
+ PackerIndex: 4,
ColorIndex: map[int][]int{
0: {13, 28, 43},
1: {14, 29, 44},
@@ -709,7 +709,7 @@ func (d *Device) saveDeviceProfile() {
4: {
Name: "Stage 5",
Value: 3200,
- PackerIndex: 3,
+ PackerIndex: 5,
ColorIndex: map[int][]int{
0: {14, 29, 44},
},
diff --git a/src/devices/scimitar/scimitar.go b/src/devices/scimitar/scimitar.go
new file mode 100644
index 0000000..aacd8b2
--- /dev/null
+++ b/src/devices/scimitar/scimitar.go
@@ -0,0 +1,1406 @@
+package scimitar
+
+// Package: CORSAIR SCIMITAR RGB ELITE
+// This is the primary package for CORSAIR SCIMITAR RGB ELITE.
+// All device actions are controlled from this package.
+// Author: Nikola Jurkovic
+// License: GPL-3.0 or later
+
+import (
+ "OpenLinkHub/src/common"
+ "OpenLinkHub/src/config"
+ "OpenLinkHub/src/logger"
+ "OpenLinkHub/src/rgb"
+ "encoding/binary"
+ "encoding/json"
+ "fmt"
+ "github.com/sstallion/go-hid"
+ "os"
+ "regexp"
+ "strings"
+ "sync"
+ "time"
+)
+
+type ZoneColors struct {
+ Color *rgb.Color
+ ColorIndex []int
+ Name string
+}
+
+// DeviceProfile struct contains all device profile
+type DeviceProfile struct {
+ Active bool
+ Path string
+ Product string
+ Serial string
+ Brightness uint8
+ RGBProfile string
+ BrightnessSlider *uint8
+ Label string
+ Profile int
+ ZoneColors map[int]ZoneColors
+ Profiles map[int]DPIProfile
+ SleepMode int
+}
+
+type DPIProfile struct {
+ Name string `json:"name"`
+ Value uint16
+ PackerIndex int
+ ColorIndex map[int][]int
+ Color *rgb.Color
+}
+
+type Device struct {
+ Debug bool
+ dev *hid.Device
+ listener *hid.Device
+ Manufacturer string `json:"manufacturer"`
+ Product string `json:"product"`
+ Serial string `json:"serial"`
+ Firmware string `json:"firmware"`
+ activeRgb *rgb.ActiveRGB
+ UserProfiles map[string]*DeviceProfile `json:"userProfiles"`
+ Devices map[int]string `json:"devices"`
+ DeviceProfile *DeviceProfile
+ OriginalProfile *DeviceProfile
+ Template string
+ VendorId uint16
+ ProductId uint16
+ Brightness map[int]string
+ LEDChannels int
+ ChangeableLedChannels int
+ CpuTemp float32
+ GpuTemp float32
+ Layouts []string
+ Rgb *rgb.RGB
+ SleepModes map[int]string
+}
+
+var (
+ pwd = ""
+ cmdSoftwareMode = []byte{0x01, 0x03, 0x00, 0x02}
+ cmdHardwareMode = []byte{0x01, 0x03, 0x00, 0x01}
+ cmdGetFirmware = []byte{0x02, 0x13}
+ cmdWriteColor = []byte{0x06, 0x00}
+ cmdOpenEndpoint = []byte{0x0d, 0x00, 0x01}
+ cmdOpenWriteEndpoint = []byte{0x01, 0x0d, 0x00, 0x01}
+ cmdSetDpi = map[int][]byte{
+ 0: {0x01, 0x21, 0x00},
+ 1: {0x01, 0x22, 0x00},
+ }
+ cmdSleep = map[int][]byte{0: {0x01, 0x37, 0x00}, 1: {0x01, 0x0e, 0x00}}
+ mutex sync.Mutex
+ timerKeepAlive = &time.Ticker{}
+ keepAliveChan = make(chan bool)
+ bufferSize = 128
+ bufferSizeWrite = bufferSize + 1
+ headerSize = 2
+ headerWriteSize = 4
+ minDpiValue = 100
+ maxDpiValue = 18000
+ deviceKeepAlive = 20000
+)
+
+func Init(vendorId, productId uint16, key string) *Device {
+ // Set global working directory
+ pwd = config.GetConfig().ConfigPath
+
+ dev, err := hid.OpenPath(key)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "vendorId": vendorId, "productId": productId}).Error("Unable to open HID device")
+ return nil
+ }
+
+ // Init new struct with HID device
+ d := &Device{
+ dev: dev,
+ Template: "scimitar.html",
+ VendorId: vendorId,
+ ProductId: productId,
+ Firmware: "n/a",
+ Brightness: map[int]string{
+ 0: "RGB Profile",
+ 1: "33 %",
+ 2: "66 %",
+ 3: "100 %",
+ },
+ Product: "SCIMITAR RGB ELITE",
+ SleepModes: map[int]string{
+ 1: "1 minute",
+ 5: "5 minutes",
+ 10: "10 minutes",
+ 15: "15 minutes",
+ 30: "30 minutes",
+ 60: "1 hour",
+ },
+ LEDChannels: 5,
+ ChangeableLedChannels: 4,
+ }
+
+ d.getDebugMode() // Debug mode
+ d.getManufacturer() // Manufacturer
+ d.getSerial() // Serial
+ d.loadRgb() // Load RGB
+ d.loadDeviceProfiles() // Load all device profiles
+ d.saveDeviceProfile() // Save profile
+ d.getDeviceFirmware() // Firmware
+ d.setSoftwareMode() // Activate software mode
+ d.initLeds() // Init LED ports
+ d.setDeviceColor() // Device color
+ d.toggleDPI() // DPI
+ d.controlListener() // Control listener
+ d.setKeepAlive() // Keepalive
+ return d
+}
+
+// Stop will stop all device operations and switch a device back to hardware mode
+func (d *Device) Stop() {
+ logger.Log(logger.Fields{"serial": d.Serial}).Info("Stopping device...")
+ if d.activeRgb != nil {
+ d.activeRgb.Stop()
+ }
+
+ timerKeepAlive.Stop()
+ keepAliveChan <- true
+
+ d.setHardwareMode()
+ if d.dev != nil {
+ err := d.dev.Close()
+ if err != nil {
+ return
+ }
+ }
+
+ if d.listener != nil {
+ err := d.listener.Close()
+ if err != nil {
+ return
+ }
+ }
+}
+
+// getManufacturer will return device manufacturer
+func (d *Device) getManufacturer() {
+ manufacturer, err := d.dev.GetMfrStr()
+ if err != nil {
+ logger.Log(logger.Fields{"error": err}).Fatal("Unable to get manufacturer")
+ }
+ d.Manufacturer = manufacturer
+}
+
+// getSerial will return device serial number
+func (d *Device) getSerial() {
+ serial, err := d.dev.GetSerialNbr()
+ if err != nil {
+ logger.Log(logger.Fields{"error": err}).Fatal("Unable to get device serial number")
+ }
+ d.Serial = serial
+}
+
+// loadRgb will load RGB file if found, or create the default.
+func (d *Device) loadRgb() {
+ rgbDirectory := pwd + "/database/rgb/"
+ rgbFilename := rgbDirectory + d.Serial + ".json"
+
+ // Check if filename has .json extension
+ if !common.IsValidExtension(rgbFilename, ".json") {
+ return
+ }
+
+ if !common.FileExists(rgbFilename) {
+ profile := rgb.GetRGB()
+ profile.Device = d.Product
+
+ // Convert to JSON
+ buffer, err := json.MarshalIndent(profile, "", " ")
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to encode RGB json")
+ return
+ }
+
+ // Create profile filename
+ file, err := os.Create(rgbFilename)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to create RGB json file")
+ return
+ }
+
+ // Write JSON buffer to file
+ _, err = file.Write(buffer)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to write to RGB json file")
+ return
+ }
+
+ // Close file
+ err = file.Close()
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to close RGB json file")
+ return
+ }
+ }
+
+ file, err := os.Open(rgbFilename)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to load RGB")
+ return
+ }
+ if err = json.NewDecoder(file).Decode(&d.Rgb); err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": rgbFilename}).Warn("Unable to decode profile")
+ return
+ }
+ err = file.Close()
+ if err != nil {
+ logger.Log(logger.Fields{"location": rgbFilename, "serial": d.Serial}).Warn("Failed to close file handle")
+ }
+}
+
+// GetRgbProfile will return rgb.Profile struct
+func (d *Device) GetRgbProfile(profile string) *rgb.Profile {
+ if d.Rgb == nil {
+ return nil
+ }
+
+ if val, ok := d.Rgb.Profiles[profile]; ok {
+ return &val
+ }
+ return nil
+}
+
+// GetDeviceTemplate will return device template name
+func (d *Device) GetDeviceTemplate() string {
+ return d.Template
+}
+
+// ChangeDeviceProfile will change device profile
+func (d *Device) ChangeDeviceProfile(profileName string) uint8 {
+ if profile, ok := d.UserProfiles[profileName]; ok {
+ currentProfile := d.DeviceProfile
+ currentProfile.Active = false
+ d.DeviceProfile = currentProfile
+ d.saveDeviceProfile()
+
+ // RGB reset
+ if d.activeRgb != nil {
+ d.activeRgb.Exit <- true // Exit current RGB mode
+ d.activeRgb = nil
+ }
+
+ newProfile := profile
+ newProfile.Active = true
+ d.DeviceProfile = newProfile
+ d.saveDeviceProfile()
+ d.setDeviceColor()
+ return 1
+ }
+ return 0
+}
+
+// UpdateRgbProfile will update device RGB profile
+func (d *Device) UpdateRgbProfile(_ int, profile string) uint8 {
+ if d.GetRgbProfile(profile) == nil {
+ logger.Log(logger.Fields{"serial": d.Serial, "profile": profile}).Warn("Non-existing RGB profile")
+ return 0
+ }
+ d.DeviceProfile.RGBProfile = profile // Set profile
+ d.saveDeviceProfile() // Save profile
+ if d.activeRgb != nil {
+ d.activeRgb.Exit <- true // Exit current RGB mode
+ d.activeRgb = nil
+ }
+ d.setDeviceColor() // Restart RGB
+ return 1
+}
+
+// ChangeDeviceBrightness will change device brightness
+func (d *Device) ChangeDeviceBrightness(mode uint8) uint8 {
+ d.DeviceProfile.Brightness = mode
+ d.saveDeviceProfile()
+ if d.activeRgb != nil {
+ d.activeRgb.Exit <- true // Exit current RGB mode
+ d.activeRgb = nil
+ }
+ d.setDeviceColor() // Restart RGB
+ return 1
+}
+
+// ChangeDeviceBrightnessValue will change device brightness via slider
+func (d *Device) ChangeDeviceBrightnessValue(value uint8) uint8 {
+ if value < 0 || value > 100 {
+ return 0
+ }
+
+ d.DeviceProfile.BrightnessSlider = &value
+ d.saveDeviceProfile()
+
+ if d.DeviceProfile.RGBProfile == "static" || d.DeviceProfile.RGBProfile == "mouse" {
+ if d.activeRgb != nil {
+ d.activeRgb.Exit <- true // Exit current RGB mode
+ d.activeRgb = nil
+ }
+ d.setDeviceColor() // Restart RGB
+ }
+ return 1
+}
+
+// SaveUserProfile will generate a new user profile configuration and save it to a file
+func (d *Device) SaveUserProfile(profileName string) uint8 {
+ if d.DeviceProfile != nil {
+ profilePath := pwd + "/database/profiles/" + d.Serial + "-" + profileName + ".json"
+
+ newProfile := d.DeviceProfile
+ newProfile.Path = profilePath
+ newProfile.Active = false
+
+ buffer, err := json.Marshal(newProfile)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err}).Error("Unable to convert to json format")
+ return 0
+ }
+
+ // Create profile filename
+ file, err := os.Create(profilePath)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to create new device profile")
+ return 0
+ }
+
+ _, err = file.Write(buffer)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to write data")
+ return 0
+ }
+
+ err = file.Close()
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "location": newProfile.Path}).Error("Unable to close file handle")
+ return 0
+ }
+ d.loadDeviceProfiles()
+ return 1
+ }
+ return 0
+}
+
+// SaveMouseDPI will save mouse DPI
+func (d *Device) SaveMouseDPI(stages map[int]uint16) uint8 {
+ i := 0
+ if d.DeviceProfile == nil {
+ return 0
+ }
+
+ if len(stages) == 0 {
+ return 0
+ }
+
+ for key, stage := range stages {
+ if _, ok := d.DeviceProfile.Profiles[key]; ok {
+ profile := d.DeviceProfile.Profiles[key]
+ if stage > uint16(maxDpiValue) {
+ continue
+ }
+ if stage < uint16(minDpiValue) {
+ continue
+ }
+ profile.Value = stage
+ d.DeviceProfile.Profiles[key] = profile
+ i++
+ }
+ }
+
+ if i > 0 {
+ d.saveDeviceProfile()
+ d.toggleDPI()
+ return 1
+ }
+ return 0
+}
+
+// SaveMouseZoneColors will save mouse zone colors
+func (d *Device) SaveMouseZoneColors(dpi rgb.Color, zoneColors map[int]rgb.Color) uint8 {
+ i := 0
+ if d.DeviceProfile == nil {
+ return 0
+ }
+ if dpi.Red > 255 ||
+ dpi.Green > 255 ||
+ dpi.Blue > 255 ||
+ dpi.Red < 0 ||
+ dpi.Green < 0 ||
+ dpi.Blue < 0 {
+ return 0
+ }
+
+ // Zone Colors
+ for key, zone := range zoneColors {
+ if zone.Red > 255 ||
+ zone.Green > 255 ||
+ zone.Blue > 255 ||
+ zone.Red < 0 ||
+ zone.Green < 0 ||
+ zone.Blue < 0 {
+ continue
+ }
+ if zoneColor, ok := d.DeviceProfile.ZoneColors[key]; ok {
+ zoneColor.Color.Red = zone.Red
+ zoneColor.Color.Green = zone.Green
+ zoneColor.Color.Blue = zone.Blue
+ zoneColor.Color.Hex = fmt.Sprintf("#%02x%02x%02x", int(zone.Red), int(zone.Green), int(zone.Blue))
+ }
+ i++
+ }
+
+ if i > 0 {
+ d.saveDeviceProfile()
+ if d.activeRgb != nil {
+ d.activeRgb.Exit <- true // Exit current RGB mode
+ d.activeRgb = nil
+ }
+ d.setDeviceColor() // Restart RGB
+ return 1
+ }
+ return 0
+}
+
+// SaveMouseDpiColors will save mouse dpi colors
+func (d *Device) SaveMouseDpiColors(dpi rgb.Color, dpiColors map[int]rgb.Color) uint8 {
+ i := 0
+ if d.DeviceProfile == nil {
+ return 0
+ }
+ if dpi.Red > 255 ||
+ dpi.Green > 255 ||
+ dpi.Blue > 255 ||
+ dpi.Red < 0 ||
+ dpi.Green < 0 ||
+ dpi.Blue < 0 {
+ return 0
+ }
+
+ // Zone Colors
+ for key, zone := range dpiColors {
+ if zone.Red > 255 ||
+ zone.Green > 255 ||
+ zone.Blue > 255 ||
+ zone.Red < 0 ||
+ zone.Green < 0 ||
+ zone.Blue < 0 {
+ continue
+ }
+ if profileColor, ok := d.DeviceProfile.Profiles[key]; ok {
+ profileColor.Color.Red = zone.Red
+ profileColor.Color.Green = zone.Green
+ profileColor.Color.Blue = zone.Blue
+ profileColor.Color.Hex = fmt.Sprintf("#%02x%02x%02x", int(zone.Red), int(zone.Green), int(zone.Blue))
+ }
+ i++
+ }
+
+ if i > 0 {
+ d.saveDeviceProfile()
+ if d.activeRgb != nil {
+ d.activeRgb.Exit <- true // Exit current RGB mode
+ d.activeRgb = nil
+ }
+ d.setDeviceColor() // Restart RGB
+ return 1
+ }
+ return 0
+}
+
+// getManufacturer will return device manufacturer
+func (d *Device) getDebugMode() {
+ d.Debug = config.GetConfig().Debug
+}
+
+// setHardwareMode will switch a device to hardware mode
+func (d *Device) setHardwareMode() {
+ _, err := d.transfer(cmdHardwareMode, nil)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode")
+ }
+}
+
+// setSoftwareMode will switch a device to software mode
+func (d *Device) setSoftwareMode() {
+ _, err := d.transfer(cmdSoftwareMode, nil)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode")
+ }
+}
+
+// GetSleepMode will return current sleep mode
+func (d *Device) GetSleepMode() int {
+ if d.DeviceProfile != nil {
+ return d.DeviceProfile.SleepMode
+ }
+ return 0
+}
+
+// getDeviceFirmware will return a device firmware version out as string
+func (d *Device) getDeviceFirmware() {
+ fw, err := d.transfer(
+ cmdGetFirmware,
+ nil,
+ )
+ if err != nil {
+ logger.Log(logger.Fields{"error": err}).Error("Unable to write to a device")
+ }
+
+ v1, v2, v3 := int(fw[3]), int(fw[4]), int(binary.LittleEndian.Uint16(fw[5:7]))
+ d.Firmware = fmt.Sprintf("%d.%d.%d", v1, v2, v3)
+}
+
+// saveDeviceProfile will save device profile for persistent configuration
+func (d *Device) saveDeviceProfile() {
+ var defaultBrightness = uint8(100)
+ profilePath := pwd + "/database/profiles/" + d.Serial + ".json"
+
+ deviceProfile := &DeviceProfile{
+ Product: d.Product,
+ Serial: d.Serial,
+ Path: profilePath,
+ BrightnessSlider: &defaultBrightness,
+ }
+
+ // First save, assign saved profile to a device
+ if d.DeviceProfile == nil {
+ // RGB, Label
+ deviceProfile.RGBProfile = "mouse"
+ deviceProfile.Label = "Mouse"
+ deviceProfile.Active = true
+ deviceProfile.ZoneColors = map[int]ZoneColors{
+ 0: { // Front
+ ColorIndex: []int{2, 7, 12},
+ Color: &rgb.Color{
+ Red: 255,
+ Green: 0,
+ Blue: 0,
+ Brightness: 1,
+ Hex: fmt.Sprintf("#%02x%02x%02x", 255, 0, 0),
+ },
+ Name: "Front",
+ },
+ 1: { // Scroll
+ ColorIndex: []int{1, 6, 11},
+ Color: &rgb.Color{
+ Red: 255,
+ Green: 255,
+ Blue: 0,
+ Brightness: 1,
+ Hex: fmt.Sprintf("#%02x%02x%02x", 255, 255, 0),
+ },
+ Name: "Scroll",
+ },
+ 2: { // Side
+ ColorIndex: []int{3, 8, 13},
+ Color: &rgb.Color{
+ Red: 0,
+ Green: 255,
+ Blue: 255,
+ Brightness: 1,
+ Hex: fmt.Sprintf("#%02x%02x%02x", 0, 255, 255),
+ },
+ Name: "Side",
+ },
+ 3: { // Logo
+ ColorIndex: []int{0, 5, 10},
+ Color: &rgb.Color{
+ Red: 0,
+ Green: 255,
+ Blue: 255,
+ Brightness: 1,
+ Hex: fmt.Sprintf("#%02x%02x%02x", 0, 255, 255),
+ },
+ Name: "Logo",
+ },
+ }
+ deviceProfile.Profiles = map[int]DPIProfile{
+ 0: {
+ Name: "Stage 1",
+ Value: 800,
+ PackerIndex: 1,
+ ColorIndex: map[int][]int{
+ 0: {4, 9, 14},
+ },
+ Color: &rgb.Color{
+ Red: 255,
+ Green: 0,
+ Blue: 0,
+ Brightness: 1,
+ Hex: fmt.Sprintf("#%02x%02x%02x", 255, 0, 0),
+ },
+ },
+ 1: {
+ Name: "Stage 2",
+ Value: 1500,
+ PackerIndex: 2,
+ ColorIndex: map[int][]int{
+ 0: {4, 9, 14},
+ },
+ Color: &rgb.Color{
+ Red: 255,
+ Green: 255,
+ Blue: 255,
+ Brightness: 1,
+ Hex: fmt.Sprintf("#%02x%02x%02x", 255, 255, 255),
+ },
+ },
+ 2: {
+ Name: "Stage 3",
+ Value: 3000,
+ PackerIndex: 3,
+ ColorIndex: map[int][]int{
+ 0: {4, 9, 14},
+ },
+ Color: &rgb.Color{
+ Red: 0,
+ Green: 255,
+ Blue: 0,
+ Brightness: 1,
+ Hex: fmt.Sprintf("#%02x%02x%02x", 0, 255, 0),
+ },
+ },
+ 3: {
+ Name: "Stage 4",
+ Value: 6000,
+ PackerIndex: 4,
+ ColorIndex: map[int][]int{
+ 0: {4, 9, 14},
+ },
+ Color: &rgb.Color{
+ Red: 255,
+ Green: 0,
+ Blue: 255,
+ Brightness: 1,
+ Hex: fmt.Sprintf("#%02x%02x%02x", 255, 0, 255),
+ },
+ },
+ 4: {
+ Name: "Stage 5",
+ Value: 9000,
+ PackerIndex: 5,
+ ColorIndex: map[int][]int{
+ 0: {4, 9, 14},
+ },
+ Color: &rgb.Color{
+ Red: 0,
+ Green: 255,
+ Blue: 255,
+ Brightness: 1,
+ Hex: fmt.Sprintf("#%02x%02x%02x", 0, 255, 255),
+ },
+ },
+ }
+ deviceProfile.Profile = 1
+ deviceProfile.SleepMode = 15
+ } else {
+ if d.DeviceProfile.BrightnessSlider == nil {
+ deviceProfile.BrightnessSlider = &defaultBrightness
+ d.DeviceProfile.BrightnessSlider = &defaultBrightness
+ } else {
+ deviceProfile.BrightnessSlider = d.DeviceProfile.BrightnessSlider
+ }
+ deviceProfile.Active = d.DeviceProfile.Active
+ deviceProfile.Brightness = d.DeviceProfile.Brightness
+ deviceProfile.RGBProfile = d.DeviceProfile.RGBProfile
+ deviceProfile.Label = d.DeviceProfile.Label
+ deviceProfile.Profiles = d.DeviceProfile.Profiles
+ deviceProfile.Profile = d.DeviceProfile.Profile
+ deviceProfile.ZoneColors = d.DeviceProfile.ZoneColors
+ deviceProfile.SleepMode = d.DeviceProfile.SleepMode
+
+ if len(d.DeviceProfile.Path) < 1 {
+ deviceProfile.Path = profilePath
+ d.DeviceProfile.Path = profilePath
+ } else {
+ deviceProfile.Path = d.DeviceProfile.Path
+ }
+ }
+
+ // Convert to JSON
+ buffer, err := json.MarshalIndent(deviceProfile, "", " ")
+ if err != nil {
+ logger.Log(logger.Fields{"error": err}).Error("Unable to convert to json format")
+ return
+ }
+
+ // Create profile filename
+ file, fileErr := os.Create(deviceProfile.Path)
+ if fileErr != nil {
+ logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Error("Unable to create new device profile")
+ return
+ }
+
+ // Write JSON buffer to file
+ _, err = file.Write(buffer)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Error("Unable to write data")
+ return
+ }
+
+ // Close file
+ err = file.Close()
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "location": deviceProfile.Path}).Error("Unable to close file handle")
+ }
+
+ d.loadDeviceProfiles() // Reload
+}
+
+// UpdateSleepTimer will update device sleep timer
+func (d *Device) UpdateSleepTimer(minutes int) uint8 {
+ if d.DeviceProfile != nil {
+ d.DeviceProfile.SleepMode = minutes
+ d.saveDeviceProfile()
+ d.setSleepTimer()
+ return 1
+ }
+ return 0
+}
+
+// setSleepTimer will set device sleep timer
+func (d *Device) setSleepTimer() uint8 {
+ if d.DeviceProfile != nil {
+ changed := 0
+ _, err := d.transfer(cmdOpenWriteEndpoint, nil)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change device sleep timer")
+ return 0
+ }
+
+ buf := make([]byte, 4)
+ sleep := d.DeviceProfile.SleepMode * (60 * 1000)
+ binary.LittleEndian.PutUint32(buf, uint32(sleep))
+
+ for i := 0; i < 2; i++ {
+ command := cmdSleep[i]
+ _, err = d.transfer(command, buf)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Warn("Unable to change device sleep timer")
+ continue
+ }
+ changed++
+ }
+
+ if changed > 0 {
+ return 1
+ }
+ }
+ return 0
+}
+
+// loadDeviceProfiles will load custom user profiles
+func (d *Device) loadDeviceProfiles() {
+ profileList := make(map[string]*DeviceProfile, 0)
+ userProfileDirectory := pwd + "/database/profiles/"
+
+ files, err := os.ReadDir(userProfileDirectory)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "location": userProfileDirectory, "serial": d.Serial}).Error("Unable to read content of a folder")
+ return
+ }
+
+ for _, fi := range files {
+ pf := &DeviceProfile{}
+ if fi.IsDir() {
+ continue // Exclude folders if any
+ }
+
+ // Define a full path of filename
+ profileLocation := userProfileDirectory + fi.Name()
+
+ // Check if filename has .json extension
+ if !common.IsValidExtension(profileLocation, ".json") {
+ continue
+ }
+
+ fileName := strings.Split(fi.Name(), ".")[0]
+ if m, _ := regexp.MatchString("^[a-zA-Z0-9-]+$", fileName); !m {
+ continue
+ }
+
+ fileSerial := ""
+ if strings.Contains(fileName, "-") {
+ fileSerial = strings.Split(fileName, "-")[0]
+ } else {
+ fileSerial = fileName
+ }
+
+ if fileSerial != d.Serial {
+ continue
+ }
+
+ file, err := os.Open(profileLocation)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": profileLocation}).Warn("Unable to load profile")
+ continue
+ }
+ if err = json.NewDecoder(file).Decode(pf); err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial, "location": profileLocation}).Warn("Unable to decode profile")
+ continue
+ }
+ err = file.Close()
+ if err != nil {
+ logger.Log(logger.Fields{"location": profileLocation, "serial": d.Serial}).Warn("Failed to close file handle")
+ }
+
+ if pf.Serial == d.Serial {
+ if fileName == d.Serial {
+ profileList["default"] = pf
+ } else {
+ name := strings.Split(fileName, "-")[1]
+ profileList[name] = pf
+ }
+ logger.Log(logger.Fields{"location": profileLocation, "serial": d.Serial}).Info("Loaded custom user profile")
+ }
+ }
+ d.UserProfiles = profileList
+ d.getDeviceProfile()
+}
+
+// getDeviceProfile will load persistent device configuration
+func (d *Device) getDeviceProfile() {
+ if len(d.UserProfiles) == 0 {
+ logger.Log(logger.Fields{"serial": d.Serial}).Warn("No profile found for device. Probably initial start")
+ } else {
+ for _, pf := range d.UserProfiles {
+ if pf.Active {
+ d.DeviceProfile = pf
+ }
+ }
+ }
+}
+
+// initLeds will initialize LED endpoint
+func (d *Device) initLeds() {
+ _, err := d.transfer(cmdOpenEndpoint, nil)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err}).Error("Unable to change device mode")
+ }
+}
+
+// setDeviceColor will activate and set device RGB
+func (d *Device) setDeviceColor() {
+ buf := make([]byte, d.LEDChannels*3)
+
+ // Reset
+ for i := 0; i < d.LEDChannels*3; i++ {
+ buf[i] = 0x00
+ }
+ d.writeColor(buf)
+
+ if d.DeviceProfile == nil {
+ logger.Log(logger.Fields{"serial": d.Serial}).Error("Unable to set color. DeviceProfile is null!")
+ return
+ }
+
+ // DPI
+ dpiColor := d.DeviceProfile.Profiles[d.DeviceProfile.Profile].Color
+ dpiColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider)
+ dpiColor = rgb.ModifyBrightness(*dpiColor)
+
+ dpiLeds := d.DeviceProfile.Profiles[d.DeviceProfile.Profile]
+ for i := 0; i < len(dpiLeds.ColorIndex); i++ {
+ dpiColorIndexRange := dpiLeds.ColorIndex[i]
+ for key, dpiColorIndex := range dpiColorIndexRange {
+ switch key {
+ case 0: // Red
+ buf[dpiColorIndex] = byte(dpiColor.Red)
+ case 1: // Green
+ buf[dpiColorIndex] = byte(dpiColor.Green)
+ case 2: // Blue
+ buf[dpiColorIndex] = byte(dpiColor.Blue)
+ }
+ }
+ }
+
+ if d.DeviceProfile.RGBProfile == "mouse" {
+ for _, zoneColor := range d.DeviceProfile.ZoneColors {
+ zoneColor.Color.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider)
+ zoneColor.Color = rgb.ModifyBrightness(*zoneColor.Color)
+ zoneColorIndexRange := zoneColor.ColorIndex
+ for key, zoneColorIndex := range zoneColorIndexRange {
+ switch key {
+ case 0: // Red
+ buf[zoneColorIndex] = byte(zoneColor.Color.Red)
+ case 1: // Green
+ buf[zoneColorIndex] = byte(zoneColor.Color.Green)
+ case 2: // Blue
+ buf[zoneColorIndex] = byte(zoneColor.Color.Blue)
+ }
+ }
+ }
+ d.writeColor(buf)
+ return
+ }
+
+ if d.DeviceProfile.RGBProfile == "static" {
+ profile := d.GetRgbProfile("static")
+ if profile == nil {
+ return
+ }
+
+ profile.StartColor.Brightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider)
+ profileColor := rgb.ModifyBrightness(profile.StartColor)
+ for _, zoneColor := range d.DeviceProfile.ZoneColors {
+ zoneColorIndexRange := zoneColor.ColorIndex
+ for key, zoneColorIndex := range zoneColorIndexRange {
+ switch key {
+ case 0: // Red
+ buf[zoneColorIndex] = byte(profileColor.Red)
+ case 1: // Green
+ buf[zoneColorIndex] = byte(profileColor.Green)
+ case 2: // Blue
+ buf[zoneColorIndex] = byte(profileColor.Blue)
+ }
+ }
+ }
+ d.writeColor(buf)
+ return
+ }
+
+ go func(lightChannels int) {
+ lock := sync.Mutex{}
+ startTime := time.Now()
+ reverse := false
+ counterColorpulse := 0
+ counterFlickering := 0
+ counterColorshift := 0
+ counterCircleshift := 0
+ counterCircle := 0
+ counterColorwarp := 0
+ counterSpinner := 0
+ counterCpuTemp := 0
+ counterGpuTemp := 0
+ var temperatureKeys *rgb.Color
+ colorwarpGeneratedReverse := false
+ d.activeRgb = rgb.Exit()
+
+ // Generate random colors
+ d.activeRgb.RGBStartColor = rgb.GenerateRandomColor(1)
+ d.activeRgb.RGBEndColor = rgb.GenerateRandomColor(1)
+
+ hue := 1
+ wavePosition := 0.0
+ for {
+ select {
+ case <-d.activeRgb.Exit:
+ return
+ default:
+ buff := make([]byte, 0)
+ rgbCustomColor := true
+ profile := d.GetRgbProfile(d.DeviceProfile.RGBProfile)
+ if profile == nil {
+ for i := 0; i < d.ChangeableLedChannels*3; i++ {
+ buff = append(buff, []byte{0, 0, 0}...)
+ }
+ logger.Log(logger.Fields{"profile": d.DeviceProfile.RGBProfile, "serial": d.Serial}).Warn("No such RGB profile found")
+ continue
+ }
+ rgbModeSpeed := common.FClamp(profile.Speed, 0.1, 10)
+ // Check if we have custom colors
+ if (rgb.Color{}) == profile.StartColor || (rgb.Color{}) == profile.EndColor {
+ rgbCustomColor = false
+ }
+
+ r := rgb.New(
+ d.ChangeableLedChannels,
+ rgbModeSpeed,
+ nil,
+ nil,
+ profile.Brightness,
+ common.Clamp(profile.Smoothness, 1, 100),
+ time.Duration(rgbModeSpeed)*time.Second,
+ rgbCustomColor,
+ )
+
+ if rgbCustomColor {
+ r.RGBStartColor = &profile.StartColor
+ r.RGBEndColor = &profile.EndColor
+ } else {
+ r.RGBStartColor = d.activeRgb.RGBStartColor
+ r.RGBEndColor = d.activeRgb.RGBEndColor
+ }
+
+ // Brightness
+ r.RGBBrightness = rgb.GetBrightnessValueFloat(*d.DeviceProfile.BrightnessSlider)
+ r.RGBStartColor.Brightness = r.RGBBrightness
+ r.RGBEndColor.Brightness = r.RGBBrightness
+
+ switch d.DeviceProfile.RGBProfile {
+ case "off":
+ {
+ for n := 0; n < d.ChangeableLedChannels; n++ {
+ buff = append(buff, []byte{0, 0, 0}...)
+ }
+ }
+ case "rainbow":
+ {
+ r.Rainbow(startTime)
+ buff = append(buff, r.Output...)
+ }
+ case "watercolor":
+ {
+ r.Watercolor(startTime)
+ buff = append(buff, r.Output...)
+ }
+ case "cpu-temperature":
+ {
+ lock.Lock()
+ counterCpuTemp++
+ if counterCpuTemp >= r.Smoothness {
+ counterCpuTemp = 0
+ }
+
+ if temperatureKeys == nil {
+ temperatureKeys = r.RGBStartColor
+ }
+
+ r.MinTemp = profile.MinTemp
+ r.MaxTemp = profile.MaxTemp
+ res := r.Temperature(float64(d.CpuTemp), counterCpuTemp, temperatureKeys)
+ temperatureKeys = res
+ lock.Unlock()
+ buff = append(buff, r.Output...)
+ }
+ case "gpu-temperature":
+ {
+ lock.Lock()
+ counterGpuTemp++
+ if counterGpuTemp >= r.Smoothness {
+ counterGpuTemp = 0
+ }
+
+ if temperatureKeys == nil {
+ temperatureKeys = r.RGBStartColor
+ }
+
+ r.MinTemp = profile.MinTemp
+ r.MaxTemp = profile.MaxTemp
+ res := r.Temperature(float64(d.GpuTemp), counterGpuTemp, temperatureKeys)
+ temperatureKeys = res
+ lock.Unlock()
+ buff = append(buff, r.Output...)
+ }
+ case "colorpulse":
+ {
+ lock.Lock()
+ counterColorpulse++
+ if counterColorpulse >= r.Smoothness {
+ counterColorpulse = 0
+ }
+
+ r.Colorpulse(counterColorpulse)
+ lock.Unlock()
+ buff = append(buff, r.Output...)
+ }
+ case "static":
+ {
+ r.Static()
+ buff = append(buff, r.Output...)
+ }
+ case "rotator":
+ {
+ r.Rotator(hue)
+ buff = append(buff, r.Output...)
+ }
+ case "wave":
+ {
+ r.Wave(wavePosition)
+ buff = append(buff, r.Output...)
+ }
+ case "storm":
+ {
+ r.Storm()
+ buff = append(buff, r.Output...)
+ }
+ case "flickering":
+ {
+ lock.Lock()
+ if counterFlickering >= r.Smoothness {
+ counterFlickering = 0
+ } else {
+ counterFlickering++
+ }
+
+ r.Flickering(counterFlickering)
+ lock.Unlock()
+ buff = append(buff, r.Output...)
+ }
+ case "colorshift":
+ {
+ lock.Lock()
+ if counterColorshift >= r.Smoothness && !reverse {
+ counterColorshift = 0
+ reverse = true
+ } else if counterColorshift >= r.Smoothness && reverse {
+ counterColorshift = 0
+ reverse = false
+ }
+
+ r.Colorshift(counterColorshift, reverse)
+ counterColorshift++
+ lock.Unlock()
+ buff = append(buff, r.Output...)
+ }
+ case "circleshift":
+ {
+ lock.Lock()
+ if counterCircleshift >= lightChannels {
+ counterCircleshift = 0
+ } else {
+ counterCircleshift++
+ }
+
+ r.Circle(counterCircleshift)
+ lock.Unlock()
+ buff = append(buff, r.Output...)
+ }
+ case "circle":
+ {
+ lock.Lock()
+ if counterCircle >= lightChannels {
+ counterCircle = 0
+ } else {
+ counterCircle++
+ }
+
+ r.Circle(counterCircle)
+ lock.Unlock()
+ buff = append(buff, r.Output...)
+ }
+ case "spinner":
+ {
+ lock.Lock()
+ if counterSpinner >= lightChannels {
+ counterSpinner = 0
+ } else {
+ counterSpinner++
+ }
+ r.Spinner(counterSpinner)
+ lock.Unlock()
+ buff = append(buff, r.Output...)
+ }
+ case "colorwarp":
+ {
+ lock.Lock()
+ if counterColorwarp >= r.Smoothness {
+ if !colorwarpGeneratedReverse {
+ colorwarpGeneratedReverse = true
+ d.activeRgb.RGBStartColor = d.activeRgb.RGBEndColor
+ d.activeRgb.RGBEndColor = rgb.GenerateRandomColor(r.RGBBrightness)
+ }
+ counterColorwarp = 0
+ } else if counterColorwarp == 0 && colorwarpGeneratedReverse == true {
+ colorwarpGeneratedReverse = false
+ } else {
+ counterColorwarp++
+ }
+
+ r.Colorwarp(counterColorwarp, d.activeRgb.RGBStartColor, d.activeRgb.RGBEndColor)
+ lock.Unlock()
+ buff = append(buff, r.Output...)
+ }
+ }
+ m := 0
+ for _, zoneColor := range d.DeviceProfile.ZoneColors {
+ zoneColorIndexRange := zoneColor.ColorIndex
+ for _, zoneColorIndex := range zoneColorIndexRange {
+ buf[zoneColorIndex] = buff[m]
+ m++
+ }
+ }
+
+ d.writeColor(buf)
+ time.Sleep(40 * time.Millisecond)
+ hue++
+ wavePosition += 0.2
+ }
+ }
+ }(d.ChangeableLedChannels)
+}
+
+func (d *Device) ModifyDpi() {
+ if d.DeviceProfile.Profile >= 4 {
+ d.DeviceProfile.Profile = 0
+ } else {
+ d.DeviceProfile.Profile++
+ }
+ d.saveDeviceProfile()
+ d.toggleDPI()
+}
+
+// toggleDPI will change DPI mode
+func (d *Device) toggleDPI() {
+ if d.DeviceProfile != nil {
+ profile := d.DeviceProfile.Profiles[d.DeviceProfile.Profile]
+ value := profile.Value
+
+ // Send DPI packet
+ if value < uint16(minDpiValue) {
+ value = uint16(minDpiValue)
+ }
+ if value > uint16(maxDpiValue) {
+ value = uint16(maxDpiValue)
+ }
+
+ buf := make([]byte, 2)
+ binary.LittleEndian.PutUint16(buf[0:2], value)
+ for i := 0; i <= 1; i++ {
+ _, err := d.transfer(cmdSetDpi[i], buf)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to set dpi")
+ }
+ }
+
+ if d.activeRgb != nil {
+ d.activeRgb.Exit <- true // Exit current RGB mode
+ d.activeRgb = nil
+ }
+ d.setDeviceColor() // Restart RGB
+ }
+}
+
+// keepAlive will keep a device alive
+func (d *Device) keepAlive() {
+ _, err := d.transferDevice([]byte{0x12}, nil)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device")
+ }
+}
+
+// setAutoRefresh will refresh device data
+func (d *Device) setKeepAlive() {
+ timerKeepAlive = time.NewTicker(time.Duration(deviceKeepAlive) * time.Millisecond)
+ keepAliveChan = make(chan bool)
+ go func() {
+ for {
+ select {
+ case <-timerKeepAlive.C:
+ d.keepAlive()
+ case <-keepAliveChan:
+ timerKeepAlive.Stop()
+ return
+ }
+ }
+ }()
+}
+
+// writeColor will write data to the device with a specific endpoint.
+func (d *Device) writeColor(data []byte) {
+ buffer := make([]byte, len(data)+headerWriteSize)
+ binary.LittleEndian.PutUint16(buffer[0:2], uint16(len(data)))
+ copy(buffer[headerWriteSize:], data)
+
+ _, err := d.transfer(cmdWriteColor, buffer)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to color endpoint")
+ }
+}
+
+// transfer will send data to a device and retrieve device output
+func (d *Device) transfer(endpoint, buffer []byte) ([]byte, error) {
+ // Packet control, mandatory for this device
+ mutex.Lock()
+ defer mutex.Unlock()
+
+ // Create write buffer
+ bufferW := make([]byte, bufferSizeWrite)
+ bufferW[1] = 0x08
+ endpointHeaderPosition := bufferW[headerSize : headerSize+len(endpoint)]
+ copy(endpointHeaderPosition, endpoint)
+ if len(buffer) > 0 {
+ copy(bufferW[headerSize+len(endpoint):headerSize+len(endpoint)+len(buffer)], buffer)
+ }
+
+ // Create read buffer
+ bufferR := make([]byte, bufferSize)
+
+ // Send command to a device
+ if _, err := d.dev.Write(bufferW); err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device")
+ return nil, err
+ }
+
+ // Get data from a device
+ if _, err := d.dev.Read(bufferR); err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to read data from device")
+ return nil, err
+ }
+ return bufferR, nil
+}
+
+// transfer will send data to a device and retrieve device output
+func (d *Device) transferDevice(endpoint, buffer []byte) ([]byte, error) {
+ // Packet control, mandatory for this device
+ mutex.Lock()
+ defer mutex.Unlock()
+
+ // Create write buffer
+ bufferW := make([]byte, bufferSizeWrite)
+ bufferW[1] = 0x08
+ endpointHeaderPosition := bufferW[headerSize : headerSize+len(endpoint)]
+ copy(endpointHeaderPosition, endpoint)
+ if len(buffer) > 0 {
+ copy(bufferW[headerSize+len(endpoint):headerSize+len(endpoint)+len(buffer)], buffer)
+ }
+
+ // Create read buffer
+ bufferR := make([]byte, bufferSize)
+
+ // Send command to a device
+ if _, err := d.dev.Write(bufferW); err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to write to a device")
+ return nil, err
+ }
+
+ // Get data from a device
+ if _, err := d.dev.ReadWithTimeout(bufferR, 50*time.Millisecond); err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Unable to read data from device")
+ return nil, err
+ }
+ return bufferR, nil
+}
+
+// controlListener will listen for events from the control buttons
+func (d *Device) controlListener() {
+ go func() {
+ enum := hid.EnumFunc(func(info *hid.DeviceInfo) error {
+ if info.InterfaceNbr == 2 {
+ listener, err := hid.OpenPath(info.Path)
+ if err != nil {
+ return err
+ }
+ d.listener = listener
+ }
+ return nil
+ })
+
+ err := hid.Enumerate(d.VendorId, d.ProductId, enum)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "vendorId": d.VendorId}).Error("Unable to enumerate devices")
+ }
+
+ // Listen loop
+ data := make([]byte, bufferSize)
+ for {
+ if d.listener == nil {
+ break
+ }
+
+ _, err = d.listener.Read(data)
+ if err != nil {
+ logger.Log(logger.Fields{"error": err, "serial": d.Serial}).Error("Error reading data")
+ break
+ }
+
+ if data[1] == 0x02 && data[2] == 0x08 {
+ d.ModifyDpi()
+ }
+ time.Sleep(40 * time.Millisecond)
+ }
+ }()
+}
diff --git a/src/server/requests/requests.go b/src/server/requests/requests.go
index 0598177..3d80709 100755
--- a/src/server/requests/requests.go
+++ b/src/server/requests/requests.go
@@ -1400,3 +1400,31 @@ func ProcessMouseZoneColorsSave(r *http.Request) *Payload {
}
return &Payload{Message: "Unable to save mouse zone colors", Code: http.StatusOK, Status: 0}
}
+
+// ProcessMouseDpiColorsSave will process a POST request from a client for mouse dpi colors save
+func ProcessMouseDpiColorsSave(r *http.Request) *Payload {
+ req := &Payload{}
+ err := json.NewDecoder(r.Body).Decode(&req)
+ if err != nil {
+ logger.Log(map[string]interface{}{"error": err}).Error("Unable to decode JSON")
+ return &Payload{
+ Message: "Unable to validate your request. Please try again!",
+ Code: http.StatusOK,
+ Status: 0,
+ }
+ }
+
+ if devices.GetDevice(req.DeviceId) == nil {
+ return &Payload{Message: "Non-existing device", Code: http.StatusOK, Status: 0}
+ }
+
+ // Run it
+ status := devices.SaveMouseDpiColors(req.DeviceId, req.ColorDpi, req.ColorZones)
+ switch status {
+ case 0:
+ return &Payload{Message: "Unable to save mouse DPI colors", Code: http.StatusOK, Status: 0}
+ case 1:
+ return &Payload{Message: "Mouse DPI colors are successfully updated", Code: http.StatusOK, Status: 1}
+ }
+ return &Payload{Message: "Unable to save mouse DPI colors", Code: http.StatusOK, Status: 0}
+}
diff --git a/src/server/server.go b/src/server/server.go
index 6cf4585..7805e62 100644
--- a/src/server/server.go
+++ b/src/server/server.go
@@ -571,6 +571,17 @@ func saveMouseZoneColors(w http.ResponseWriter, r *http.Request) {
resp.Send(w)
}
+// saveMouseDpiColors handles mouse DPI colors save
+func saveMouseDpiColors(w http.ResponseWriter, r *http.Request) {
+ request := requests.ProcessMouseDpiColorsSave(r)
+ resp := &Response{
+ Code: request.Code,
+ Status: request.Status,
+ Message: request.Message,
+ }
+ resp.Send(w)
+}
+
// uiDeviceOverview handles device overview
func uiDeviceOverview(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
@@ -854,6 +865,8 @@ func setRoutes() *mux.Router {
HandlerFunc(saveMouseDpi)
r.Methods(http.MethodPost).Path("/api/mouse/zoneColors").
HandlerFunc(saveMouseZoneColors)
+ r.Methods(http.MethodPost).Path("/api/mouse/dpiColors").
+ HandlerFunc(saveMouseDpiColors)
r.Methods(http.MethodPost).Path("/api/mouse/sleep").
HandlerFunc(changeSleepMode)
diff --git a/src/templates/templates.go b/src/templates/templates.go
index ffcb2a2..3ee164e 100644
--- a/src/templates/templates.go
+++ b/src/templates/templates.go
@@ -70,6 +70,7 @@ func Init() {
"web/slipstream.html",
"web/nightsabreW.html",
"web/nightsabreWU.html",
+ "web/scimitar.html",
"web/rgb.html",
"web/temperature.html",
"web/scheduler.html",
diff --git a/static/js/mouse.js b/static/js/mouse.js
index 28c9c40..dedbfb3 100644
--- a/static/js/mouse.js
+++ b/static/js/mouse.js
@@ -168,12 +168,14 @@ document.addEventListener("DOMContentLoaded", function () {
}
const dpiColor = $("#dpiColor").val();
- const dpiColorRgb = hexToRgb(dpiColor);
-
const pf = {};
pf["deviceId"] = deviceId;
- pf["colorDpi"] = {red:dpiColorRgb.r, green:dpiColorRgb.g, blue:dpiColorRgb.b}
pf["colorZones"] = colors
+ if (dpiColor != null) {
+ const dpiColorRgb = hexToRgb(dpiColor);
+ pf["colorDpi"] = {red:dpiColorRgb.r, green:dpiColorRgb.g, blue:dpiColorRgb.b}
+ }
+
const json = JSON.stringify(pf, null, 2);
$.ajax({
url: '/api/mouse/zoneColors',
@@ -194,6 +196,40 @@ document.addEventListener("DOMContentLoaded", function () {
});
});
+ $('#saveDpiColors').on('click', function () {
+ const deviceId = $("#deviceId").val();
+ const dpis = parseInt($("#dpis").val());
+
+ let colors = {};
+ for (let i = 0; i < dpis; i++) {
+ const dpiColor = $("#dpiColor"+i).val();
+ const dpiColorRgb = hexToRgb(dpiColor);
+ colors[i] = {red: dpiColorRgb.r, green: dpiColorRgb.g, blue: dpiColorRgb.b}
+ }
+
+ const pf = {};
+ pf["deviceId"] = deviceId;
+ pf["colorZones"] = colors
+ const json = JSON.stringify(pf, null, 2);
+ $.ajax({
+ url: '/api/mouse/dpiColors',
+ type: 'POST',
+ data: json,
+ cache: false,
+ success: function(response) {
+ try {
+ if (response.status === 1) {
+ location.reload()
+ } else {
+ toast.warning(response.message);
+ }
+ } catch (err) {
+ toast.warning(response.message);
+ }
+ }
+ });
+ });
+
$('.mouseSleepModes').on('change', function () {
const deviceId = $("#deviceId").val();
const pf = {};
diff --git a/web/scimitar.html b/web/scimitar.html
new file mode 100644
index 0000000..97608f1
--- /dev/null
+++ b/web/scimitar.html
@@ -0,0 +1,185 @@
+
+
+{{ template "header" . }}
+
+
+
+ {{ $devs := .Devices }}
+ {{ $temperatures := .Temperatures }}
+ {{ $device := .Device }}
+ {{ $rgb := .Rgb }}
+ {{ $profile := $device.DeviceProfile.Profile }}
+ {{ $deviceProfile := .Device.DeviceProfile }}
+
+
+
+
+
+ {{ template "navigation" . }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ range $key, $pf := $device.DeviceProfile.Profiles }}
+
+
+
+ {{ if eq $key $device.DeviceProfile.Profile }}
+ {{ $pf.Name }} *
+ {{ else }}
+ {{ $pf.Name }}
+ {{ end }}
+
+
+
+
+
+
+
+
+
+ {{ end }}
+
+ {{ if eq "mouse" $device.DeviceProfile.RGBProfile }}
+
+ {{ range $key, $dpiZone := $device.DeviceProfile.Profiles }}
+
+
+
+
DPI: {{ $dpiZone.Name }}
+
+
+
+
+
+
+ {{ end }}
+
+
+
+ {{ range $key, $zone := $device.DeviceProfile.ZoneColors }}
+
+
+
+
{{ $zone.Name }}
+
+
+
+
+
+
+ {{ end }}
+
+ {{ end }}
+
+
+
+
+
+
+ {{ if eq "mouse" $device.DeviceProfile.RGBProfile }}
+
+
+ {{ end }}
+
+
+
+
+
+
+
+
+ {{ template "footer" . }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file