Basic control implementation with hud toggle
parent
d2a594e002
commit
fe5aca0ed3
@ -0,0 +1,199 @@
|
||||
#include <assert.h>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include "mesa/util/os_socket.h"
|
||||
#include "overlay.h"
|
||||
#include "vulkan.h"
|
||||
#include "control.h"
|
||||
|
||||
using namespace std;
|
||||
static void parse_command(struct instance_data *instance_data,
|
||||
const char *cmd, unsigned cmdlen,
|
||||
const char *param, unsigned paramlen)
|
||||
{
|
||||
if (!strncmp(cmd, "hud", cmdlen)) {
|
||||
params_ptr->no_display = !params_ptr->no_display;
|
||||
}
|
||||
}
|
||||
|
||||
#define BUFSIZE 4096
|
||||
|
||||
/**
|
||||
* This function will process commands through the control file.
|
||||
*
|
||||
* A command starts with a colon, followed by the command, and followed by an
|
||||
* option '=' and a parameter. It has to end with a semi-colon. A full command
|
||||
* + parameter looks like:
|
||||
*
|
||||
* :cmd=param;
|
||||
*/
|
||||
static void process_char(struct instance_data *instance_data, char c)
|
||||
{
|
||||
static char cmd[BUFSIZE];
|
||||
static char param[BUFSIZE];
|
||||
|
||||
static unsigned cmdpos = 0;
|
||||
static unsigned parampos = 0;
|
||||
static bool reading_cmd = false;
|
||||
static bool reading_param = false;
|
||||
|
||||
switch (c) {
|
||||
case ':':
|
||||
cmdpos = 0;
|
||||
parampos = 0;
|
||||
reading_cmd = true;
|
||||
reading_param = false;
|
||||
break;
|
||||
case ';':
|
||||
if (!reading_cmd)
|
||||
break;
|
||||
cmd[cmdpos++] = '\0';
|
||||
param[parampos++] = '\0';
|
||||
parse_command(instance_data, cmd, cmdpos, param, parampos);
|
||||
reading_cmd = false;
|
||||
reading_param = false;
|
||||
break;
|
||||
case '=':
|
||||
if (!reading_cmd)
|
||||
break;
|
||||
reading_param = true;
|
||||
break;
|
||||
default:
|
||||
if (!reading_cmd)
|
||||
break;
|
||||
|
||||
if (reading_param) {
|
||||
/* overflow means an invalid parameter */
|
||||
if (parampos >= BUFSIZE - 1) {
|
||||
reading_cmd = false;
|
||||
reading_param = false;
|
||||
break;
|
||||
}
|
||||
|
||||
param[parampos++] = c;
|
||||
} else {
|
||||
/* overflow means an invalid command */
|
||||
if (cmdpos >= BUFSIZE - 1) {
|
||||
reading_cmd = false;
|
||||
break;
|
||||
}
|
||||
|
||||
cmd[cmdpos++] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void control_send(struct instance_data *instance_data,
|
||||
const char *cmd, unsigned cmdlen,
|
||||
const char *param, unsigned paramlen)
|
||||
{
|
||||
unsigned msglen = 0;
|
||||
char buffer[BUFSIZE];
|
||||
|
||||
assert(cmdlen + paramlen + 3 < BUFSIZE);
|
||||
|
||||
buffer[msglen++] = ':';
|
||||
|
||||
memcpy(&buffer[msglen], cmd, cmdlen);
|
||||
msglen += cmdlen;
|
||||
|
||||
if (paramlen > 0) {
|
||||
buffer[msglen++] = '=';
|
||||
memcpy(&buffer[msglen], param, paramlen);
|
||||
msglen += paramlen;
|
||||
buffer[msglen++] = ';';
|
||||
}
|
||||
|
||||
os_socket_send(instance_data->control_client, buffer, msglen, 0);
|
||||
}
|
||||
|
||||
static void control_send_connection_string(struct device_data *device_data)
|
||||
{
|
||||
struct instance_data *instance_data = device_data->instance;
|
||||
|
||||
const char *controlVersionCmd = "MesaOverlayControlVersion";
|
||||
const char *controlVersionString = "1";
|
||||
|
||||
control_send(instance_data, controlVersionCmd, strlen(controlVersionCmd),
|
||||
controlVersionString, strlen(controlVersionString));
|
||||
|
||||
const char *deviceCmd = "DeviceName";
|
||||
const char *deviceName = device_data->properties.deviceName;
|
||||
|
||||
control_send(instance_data, deviceCmd, strlen(deviceCmd),
|
||||
deviceName, strlen(deviceName));
|
||||
|
||||
const char *mesaVersionCmd = "MesaVersion";
|
||||
const char *mesaVersionString = "Mesa";
|
||||
|
||||
control_send(instance_data, mesaVersionCmd, strlen(mesaVersionCmd),
|
||||
mesaVersionString, strlen(mesaVersionString));
|
||||
|
||||
}
|
||||
|
||||
void control_client_check(struct device_data *device_data)
|
||||
{
|
||||
struct instance_data *instance_data = device_data->instance;
|
||||
|
||||
/* Already connected, just return. */
|
||||
if (instance_data->control_client >= 0)
|
||||
return;
|
||||
|
||||
int socket = os_socket_accept(instance_data->params.control);
|
||||
if (socket == -1) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != ECONNABORTED)
|
||||
fprintf(stderr, "ERROR on socket: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
if (socket >= 0) {
|
||||
os_socket_block(socket, false);
|
||||
instance_data->control_client = socket;
|
||||
control_send_connection_string(device_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void control_client_disconnected(struct instance_data *instance_data)
|
||||
{
|
||||
os_socket_close(instance_data->control_client);
|
||||
instance_data->control_client = -1;
|
||||
}
|
||||
|
||||
void process_control_socket(struct instance_data *instance_data)
|
||||
{
|
||||
const int client = instance_data->control_client;
|
||||
if (client >= 0) {
|
||||
char buf[BUFSIZE];
|
||||
|
||||
while (true) {
|
||||
ssize_t n = os_socket_recv(client, buf, BUFSIZE, 0);
|
||||
|
||||
if (n == -1) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
/* nothing to read, try again later */
|
||||
break;
|
||||
}
|
||||
|
||||
if (errno != ECONNRESET)
|
||||
fprintf(stderr, "ERROR on connection: %s\n", strerror(errno));
|
||||
|
||||
control_client_disconnected(instance_data);
|
||||
} else if (n == 0) {
|
||||
/* recv() returns 0 when the client disconnects */
|
||||
control_client_disconnected(instance_data);
|
||||
}
|
||||
|
||||
for (ssize_t i = 0; i < n; i++) {
|
||||
process_char(instance_data, buf[i]);
|
||||
}
|
||||
|
||||
/* If we try to read BUFSIZE and receive BUFSIZE bytes from the
|
||||
* socket, there's a good chance that there's still more data to be
|
||||
* read, so we will try again. Otherwise, simply be done for this
|
||||
* iteration and try again on the next frame.
|
||||
*/
|
||||
if (n < BUFSIZE)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
extern void control_client_check(struct device_data *device_data);
|
||||
extern void process_control_socket(struct instance_data *instance_data);
|
@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import select
|
||||
from select import EPOLLIN, EPOLLPRI, EPOLLERR
|
||||
import time
|
||||
from collections import namedtuple
|
||||
import argparse
|
||||
|
||||
TIMEOUT = 1.0 # seconds
|
||||
|
||||
VERSION_HEADER = bytearray('MesaOverlayControlVersion', 'utf-8')
|
||||
DEVICE_NAME_HEADER = bytearray('DeviceName', 'utf-8')
|
||||
MESA_VERSION_HEADER = bytearray('MesaVersion', 'utf-8')
|
||||
|
||||
DEFAULT_SERVER_ADDRESS = "\0mangohud"
|
||||
|
||||
class Connection:
|
||||
def __init__(self, path):
|
||||
# Create a Unix Domain socket and connect
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
try:
|
||||
sock.connect(path)
|
||||
except socket.error as msg:
|
||||
print(msg)
|
||||
sys.exit(1)
|
||||
|
||||
self.sock = sock
|
||||
|
||||
# initialize poll interface and register socket
|
||||
epoll = select.epoll()
|
||||
epoll.register(sock, EPOLLIN | EPOLLPRI | EPOLLERR)
|
||||
self.epoll = epoll
|
||||
|
||||
def recv(self, timeout):
|
||||
'''
|
||||
timeout as float in seconds
|
||||
returns:
|
||||
- None on error or disconnection
|
||||
- bytes() (empty) on timeout
|
||||
'''
|
||||
|
||||
events = self.epoll.poll(timeout)
|
||||
for ev in events:
|
||||
(fd, event) = ev
|
||||
if fd != self.sock.fileno():
|
||||
continue
|
||||
|
||||
# check for socket error
|
||||
if event & EPOLLERR:
|
||||
return None
|
||||
|
||||
# EPOLLIN or EPOLLPRI, just read the message
|
||||
msg = self.sock.recv(4096)
|
||||
|
||||
# socket disconnected
|
||||
if len(msg) == 0:
|
||||
return None
|
||||
|
||||
return msg
|
||||
|
||||
return bytes()
|
||||
|
||||
def send(self, msg):
|
||||
self.sock.send(msg)
|
||||
|
||||
class MsgParser:
|
||||
MSGBEGIN = bytes(':', 'utf-8')[0]
|
||||
MSGEND = bytes(';', 'utf-8')[0]
|
||||
MSGSEP = bytes('=', 'utf-8')[0]
|
||||
|
||||
def __init__(self, conn):
|
||||
self.cmdpos = 0
|
||||
self.parampos = 0
|
||||
self.bufferpos = 0
|
||||
self.reading_cmd = False
|
||||
self.reading_param = False
|
||||
self.buffer = None
|
||||
self.cmd = bytearray(4096)
|
||||
self.param = bytearray(4096)
|
||||
|
||||
self.conn = conn
|
||||
|
||||
def readCmd(self, ncmds, timeout=TIMEOUT):
|
||||
'''
|
||||
returns:
|
||||
- None on error or disconnection
|
||||
- bytes() (empty) on timeout
|
||||
'''
|
||||
|
||||
parsed = []
|
||||
|
||||
remaining = timeout
|
||||
|
||||
while remaining > 0 and ncmds > 0:
|
||||
now = time.monotonic()
|
||||
|
||||
if self.buffer == None:
|
||||
self.buffer = self.conn.recv(remaining)
|
||||
self.bufferpos = 0
|
||||
|
||||
# disconnected or error
|
||||
if self.buffer == None:
|
||||
return None
|
||||
|
||||
for i in range(self.bufferpos, len(self.buffer)):
|
||||
c = self.buffer[i]
|
||||
self.bufferpos += 1
|
||||
if c == self.MSGBEGIN:
|
||||
self.cmdpos = 0
|
||||
self.parampos = 0
|
||||
self.reading_cmd = True
|
||||
self.reading_param = False
|
||||
elif c == self.MSGEND:
|
||||
if not self.reading_cmd:
|
||||
continue
|
||||
self.reading_cmd = False
|
||||
self.reading_param = False
|
||||
|
||||
cmd = self.cmd[0:self.cmdpos]
|
||||
param = self.param[0:self.parampos]
|
||||
self.reading_cmd = False
|
||||
self.reading_param = False
|
||||
|
||||
parsed.append((cmd, param))
|
||||
ncmds -= 1
|
||||
if ncmds == 0:
|
||||
break
|
||||
elif c == self.MSGSEP:
|
||||
if self.reading_cmd:
|
||||
self.reading_param = True
|
||||
else:
|
||||
if self.reading_param:
|
||||
self.param[self.parampos] = c
|
||||
self.parampos += 1
|
||||
elif self.reading_cmd:
|
||||
self.cmd[self.cmdpos] = c
|
||||
self.cmdpos += 1
|
||||
|
||||
# if we read the entire buffer and didn't finish the command,
|
||||
# throw it away
|
||||
self.buffer = None
|
||||
|
||||
# check if we have time for another iteration
|
||||
elapsed = time.monotonic() - now
|
||||
remaining = max(0, remaining - elapsed)
|
||||
|
||||
# timeout
|
||||
return parsed
|
||||
|
||||
def control(args):
|
||||
if args.socket:
|
||||
address = '\0' + args.socket
|
||||
else:
|
||||
address = DEFAULT_SERVER_ADDRESS
|
||||
|
||||
conn = Connection(address)
|
||||
msgparser = MsgParser(conn)
|
||||
|
||||
version = None
|
||||
name = None
|
||||
mesa_version = None
|
||||
|
||||
msgs = msgparser.readCmd(3)
|
||||
|
||||
if args.info:
|
||||
info = "Protocol Version: {}\n"
|
||||
info += "Device Name: {}\n"
|
||||
info += "Mesa Version: {}"
|
||||
print(info.format(version, name, mesa_version))
|
||||
|
||||
if args.cmd == 'start-capture':
|
||||
conn.send(bytearray(':capture=1;', 'utf-8'))
|
||||
elif args.cmd == 'stop-capture':
|
||||
conn.send(bytearray(':capture=0;', 'utf-8'))
|
||||
elif args.cmd == 'toggle-hud':
|
||||
conn.send(bytearray(':hud;', 'utf-8'))
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='MESA_overlay control client')
|
||||
parser.add_argument('--info', action='store_true', help='Print info from socket')
|
||||
parser.add_argument('--socket', '-s', type=str, help='Path to socket')
|
||||
|
||||
commands = parser.add_subparsers(help='commands to run', dest='cmd')
|
||||
commands.add_parser('start-capture')
|
||||
commands.add_parser('stop-capture')
|
||||
commands.add_parser('toggle-hud')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
control(args)
|
@ -0,0 +1,32 @@
|
||||
#include <string>
|
||||
#include "mesa/util/os_socket.h"
|
||||
#include "vk_enum_to_str.h"
|
||||
#include "notify.h"
|
||||
#include <vulkan/vk_layer.h>
|
||||
using namespace std;
|
||||
|
||||
struct instance_data {
|
||||
struct vk_instance_dispatch_table vtable;
|
||||
VkInstance instance;
|
||||
struct overlay_params params;
|
||||
uint32_t api_version;
|
||||
string engineName, engineVersion;
|
||||
notify_thread notifier;
|
||||
int control_client;
|
||||
};
|
||||
|
||||
struct device_data {
|
||||
struct instance_data *instance;
|
||||
|
||||
PFN_vkSetDeviceLoaderData set_device_loader_data;
|
||||
|
||||
struct vk_device_dispatch_table vtable;
|
||||
VkPhysicalDevice physical_device;
|
||||
VkDevice device;
|
||||
|
||||
VkPhysicalDeviceProperties properties;
|
||||
|
||||
struct queue_data *graphic_queue;
|
||||
|
||||
std::vector<struct queue_data *> queues;
|
||||
};
|
Loading…
Reference in New Issue