英文:
Read media keys from Go program
问题
我正在为自己的网络编写一个跨平台的分布式媒体播放器。
当前版本包括三/四个部分:
- 一个存储音频文件的NAS。
- 一个存储文件信息的元数据服务器。
- 一个HTML/JS客户端,允许对元数据服务器进行操作,并将媒体加入播放队列。
- 一个播放器守护进程。
我的问题出现在第四部分。播放器没有用户界面,也不需要一个。它将通过来自客户端的网络命令进行控制,并通过监听当前主机上的媒体键来操作。
播放器守护进程需要在Windows和Linux上工作,但我似乎找不到一种方法(任何方法)来在这两个操作系统上读取这些键。我所了解的大部分读取键盘的方法都无法读取这些键。
英文:
I am writing a media cross-platform distributed media player for use on my own network.
The current version has three/four parts:
- A NAS holding the audio files.
- A metadata server holding information about the files.
- A HTML/JS client that allows manipulation of the metadata server and queuing media for the:
- 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
得分: 1
通过几位评论者的帮助,我现在已经完全弄清楚了。
Linux版本如下:
package main
import (
"bytes"
"encoding/binary"
"fmt"
"os"
"os/exec"
"syscall"
)
// 通过解析 /proc/bus/input/devices 文件查找键盘设备。
// 从 `github.com/gearmover/keylogger` 复制并进行了微小修改。
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
}
// 使用 MS 的名称,只是因为我不想查找 Linux 版本。
var keys = map[uint16]string{
0xa3: "VK_MEDIA_NEXT_TRACK",
0xa5: "VK_MEDIA_PREV_TRACK",
0xa6: "VK_MEDIA_STOP",
0xa4: "VK_MEDIA_PLAY_PAUSE",
}
// 大部分代码来自 `github.com/gearmover/keylogger`。
func main() {
// 在执行其他程序时降低权限
syscall.Setgid(65534)
syscall.Setuid(65534)
// 从 /proc/bus/input/devices 中获取键盘设备
devices, err := dumpDevices()
if err != nil {
fmt.Println(err)
}
if len(devices) == 0 {
fmt.Println("未找到输入设备")
return
}
// 恢复 root 权限
syscall.Setgid(0)
syscall.Setuid(0)
// 打开第一个键盘设备。
input, err := os.OpenFile(devices[0], os.O_RDONLY, 0600)
if err != nil {
fmt.Println(err)
return
}
defer input.Close()
// 记录媒体键
var buffer = make([]byte, 24)
for {
// 读取输入事件
n, err := input.Read(buffer)
if err != nil {
return
}
if n != 24 {
fmt.Println("奇怪的输入事件大小:", n)
continue
}
// 根据 <linux/input.h> 头文件的结构解析输入事件
binary.LittleEndian.Uint64(buffer[0:8]) // 时间戳,我不关心
binary.LittleEndian.Uint64(buffer[8:16])
etype := binary.LittleEndian.Uint16(buffer[16:18]) // 事件类型,键盘事件始终为 1
code := binary.LittleEndian.Uint16(buffer[18:20]) // 按键扫描码
value := int32(binary.LittleEndian.Uint32(buffer[20:24])) // 按下(1),释放(0),或重复(2)
if etype == 1 && value == 1 && keys[code] != "" {
// 在真实应用中,我会在这里发送一条消息。
fmt.Println(keys[code])
}
}
}
Windows版本如下:
package main
import (
"fmt"
"syscall"
"time"
)
var user32 = syscall.NewLazyDLL("user32.dll")
var procGAKS = user32.NewProc("GetAsyncKeyState")
// 来自 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("运行中...")
// 为了避免对每个按键触发多次,需要跟踪按键状态。
// 可以检查 GAKS 返回值的位,但这不可靠。
down := [4]bool{false, false, false, false}
for {
time.Sleep(1 * time.Millisecond)
for i, key := range keys {
// val 不是简单的布尔值!
// 0 表示“未按下”(还有一些错误)
// 如果最低位设置了,表示刚刚按下(这可能不可靠)
// 如果最高位设置了,表示按键当前按下。
val, _, _ := procGAKS.Call(uintptr(key))
// 将按下转换为状态转换并跟踪按键状态。
goingdown := false
if int(val) != 0 && !down[i] {
goingdown = true
down[i] = true
}
if int(val) == 0 && down[i] {
down[i] = false
}
if goingdown {
// 在真实应用中,我会在这里发送一条消息。
fmt.Println(names[i])
}
}
}
}
唯一的“问题”是 Linux 版本必须以 root 权限运行。对我来说这不是问题。如果以 root 权限运行是个问题,我认为有一种涉及 X11 的方法...
英文:
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(‘\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])
}
}
}
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...
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论