douque9982 2017-08-05 20:23
浏览 44
已采纳

从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.

  • 写回答

1条回答 默认 最新

  • doudiejian5827 2017-08-07 13:10
    关注

    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...

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 Power query添加列问题
  • ¥50 Kubernetes&Fission&Eleasticsearch
  • ¥15 有没有帮写代码做实验仿真的
  • ¥15 報錯:Person is not mapped,如何解決?
  • ¥30 vmware exsi重置后登不上
  • ¥15 易盾点选的cb参数怎么解啊
  • ¥15 MATLAB运行显示错误,如何解决?
  • ¥15 c++头文件不能识别CDialog
  • ¥15 Excel发现不可读取的内容
  • ¥15 关于#stm32#的问题:CANOpen的PDO同步传输问题