CadMouse & modern linux

Post questions, comments and feedback to our 3Dconnexion UNIX and Linux Development Team.

Moderator: Moderators

Post Reply
izen
Posts: 7
Joined: Wed Dec 27, 2023 3:26 am

CadMouse & modern linux

Post by izen »

Hi everyone. The 3Dconnexion CadMouse Wireless is pretty frustrating to use on modern Linux (Fedora 43 in my case). The default accelerated scrolling is bogus at best, and the sensitivity is a mess. To top it off, plugging it in via Type-C renders the mouse completely dead. Here is a quick-and-dirty background daemon to fix all of that.

Code: Select all

cadmoused.c:
// Vibecoded with Google Gemini 3.1 Pro

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/hidraw.h>
#include <errno.h>

#ifndef HIDIOCSFEATURE
#define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len)
#endif

// Max number of simultaneously open hidraw interfaces
#define MAX_ACTIVE_NODES 16

// Target device structure (VID and PID in uppercase)
typedef struct {
    const char *vid;
    const char *pid;
    const char *name;
} TargetDevice;

// YOUR TARGET DEVICES
TargetDevice TARGET_DEVICES[] = {
    {"256F", "C658", "CadMouse (Type-C)"},
    {"256F", "C652", "CadMouse (Universal Receiver)"}
};
int NUM_TARGETS = sizeof(TARGET_DEVICES) / sizeof(TARGET_DEVICES[0]);

// Initialization payload (Feature Report ID 16)
// Sniffed from win10
// no idiotic scroll accelerator, normal sense.
const unsigned char FEATURE_PAYLOAD[32] = {
    16, 0, 48, 0, 1, 0xFF, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 3, 0, 10, 11, 12, 12, 14,
    13, 47, 0, 30, 0, 0, 0, 1
};

// Active connection pool
typedef struct {
    int fd;
    char path[64];
} ActiveNode;

ActiveNode active_nodes[MAX_ACTIVE_NODES];

void init_pool() {
    for (int i = 0; i < MAX_ACTIVE_NODES; i++) {
        active_nodes[i].fd = -1;
    }
}

int main() {
    // make systemd logging happy
    setvbuf(stdout, NULL, _IOLBF, 0);

    init_pool();
    printf("CadMouse keepalive daemon started. Monitoring %d target(s)...\n", NUM_TARGETS);

    while (1) {
        // 1. CHECK DISCONNECTIONS
        for (int i = 0; i < MAX_ACTIVE_NODES; i++) {
            if (active_nodes[i].fd != -1) {
                if (access(active_nodes[i].path, F_OK) == -1) {
                    printf("Device disconnected: %s\n", active_nodes[i].path);
                    close(active_nodes[i].fd);
                    active_nodes[i].fd = -1;
                }
            }
        }

        // 2. SCAN FOR NEW CONNECTIONS
        DIR *dir;
        struct dirent *ent;
        if ((dir = opendir("/sys/class/hidraw")) != NULL) {
            while ((ent = readdir(dir)) != NULL) {
                if (strncmp(ent->d_name, "hidraw", 6) != 0) continue;

                char dev_path[64];
                snprintf(dev_path, sizeof(dev_path), "/dev/%s", ent->d_name);

                // Skip if this interface is already being monitored
                int already_active = 0;
                for (int i = 0; i < MAX_ACTIVE_NODES; i++) {
                    if (active_nodes[i].fd != -1 && strcmp(active_nodes[i].path, dev_path) == 0) {
                        already_active = 1;
                        break;
                    }
                }
                if (already_active) continue;

                // Read uevent to extract VID and PID
                char sys_path[256];
                char uevent_buf[1024];
                snprintf(sys_path, sizeof(sys_path), "/sys/class/hidraw/%s/device/uevent", ent->d_name);

                FILE *f = fopen(sys_path, "r");
                if (!f) continue;
                size_t read_bytes = fread(uevent_buf, 1, sizeof(uevent_buf) - 1, f);
                uevent_buf[read_bytes] = '\0';
                fclose(f);

                // Check against our target list
                int matched_target_idx = -1;
                for (int i = 0; i < NUM_TARGETS; i++) {
                    if (strstr(uevent_buf, TARGET_DEVICES[i].vid) != NULL &&
                        strstr(uevent_buf, TARGET_DEVICES[i].pid) != NULL) {
                        matched_target_idx = i;
                        break;
                    }
                }

                if (matched_target_idx != -1) {
                    // Find a free slot in the pool
                    for (int i = 0; i < MAX_ACTIVE_NODES; i++) {
                        if (active_nodes[i].fd == -1) {
                            active_nodes[i].fd = open(dev_path, O_RDWR);
                            if (active_nodes[i].fd >= 0) {
                                strncpy(active_nodes[i].path, dev_path, sizeof(active_nodes[i].path));
                                printf("Detected: %s (%s). Applying configuration...\n",
                                       TARGET_DEVICES[matched_target_idx].name, dev_path);

                                // Send feature payload
                                int res = ioctl(active_nodes[i].fd, HIDIOCSFEATURE(32), FEATURE_PAYLOAD);
                                if (res >= 0) {
                                    printf("\t-> SUCCESS: Configuration accepted by %s\n", dev_path);
                                }
                            }
                            break;
                        }
                    }
                }
            }
            closedir(dir);
        }

        // Sleep for 5 seconds to minimize CPU footprint
        sleep(5);
    }

    return 0;
}


$ gcc -O2 cadmoused.c -o cadmoused
$ mkdir -p ~/.local/bin
$ mv cadmoused ~/.local/bin/
$ nano ~/.config/systemd/user/cadmoused.service

cadmoused.service:
[Unit]
Description=3Dconnexion CadMouse Background Keepalive
After=graphical-session.target

[Service]
ExecStart=%h/.local/bin/cadmoused

Restart=always
RestartSec=3
Nice=10

[Install]
WantedBy=default.target

$ systemctl --user daemon-reload
$ systemctl --user enable --now cadmoused.service
$ journalctl --user -u cadmoused.service -f

Post Reply