holoplot / go-evdev

Go package for the Linux evdev interface
MIT License
37 stars 10 forks source link

Feature Request - Force Feedback support #11

Open Speshl opened 9 months ago

Speshl commented 9 months ago

Any chances of getting force feedback support added to this repo? Specifically upload effect which is EVIOCSFF (0x80).

I am attempting to add it myself, but I am unfamiliar with IOCTL, so it isn't going great. Getting a bad address error when attempting to make the call.

func ioctlEVIOCSFF(fd uintptr, effect Effect) error {
    code := ioctlMakeCode(ioctlDirWrite, 'E', 0x80, unsafe.Sizeof(effect))
    return doIoctl(fd, code, unsafe.Pointer(&effect))
}
zonque commented 9 months ago

I don't think I have a force feedback device at my disposal to try this. Your code goes in the right direction tho, and what needs to be done is very well in line with existing code. IOW - I think you're close :)

Could you share your definition of the Effect struct?

Speshl commented 9 months ago

Sure here is the effect type I have as it exists now. Part of the issue is that in C the FF effect type is defined as a Union and I have not come accross an easy way to mimic that in golang. Attempted to pad out the size of the constant struct to be the proper byte length but that doesn't seem to work.

// Used to build up force feedback effects
type Envelope struct { // 8 bytes
    AttackLength uint16
    AttackLevel  uint16
    FadeLength   uint16
    FadeLevel    uint16
}

type Constant struct { //10 bytes, padded to 24
    Level    int16
    Envelope Envelope

    unused [14]byte
}

type Rumble struct {
    Strong uint16
    Weak   uint16
}

type Periodic struct {
    Waveform     uint16
    Period       uint16
    Magnitude    int16
    Offset       int16
    Phase        uint16
    Envelope     Envelope
    CustomLength uint32
    CustomDate   *int16
}

type Condition struct {
    RightSaturation uint16
    LeftSaturation  uint16
    RightCoeff      int16
    LeftCoeff       int16
    Deadband        uint16
    Center          uint16
}

type Ramp struct {
    Start    int16
    End      int16
    Envelope Envelope
}

type Replay struct { //4 bytes
    Length uint16
    Delay  uint16
}

type Trigger struct { //4 bytes
    Button   uint16
    Interval uint16
}

type EffectType struct {
    Constant Constant //10 bytes, padded to 24
    // Ramp      Ramp //8 bytes
    // Periodic  Periodic //20 bytes
    // Condition [2]Condition //one for each axis 12 (24)
    // Rumble    Rumble //4 bytes
}

type Effect struct { //38 bytes
    Type       uint16
    Id         int16
    Direction  uint16
    Trigger    Trigger
    Replay     Replay
    EffectType EffectType
}
Speshl commented 9 months ago

I ended up just forking your repo and doing it with CGO to see if I could get it to work there and it does. Here is my cgo so you can see how to build up the effect to a working constant effect.

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>  // Include for the write() function
#include <linux/input.h>

void Hello(){
    printf("Hello world\n");
}

int upload_effect(uintptr_t fd,  int16_t level, bool effectExists){
    struct ff_effect effect = {};

    effect.type = FF_CONSTANT;
    if(!effectExists){
        effect.id = -1;           // Unique ID for the effect (use -1 for auto-assignment)
    }else{
        effect.id = 0;
    }

    effect.direction = 20000;     // Direction of the effect (0 for omni-directional)
    effect.trigger.button = 0; // Button that triggers the effect (0 for no button)
    effect.trigger.interval = 0; // Interval between triggers (0 for continuous)
    effect.replay.length = 0;  // Duration of the effect in milliseconds
    effect.replay.delay = 0;     // Delay before replaying the effect (0 for no delay)

    // Parameters specific to the constant effect
    effect.u.constant.level = level; // Example: Constant force level (signed 16-bit)
    int error = ioctl(fd, EVIOCSFF, &effect);
    if(error != 0){
        return -2;
    }

    struct input_event event;
    struct timeval tval;
    //memset(&event, 0, sizeof(event));
    gettimeofday(&tval, 0);
    event.input_event_usec = tval.tv_usec;
    event.input_event_sec = tval.tv_sec;
    event.type = EV_FF;
    event.code = effect.id;
    event.value = 1;

    if (write(fd, &event, sizeof(event)) != sizeof(event)) {
        return -3;
    }
    return effect.id;
}