我正在编写媒体跨平台的分布式媒体播放器,以便在我自己的networking上使用。
目前的版本有三个/四个部分:
我的问题在于第4部分。玩家没有UI,也不需要UI。 它将通过来自客户端的networking命令和监听当前主机上的媒体密钥来控制。
玩家守护进程需要在Windows和Linux上工作,但我似乎无法找出任何方式(任何方式)在任何一个操作系统上读取这些密钥。 大部分我知道阅读键盘的方法都不会读取这些键。
在几位评论员的帮助下,我现在已经明白了。
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 …