从Go程序中读取媒体键

时间:2017-08-05 20:23:59

标签: linux windows go

我正在编写一个媒体跨平台的分布式媒体播放器,供我自己的网络使用。

当前版本有三个/四个部分:

  1. 持有音频文件的NAS。
  2. 包含有关文件信息的元数据服务器。
  3. 允许操作元数据服务器和排队媒体的HTML / JS客户端:
  4. 玩家守护。
  5. 我的问题在于第4部分。播放器没有UI,也不需要UI。它将通过来自客户端的网络命令以及通过侦听当前主机上的媒体键来控制。

    播放器守护程序需要在Windows和Linux上运行,但我似乎无法找到在任一操作系统上读取这些键的方法(任何方式)。我知道阅读键盘的大多数方法根本不会读取这些键。

1 个答案:

答案 0 :(得分:0)

在几位评论者的帮助下,我现在已经弄明白了。

Linux版本如下:

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(‘\n’); err == nil; {
        devices = append(devices, “/dev/input/”+line[:len(line)-1])

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

    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])
        }
    }
}

Windows版本:

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])
            }
        }
    }
}

唯一的“问题”是Linux版本必须以root身份运行。对我来说这不是问题。如果以root身份运行是一个问题,我认为有一种方法涉及X11 ......