从Go程序中读取媒体密钥

I am writing a media cross-platform distributed media player for use on my own network.

The current version has three/four parts:

  1. A NAS holding the audio files.
  2. A metadata server holding information about the files.
  3. A HTML/JS client that allows manipulation of the metadata server and queuing media for the:
  4. A player deamon.

My problem lies with part 4. The player has no UI, nor does it need one. It will be controlled via network commands from the client and by listening to the media keys on its current host.

The player daemon needs to work on both Windows and Linux, but I can't seem to figure out a way (any way) to read these keys on either OS. Most of the way I know to read the keyboard will not read these keys at all.

With the help of several commenters, I now have it all figured out.

The Linux version is as follows:

package main

import (
    “bytes”
    “encoding/binary”
    “fmt”
    “os”
    “os/exec”
    “syscall”
)

// parses through the /proc/bus/input/devices file for keyboard devices.
// Copied from `github.com/gearmover/keylogger` with trivial modification.
func dumpDevices() ([]string, error) {
    cmd := exec.Command(“/bin/sh”, “-c”, “/bin/grep -E ‘Handlers|EV=’ /proc/bus/input/devices | /bin/grep -B1 ‘EV=120013’ | /bin/grep -Eo ‘event[0-9]+’”)

    output, err := cmd.Output()
    if err != nil {
        return nil, err
    }

    buf := bytes.NewBuffer(output)

    var devices []string

    for line, err := buf.ReadString(‘
’); err == nil; {
        devices = append(devices, “/dev/input/”+line[:len(line)-1])

        line, err = buf.ReadString(‘
’)
    }

    return devices, nil
}

// Using MS names, just because I don’t feel like looking up the Linux versions.
var keys = map[uint16]string{
    0xa3: “VK_MEDIA_NEXT_TRACK”,
    0xa5: “VK_MEDIA_PREV_TRACK”,
    0xa6: “VK_MEDIA_STOP”,
    0xa4: “VK_MEDIA_PLAY_PAUSE”,
}

// Most of the code here comes from `github.com/gearmover/keylogger`.
func main() {
    // drop privileges when executing other programs
    syscall.Setgid(65534)
    syscall.Setuid(65534)

    // dump our keyboard devices from /proc/bus/input/devices
    devices, err := dumpDevices()
    if err != nil {
        fmt.Println(err)
    }
    if len(devices) == 0 {
        fmt.Println(“No input devices found”)
        return
    }

    // bring back our root privs
    syscall.Setgid(0)
    syscall.Setuid(0)

    // Open the first keyboard device.
    input, err := os.OpenFile(devices[0], os.O_RDONLY, 0600)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer input.Close()

    // Log media keys
    var buffer = make([]byte, 24)
    for {
        // read the input events as they come in
        n, err := input.Read(buffer)
        if err != nil {
            return
        }

        if n != 24 {
            fmt.Println(“Weird Input Event Size: “, n)
            continue
        }

        // parse the input event according to the <linux/input.h> header struct
        binary.LittleEndian.Uint64(buffer[0:8]) // Time stamp stuff I could care less about
        binary.LittleEndian.Uint64(buffer[8:16])
        etype := binary.LittleEndian.Uint16(buffer[16:18])        // Event Type. Always 1 for keyboard events
        code := binary.LittleEndian.Uint16(buffer[18:20])         // Key scan code
        value := int32(binary.LittleEndian.Uint32(buffer[20:24])) // press(1), release(0), or repeat(2)

        if etype == 1 && value == 1 && keys[code] != “” {
            // In a real application I would send a message here.
            fmt.Println(keys[code])
        }
    }
}

And the Windows version:

package main

import (
    “fmt”
    “syscall”
    “time”
)

var user32 = syscall.NewLazyDLL(“user32.dll”)
var procGAKS = user32.NewProc(“GetAsyncKeyState”)

// Key codes from MSDN
var keys = [4]uint{
    0xb0, // VK_MEDIA_NEXT_TRACK
    0xb1, // VK_MEDIA_PREV_TRACK
    0xb2, // VK_MEDIA_STOP
    0xb3, // VK_MEDIA_PLAY_PAUSE
}

var names = [4]string{
    “VK_MEDIA_NEXT_TRACK”,
    “VK_MEDIA_PREV_TRACK”,
    “VK_MEDIA_STOP”,
    “VK_MEDIA_PLAY_PAUSE”,
}

func main() {
    fmt.Println(“Running…”)

    // Since I don’t want to trigger dozens of times for each key I need to track state.
    // I could check the bits of GAKS’ return value, but that is not reliable.
    down := [4]bool{false, false, false, false}

    for {
        time.Sleep(1 * time.Millisecond)
        for i, key := range keys {
            // val is not a simple boolean!
            // 0 means “not pressed” (also certain errors)
            // If LSB is set the key was just pressed (this may not be reliable)
            // If MSB is set the key is currently down.
            val, _, _ := procGAKS.Call(uintptr(key))

            // Turn a press into a transition and track key state.
            goingdown := false
            if int(val) != 0 && !down[i] {
                goingdown = true
                down[i] = true
            }
            if int(val) == 0 && down[i] {
                down[i] = false
            }
            if goingdown {
                // In a real application I would send a message here.
                fmt.Println(names[i])
            }
        }
    }
}

The only "issue" is that the Linux version must be run as root. For me this is not a problem. If running as root is a problem I think there is a way that involves X11...