// acer-rgbd.cpp // Daemon que salva 3 estados (keyboard/lid/button) e reaplica no boot. // Protocolo (uma linha por conexão): // SET dev=keyboard hidraw=/dev/hidraw2 effect=static bright=100 r=255 g=255 b=0 zone=all dir=none // SET dev=lid hidraw=/dev/hidraw2 effect=breathing bright=60 speed=5 // SET dev=button hidraw=/dev/hidraw2 effect=neon bright=80 speed=7 // GET // // Estado persistido (3 linhas) em: /var/lib/acer-rgbd/state.txt // Socket: /run/acer-rgbd.sock #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define RGB_FEATURE_ID 0xa4 #define KEYBOARD_RGB_ID 0x21 #define LID_RGB_ID 0x83 #define BUTTON_RGB_ID 0x65 #define RGB_EFFECT_STATIC 0x02 #define RGB_EFFECT_BREATHING 0x04 #define RGB_EFFECT_NEON 0x05 #define RGB_EFFECT_WAVE 0x07 #define RGB_EFFECT_RIPPLE 0x08 #define RGB_EFFECT_ZOOM 0x09 #define RGB_EFFECT_SNAKE 0x0a #define RGB_EFFECT_DISCO 0x0b #define RGB_EFFECT_SHIFTING 0xff static constexpr const char* SOCK_PATH = "/run/acer-rgbd.sock"; static constexpr const char* STATE_DIR = "/var/lib/acer-rgbd"; static constexpr const char* STATE_PATH = "/var/lib/acer-rgbd/state.txt"; struct Settings { std::string hidraw = "/dev/hidraw2"; uint8_t device = KEYBOARD_RGB_ID; uint8_t effect = RGB_EFFECT_STATIC; uint8_t brightness = 100; uint8_t speed = 0; uint8_t direction = 0; uint8_t r = 255, g = 255, b = 255; uint8_t zone = 0x0f; // all (keyboard) }; static bool write_file_atomic(const std::string& path, const std::string& content) { std::string tmp = path + ".tmp"; int fd = ::open(tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) return false; ssize_t w = ::write(fd, content.data(), content.size()); ::close(fd); if (w < 0 || (size_t)w != content.size()) return false; return ::rename(tmp.c_str(), path.c_str()) == 0; } static std::optional read_file(const std::string& path) { int fd = ::open(path.c_str(), O_RDONLY); if (fd < 0) return std::nullopt; std::string out; char buf[4096]; for (;;) { ssize_t r = ::read(fd, buf, sizeof(buf)); if (r == 0) break; if (r < 0) { ::close(fd); return std::nullopt; } out.append(buf, buf + r); } ::close(fd); return out; } static std::vector split_ws(std::string_view s) { std::vector v; size_t i = 0; while (i < s.size()) { while (i < s.size() && std::isspace((unsigned char)s[i])) i++; if (i >= s.size()) break; size_t j = i; while (j < s.size() && !std::isspace((unsigned char)s[j])) j++; v.emplace_back(s.substr(i, j - i)); i = j; } return v; } static std::unordered_map parse_kv(const std::vector& toks, size_t start) { std::unordered_map m; for (size_t i = start; i < toks.size(); i++) { auto& t = toks[i]; auto pos = t.find('='); if (pos == std::string::npos) continue; m.emplace(t.substr(0, pos), t.substr(pos + 1)); } return m; } static bool parse_u8(const std::string& s, int minv, int maxv, uint8_t& out) { char* end = nullptr; long v = std::strtol(s.c_str(), &end, 10); if (!end || *end != '\0') return false; if (v < minv || v > maxv) return false; out = (uint8_t)v; return true; } // dev string -> device id static std::optional parse_device(const std::string& s) { if (s == "keyboard") return KEYBOARD_RGB_ID; if (s == "lid") return LID_RGB_ID; if (s == "button") return BUTTON_RGB_ID; return std::nullopt; } // effect string -> effect id static std::optional parse_effect(const std::string& s) { if (s == "static") return RGB_EFFECT_STATIC; if (s == "breathing") return RGB_EFFECT_BREATHING; if (s == "neon") return RGB_EFFECT_NEON; if (s == "wave") return RGB_EFFECT_WAVE; if (s == "ripple") return RGB_EFFECT_RIPPLE; if (s == "zoom") return RGB_EFFECT_ZOOM; if (s == "snake") return RGB_EFFECT_SNAKE; if (s == "disco") return RGB_EFFECT_DISCO; if (s == "shifting") return RGB_EFFECT_SHIFTING; return std::nullopt; } // dir string -> 0/1/2 static std::optional parse_dir(const std::string& s) { if (s == "none") return 0; if (s == "right") return 1; if (s == "left") return 2; return std::nullopt; } // zone string -> bitmask static std::optional parse_zone(const std::string& s) { if (s == "all") return 0x0f; if (s == "1") return 0x01; if (s == "2") return 0x02; if (s == "3") return 0x04; if (s == "4") return 0x08; return std::nullopt; } static bool hid_write_feature(const Settings& cfg, std::string& err) { int fd = ::open(cfg.hidraw.c_str(), O_RDWR | O_NONBLOCK); if (fd < 0) { err = "open hidraw failed"; return false; } std::vector bytes = { RGB_FEATURE_ID, cfg.device, cfg.effect, cfg.brightness, cfg.speed, cfg.direction, cfg.r, cfg.g, cfg.b, cfg.zone, 0x00 }; int retval = ::ioctl(fd, HIDIOCSFEATURE(bytes.size()), bytes.data()); ::close(fd); if (retval < 0) { err = "ioctl HIDIOCSFEATURE failed"; return false; } return true; } static bool apply_from_kv(Settings& s, const std::unordered_map& kv, std::string& err) { if (auto it = kv.find("hidraw"); it != kv.end()) s.hidraw = it->second; // dev já é resolvido fora para escolher o slot correto, mas se vier aqui também ok: if (auto it = kv.find("dev"); it != kv.end()) { auto d = parse_device(it->second); if (!d) { err="invalid dev"; return false; } s.device = *d; } if (auto it = kv.find("effect"); it != kv.end()) { auto e = parse_effect(it->second); if (!e) { err="invalid effect"; return false; } s.effect = *e; } if (auto it = kv.find("bright"); it != kv.end()) { if (!parse_u8(it->second, 0, 100, s.brightness)) { err="bright 0-100"; return false; } } if (auto it = kv.find("speed"); it != kv.end()) { if (!parse_u8(it->second, 0, 9, s.speed)) { err="speed 0-9"; return false; } } if (auto it = kv.find("dir"); it != kv.end()) { auto d = parse_dir(it->second); if (!d) { err="dir none|right|left"; return false; } s.direction = *d; } if (auto it = kv.find("r"); it != kv.end()) { if (!parse_u8(it->second, 0, 255, s.r)) { err="r 0-255"; return false; } } if (auto it = kv.find("g"); it != kv.end()) { if (!parse_u8(it->second, 0, 255, s.g)) { err="g 0-255"; return false; } } if (auto it = kv.find("b"); it != kv.end()) { if (!parse_u8(it->second, 0, 255, s.b)) { err="b 0-255"; return false; } } if (auto it = kv.find("zone"); it != kv.end()) { auto z = parse_zone(it->second); if (!z) { err="zone all|1|2|3|4"; return false; } s.zone = *z; } // Regras coerentes com o programa original: if (s.effect == RGB_EFFECT_STATIC) { s.speed = 0; } if (s.device != KEYBOARD_RGB_ID) { s.direction = 0; s.zone = 0x00; } else { if (s.effect == RGB_EFFECT_STATIC || s.effect == RGB_EFFECT_BREATHING) { s.direction = 0; } if (s.zone == 0x00) s.zone = 0x0f; // se alguém mandar zone=0 por engano } return true; } static bool ensure_dirs() { ::mkdir(STATE_DIR, 0755); return true; } static std::string device_to_str(uint8_t dev) { if (dev == KEYBOARD_RGB_ID) return "keyboard"; if (dev == LID_RGB_ID) return "lid"; return "button"; } static std::string effect_to_str(uint8_t eff) { switch (eff) { case RGB_EFFECT_STATIC: return "static"; case RGB_EFFECT_BREATHING: return "breathing"; case RGB_EFFECT_NEON: return "neon"; case RGB_EFFECT_WAVE: return "wave"; case RGB_EFFECT_RIPPLE: return "ripple"; case RGB_EFFECT_ZOOM: return "zoom"; case RGB_EFFECT_SNAKE: return "snake"; case RGB_EFFECT_DISCO: return "disco"; case RGB_EFFECT_SHIFTING: return "shifting"; default: return "static"; } } static std::string dir_to_str(uint8_t d) { if (d == 1) return "right"; if (d == 2) return "left"; return "none"; } static std::string zone_to_str(uint8_t z) { if (z == 0x01) return "1"; if (z == 0x02) return "2"; if (z == 0x04) return "3"; if (z == 0x08) return "4"; return "all"; } static std::string serialize_one(const Settings& s) { return std::format( "SET dev={} hidraw={} effect={} bright={} speed={} dir={} r={} g={} b={} zone={}\n", device_to_str(s.device), s.hidraw, effect_to_str(s.effect), (int)s.brightness, (int)s.speed, dir_to_str(s.direction), (int)s.r, (int)s.g, (int)s.b, zone_to_str(s.zone) ); } static std::string serialize_all(const std::array& st) { return serialize_one(st[0]) + serialize_one(st[1]) + serialize_one(st[2]); } static int dev_index(uint8_t dev) { if (dev == KEYBOARD_RGB_ID) return 0; if (dev == LID_RGB_ID) return 1; return 2; } static bool load_and_apply_all(std::array& states) { auto content = read_file(STATE_PATH); if (!content) return false; bool any = false; size_t pos = 0; while (pos < content->size()) { size_t end = content->find('\n', pos); if (end == std::string::npos) end = content->size(); std::string line = content->substr(pos, end - pos); pos = end + 1; auto toks = split_ws(line); if (toks.size() < 2 || toks[0] != "SET") continue; auto kv = parse_kv(toks, 1); auto it = kv.find("dev"); if (it == kv.end()) continue; auto devOpt = parse_device(it->second); if (!devOpt) continue; int idx = dev_index(*devOpt); Settings newState = states[idx]; std::string perr; if (!apply_from_kv(newState, kv, perr)) continue; newState.device = *devOpt; std::string hwerr; if (hid_write_feature(newState, hwerr)) { states[idx] = newState; any = true; } else { // couldn't apply to hardware now (device may not exist yet), keep desired state std::println("[WARN] failed to apply to hw for dev {}: {}", device_to_str(newState.device), hwerr); states[idx] = newState; } } return any; } static int make_server_socket(std::string& err) { int fd = ::socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { err="socket failed"; return -1; } ::unlink(SOCK_PATH); sockaddr_un addr{}; addr.sun_family = AF_UNIX; std::snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", SOCK_PATH); if (::bind(fd, (sockaddr*)&addr, sizeof(addr)) < 0) { err="bind failed"; ::close(fd); return -1; } ::chmod(SOCK_PATH, 0666); if (::listen(fd, 16) < 0) { err="listen failed"; ::close(fd); return -1; } return fd; } static std::string read_line(int fd) { std::string s; char c; while (true) { ssize_t r = ::read(fd, &c, 1); if (r <= 0) break; if (c == '\n') break; s.push_back(c); if (s.size() > 8192) break; } return s; } static void write_all(int fd, std::string_view s) { ::write(fd, s.data(), s.size()); } int main() { if (geteuid() != 0) { std::println("[ERR] Run as root (or use udev permissions)."); return 1; } ensure_dirs(); // 3 estados: keyboard/lid/button std::array states; states[0].device = KEYBOARD_RGB_ID; states[0].zone = 0x0f; states[1].device = LID_RGB_ID; states[1].zone = 0x00; states[2].device = BUTTON_RGB_ID; states[2].zone = 0x00; // restore no boot (void)load_and_apply_all(states); std::string err; int sfd = make_server_socket(err); if (sfd < 0) { std::println("[ERR] {}", err); return 1; } for (;;) { int cfd = ::accept(sfd, nullptr, nullptr); if (cfd < 0) continue; std::string line = read_line(cfd); auto toks = split_ws(line); if (toks.empty()) { write_all(cfd, "ERR empty\n"); ::close(cfd); continue; } if (toks[0] == "GET") { auto content = read_file(STATE_PATH); if (content) write_all(cfd, *content); else write_all(cfd, "ERR no-state\n"); ::close(cfd); continue; } if (toks[0] != "SET") { write_all(cfd, "ERR unknown-cmd\n"); ::close(cfd); continue; } auto kv = parse_kv(toks, 1); auto it = kv.find("dev"); if (it == kv.end()) { write_all(cfd, "ERR missing dev\n"); ::close(cfd); continue; } auto devOpt = parse_device(it->second); if (!devOpt) { write_all(cfd, "ERR invalid dev\n"); ::close(cfd); continue; } int idx = dev_index(*devOpt); Settings newState = states[idx]; std::string perr; if (!apply_from_kv(newState, kv, perr)) { write_all(cfd, std::format("ERR {}\n", perr)); ::close(cfd); continue; } // garantir coerência newState.device = *devOpt; // defaults de zone por device (se não veio e for teclado) if (newState.device == KEYBOARD_RGB_ID && newState.zone == 0x00) newState.zone = 0x0f; if (newState.device != KEYBOARD_RGB_ID) newState.zone = 0x00; std::string hwerr; if (!hid_write_feature(newState, hwerr)) { write_all(cfd, std::format("ERR {}\n", hwerr)); ::close(cfd); continue; } states[idx] = newState; (void)write_file_atomic(STATE_PATH, serialize_all(states)); write_all(cfd, "OK\n"); ::close(cfd); } }