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" . }} +
+
+ +
+
+
+
+
+
+
+ Device +
+
+ {{ .Device.Product }}
+

+ Firmware: {{ .Device.Firmware }} +

+
+
+
+ + + + + + + + + + + + + + +

User Profile

Brightness

RGB Profile

Save Profile

+ + + + + + + +
+
+
+
+
+ {{ 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