t/libmonado: Add libmonado implementation

Work done by lots of different authors that did various pieces of the library.

Co-authored-by: Korcan Hussein <korcan.hussein@collabora.com>
Co-authored-by: Jakob Bornecrantz <jakob@collabora.com>
Co-authored-by: Ryan Pavlik <ryan.pavlik@collabora.com>
This commit is contained in:
Lubosz Sarnecki 2023-07-13 11:12:01 +02:00 committed by Jakob Bornecrantz
parent be0df71004
commit 040ad33188
9 changed files with 1284 additions and 0 deletions

View file

@ -41,3 +41,8 @@ if(XRT_FEATURE_SERVICE
)
add_subdirectory(sdl_test)
endif()
# Monado management library
if(XRT_FEATURE_SERVICE AND XRT_HAVE_LINUX)
add_subdirectory(libmonado)
endif()

View file

@ -0,0 +1,16 @@
# Copyright 2019-2023, Collabora, Ltd.
# SPDX-License-Identifier: BSL-1.0
add_library(monado SHARED monado.c libmonado.def)
set(LIBMONADO_HEADER_DIR ${CMAKE_INSTALL_INCLUDEDIR}/monado)
target_link_libraries(monado PRIVATE aux_util ipc_client)
target_include_directories(
monado INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${LIBMONADO_HEADER_DIR}>
)
install(TARGETS monado RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES monado.h DESTINATION ${LIBMONADO_HEADER_DIR})
add_executable(libmonado-example example.c)
target_link_libraries(libmonado-example PRIVATE monado)

View file

@ -0,0 +1,200 @@
// Copyright 2020-2023, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Small cli application to demonstrate use of libmonado.
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
* @author Pete Black <pblack@collabora.com>
*/
#include "monado.h"
#include <ctype.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <stdbool.h>
#define P(...) fprintf(stdout, __VA_ARGS__)
#define PE(...) fprintf(stderr, __VA_ARGS__)
#define CHECK_ID_EXIT(ID) \
do { \
if (ID < 0 || ID > INT_MAX) { \
PE("Invalid client index %i.\n", s_val); \
exit(1); \
} \
} while (false)
typedef enum op_mode
{
MODE_GET,
MODE_SET_PRIMARY,
MODE_SET_FOCUSED,
MODE_TOGGLE_IO,
} op_mode_t;
int
get_mode(mnd_root_t *root)
{
mnd_result_t mret = mnd_root_update_client_list(root);
if (mret != MND_SUCCESS) {
PE("Failed to get client list.\n");
exit(1);
}
uint32_t num_clients = 0;
mret = mnd_root_get_number_clients(root, &num_clients);
if (mret != MND_SUCCESS) {
PE("Failed to get client count.\n");
exit(1);
}
P("Clients: (%d)\n", num_clients);
for (uint32_t i = 0; i < num_clients; i++) {
uint32_t client_id = 0;
uint32_t flags = 0;
const char *name = NULL;
mret = mnd_root_get_client_id_at_index(root, i, &client_id);
if (mret != MND_SUCCESS) {
PE("Failed to get client id for index %u", i);
continue;
}
mret = mnd_root_get_client_state(root, client_id, &flags);
if (mret != MND_SUCCESS) {
PE("Failed to get client state for client id: %u (index: %u)", client_id, i);
continue;
}
mret = mnd_root_get_client_name(root, client_id, &name);
if (mret != MND_SUCCESS) {
PE("Failed to get client name for client id: %u (index: %u)", client_id, i);
continue;
}
P("\tid: % 8d"
"\tact: %d"
"\tdisp: %d"
"\tfoc: %d"
"\tio: %d"
"\tovly: %d"
"\t%s\n",
client_id, //
(flags & MND_CLIENT_SESSION_ACTIVE) != 0 ? 1 : 0, //
(flags & MND_CLIENT_SESSION_VISIBLE) != 0 ? 1 : 0, //
(flags & MND_CLIENT_SESSION_FOCUSED) != 0 ? 1 : 0, //
(flags & MND_CLIENT_IO_ACTIVE) != 0 ? 1 : 0, //
(flags & MND_CLIENT_SESSION_OVERLAY) != 0 ? 1 : 0, //
name);
}
return 0;
}
int
set_primary(mnd_root_t *root, int client_index)
{
mnd_result_t mret = mnd_root_set_client_primary(root, client_index);
if (mret != MND_SUCCESS) {
PE("Failed to set active client to index %d.\n", client_index);
return 1;
}
return 0;
}
int
set_focused(mnd_root_t *root, int client_index)
{
mnd_result_t mret = mnd_root_set_client_focused(root, client_index);
if (mret != MND_SUCCESS) {
PE("Failed to set focused client to index %d.\n", client_index);
return 1;
}
return 0;
}
int
toggle_io(mnd_root_t *root, int client_index)
{
mnd_result_t mret = mnd_root_toggle_client_io_active(root, client_index);
if (mret != MND_SUCCESS) {
PE("Failed to toggle io for client index %d.\n", client_index);
return 1;
}
return 0;
}
int
main(int argc, char *argv[])
{
op_mode_t op_mode = MODE_GET;
// parse arguments
int c;
int s_val = 0;
opterr = 0;
while ((c = getopt(argc, argv, "p:f:i:")) != -1) {
switch (c) {
case 'p':
s_val = atoi(optarg);
CHECK_ID_EXIT(s_val);
op_mode = MODE_SET_PRIMARY;
break;
case 'f':
s_val = atoi(optarg);
CHECK_ID_EXIT(s_val);
op_mode = MODE_SET_FOCUSED;
break;
case 'i':
s_val = atoi(optarg);
CHECK_ID_EXIT(s_val);
op_mode = MODE_TOGGLE_IO;
break;
case '?':
if (optopt == 's') {
PE("Option -s requires a client index to set.\n");
} else if (isprint(optopt)) {
PE("Option `-%c' unknown. Usage:\n", optopt);
PE(" -f <index>: Set focused client\n");
PE(" -p <index>: Set primary client\n");
PE(" -i <index>: Toggle whether client receives input\n");
} else {
PE("Option `\\x%x' unknown.\n", optopt);
}
exit(1);
default: exit(0);
}
}
mnd_root_t *root = NULL;
mnd_result_t mret;
mret = mnd_root_create(&root);
if (mret != MND_SUCCESS) {
PE("Failed to connect.");
return 1;
}
mret = mnd_root_update_client_list(root);
if (mret != MND_SUCCESS) {
PE("Failed to update client list.");
return 1;
}
switch (op_mode) {
case MODE_GET: exit(get_mode(root)); break;
case MODE_SET_PRIMARY: exit(set_primary(root, s_val)); break;
case MODE_SET_FOCUSED: exit(set_focused(root, s_val)); break;
case MODE_TOGGLE_IO: exit(toggle_io(root, s_val)); break;
default: P("Unrecognised operation mode.\n"); exit(1);
}
return 0;
}

View file

@ -0,0 +1,189 @@
#!/usr/bin/env luajit
-- Copyright 2020-2023, Collabora, Ltd.
-- SPDX-License-Identifier: BSL-1.0
-- Author: Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
local ffi = require("ffi")
ffi.cdef[[
typedef enum mnd_result
{
MND_SUCCESS = 0,
MND_ERROR_INVALID_VERSION = -1,
MND_ERROR_INVALID_VALUE = -2,
MND_ERROR_CONNECTING_FAILED = -3,
MND_ERROR_OPERATION_FAILED = -4,
} mnd_result_t;
typedef enum mnd_client_flags
{
MND_CLIENT_PRIMARY_APP = (1u << 0u),
MND_CLIENT_SESSION_ACTIVE = (1u << 1u),
MND_CLIENT_SESSION_VISIBLE = (1u << 2u),
MND_CLIENT_SESSION_FOCUSED = (1u << 3u),
MND_CLIENT_SESSION_OVERLAY = (1u << 4u),
MND_CLIENT_IO_ACTIVE = (1u << 5u),
} mnd_client_flags_t;
typedef struct mnd_root mnd_root_t;
void
mnd_api_get_version(uint32_t *out_major, uint32_t *out_minor, uint32_t *out_patch);
mnd_result_t
mnd_root_create(mnd_root_t **out_root);
void
mnd_root_destroy(mnd_root_t **root_ptr);
mnd_result_t
mnd_root_update_client_list(mnd_root_t *root);
mnd_result_t
mnd_root_get_number_clients(mnd_root_t *root, uint32_t *out_num);
mnd_result_t
mnd_root_get_client_id_at_index(mnd_root_t *root, uint32_t index, uint32_t *out_client_id);
mnd_result_t
mnd_root_get_client_name(mnd_root_t *root, uint32_t client_id, const char **out_name);
mnd_result_t
mnd_root_get_client_state(mnd_root_t *root, uint32_t client_id, uint32_t *out_flags);
mnd_result_t
mnd_root_set_client_primary(mnd_root_t *root, uint32_t client_id);
mnd_result_t
mnd_root_set_client_focused(mnd_root_t *root, uint32_t client_id);
mnd_result_t
mnd_root_toggle_client_io_active(mnd_root_t *root, uint32_t client_id);
]]
local status, lib = pcall(ffi.load, "libmonado.so")
if not status then
print("Could not find an installed libmonado.so in your path.")
print("Add the Monado build directory to your LD_LIBRARY_PATH:")
print(string.format("LD_LIBRARY_PATH=/home/user/monado/build/src/xrt/targets/libmonado/ %s", arg[0]))
os.exit(1)
end
-- Parse arguments
local args = {...}
for i=1, #args, 1 do
if args[i] == "-f" or args[i] == "--focused" then
args_focused = tonumber(args[i+1])
elseif args[i] == "-p" or args[i] == "--primary" then
args_primary = tonumber(args[i+1])
elseif args[i] == "-i" or args[i] == "--input" then
args_input = tonumber(args[i+1])
end
end
-- Using ** doesn't work here, it hits an assertion in moando due being NULL
local root_ptr = ffi.new("mnd_root_t*[1]")
function create_root()
local ret = lib.mnd_root_create(root_ptr)
if ret ~= 0 then
error("Could not create root")
end
return root_ptr[0]
end
local root = create_root()
function update_clients()
local ret = lib.mnd_root_update_client_list(root)
if ret ~= 0 then
error("Could not update clients")
end
end
function get_client_count()
client_count_ptr = ffi.new("uint32_t[1]")
ret = lib.mnd_root_get_number_clients(root, client_count_ptr)
if ret ~= 0 then
error("Could not get number of clients")
end
return client_count_ptr[0]
end
function get_client_id_at_index(index)
client_id_ptr = ffi.new("uint32_t[1]")
local ret = lib.mnd_root_get_client_id_at_index(root, index, client_id_ptr)
if ret ~= 0 then
error("Could not get client id at index.")
end
return client_id_ptr[0]
end
function get_client_name(client_id)
name_ptr = ffi.new("const char*[1]")
local ret = lib.mnd_root_get_client_name(root, client_id, name_ptr)
if ret ~= 0 then
error("Could not get client name.")
end
return name_ptr[0]
end
function get_client_flags(client_id)
flags_ptr = ffi.new("uint32_t[1]")
local ret = lib.mnd_root_get_client_state(root, client_id, flags_ptr)
if ret ~= 0 then
error("Could not get client flags.")
end
return flags_ptr[0]
end
function set_primary(client_id)
local ret = lib.mnd_root_set_client_primary(root, client_id)
if ret ~= 0 then
error("Failed to set primary client to client id", client_id)
end
end
function set_focused(client_id)
ret = lib.mnd_root_set_client_focused(root, client_id)
if ret ~= 0 then
error("Failed to set focused client to client id", client_id)
end
end
function toggle_io(client_id)
ret = lib.mnd_root_toggle_client_io_active(root, client_id)
if ret ~= 0 then
error("Failed to toggle io for client client id", client_id)
end
end
if args_primary then
set_primary(args_primary)
end
if args_focused then
set_focused(args_focused)
end
if args_input then
toggle_io(args_input)
end
update_clients(root)
print("Client count:", get_client_count())
for i = 0, get_client_count() - 1 do
local client_id = get_client_id_at_index(i)
local name = get_client_name(client_id)
local flags = get_client_flags(client_id)
local primary = bit.band(flags, lib.MND_CLIENT_PRIMARY_APP) ~= 0
local active = bit.band(flags, lib.MND_CLIENT_SESSION_ACTIVE) ~= 0
local visible = bit.band(flags, lib.MND_CLIENT_SESSION_VISIBLE) ~= 0
local focused = bit.band(flags, lib.MND_CLIENT_SESSION_FOCUSED) ~= 0
local overlay = bit.band(flags, lib.MND_CLIENT_SESSION_OVERLAY) ~= 0
local io_active = bit.band(flags, lib.MND_CLIENT_IO_ACTIVE) ~= 0
print(string.format("id: %d primary: %5s active: %5s visible: %5s focused: %5s io: %5s overlay: %5s name: %s",
client_id, tostring(primary), tostring(active), tostring(visible), tostring(focused),
tostring(io_active), tostring(overlay), ffi.string(name)))
end
lib.mnd_root_destroy(root_ptr)

View file

@ -0,0 +1,65 @@
#!/bin/env python3
# Copyright 2020-2023, Collabora, Ltd.
# SPDX-License-Identifier: BSL-1.0
# Author: Jakob Bornecrantz <jakob@collabora.com>
# Author: Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
# Author: Korcan Hussein <korcan.hussein@collabora.com>
import argparse
from monado import Monado, MonadoLibraryNotFoundError, MonadoHeaderNotFoundError
def main():
parser = argparse.ArgumentParser(description='libmonado Python example.')
parser.add_argument("-f", "--focused", type=int, metavar='CLIENT_ID',
help="Set focused client")
parser.add_argument("-p", "--primary", type=int, metavar='CLIENT_ID',
help="Set primary client")
parser.add_argument("-i", "--input", type=int, metavar='CLIENT_ID',
help="Toggle whether client receives input")
args = parser.parse_args()
try:
m = Monado()
except MonadoLibraryNotFoundError:
print("Could not find an installed libmonado.so in your path.")
print("Add the Monado build directory to your LD_LIBRARY_PATH:")
print(f"LD_LIBRARY_PATH=/home/user/monado/build/src/xrt/targets/libmonado/ ./{parser.prog}")
return
except MonadoHeaderNotFoundError:
print("Could not find an installed monado.h.")
print("Try setting the MONADO_HEADER_PATH manually:")
print(f"MONADO_HEADER_PATH=/home/user/monado/src/xrt/targets/libmonado/monado.h ./{parser.prog}")
return
if args.focused:
m.set_focused(args.focused)
if args.primary:
m.set_primary(args.primary)
if args.input:
m.toggle_io(args.input)
m.update_clients()
print(f"Clients: {m.client_count}")
for x in range(m.client_count):
c = m.snapshot_client(x)
print(f"\tid: {c.ident:4d}, primary: {c.primary:d}, active: {c.active:d}, "
f"visible: {c.visible:d}, focused: {c.focused:d}, io: {c.io_active:d}, "
f"overlay: {c.overlay:d}, name: {c.name}")
devices = m.get_devices()
print(f"Devices: {len(devices)}")
for dev in devices:
print(f"\tid: {dev.ident:4d}, name: {dev.name}")
roles_map = m.get_device_roles()
print(f"Roles: {len(roles_map)}")
for role_name, dev_id in roles_map.items():
print(f"\trole: {role_name},\tdevice-index: {dev_id:4d}")
m.destroy()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,15 @@
; Copyright 2019-2023, Collabora, Ltd.
; SPDX-License-Identifier: BSL-1.0
EXPORTS
mnd_root_create
mnd_root_destroy
mnd_root_update_client_list
mnd_root_get_number_clients
mnd_root_get_client_name
mnd_root_get_client_state
mnd_root_set_client_primary
mnd_root_set_client_focused
mnd_root_toggle_client_io_active
mnd_root_get_device_count
mnd_root_get_device_info
mnd_root_get_device_from_role

View file

@ -0,0 +1,336 @@
// Copyright 2019-2023, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Implementation of libmonado
* @author Jakob Bornecrantz <jakob@collabora.com>
* @author Pete Black <pblack@collabora.com>
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
*/
#include "monado.h"
#include "xrt/xrt_results.h"
#include "util/u_misc.h"
#include "util/u_file.h"
#include "util/u_logging.h"
#include "shared/ipc_protocol.h"
#include "client/ipc_client_connection.h"
#include "client/ipc_client.h"
#include "ipc_client_generated.h"
#include <limits.h>
#include <stdint.h>
#include <assert.h>
struct mnd_root
{
struct ipc_connection ipc_c;
//! List of clients.
struct ipc_client_list clients;
/// State of most recent app asked about
struct ipc_app_state app_state;
};
#define P(...) fprintf(stdout, __VA_ARGS__)
#define PE(...) fprintf(stderr, __VA_ARGS__)
/*
*
* Helper functions.
*
*/
#define CHECK_NOT_NULL(ARG) \
do { \
if (ARG == NULL) { \
PE("Argument '" #ARG "' can not be null!"); \
return MND_ERROR_INVALID_VALUE; \
} \
} while (false)
#define CHECK_CLIENT_ID(ID) \
do { \
if (ID == 0 && ID > INT_MAX) { \
PE("Invalid client id (%u)", ID); \
return MND_ERROR_INVALID_VALUE; \
} \
} while (false)
#define CHECK_CLIENT_INDEX(INDEX) \
do { \
if (INDEX >= root->clients.id_count) { \
PE("Invalid client index, too large (%u)", INDEX); \
return MND_ERROR_INVALID_VALUE; \
} \
} while (false)
static int
get_client_info(mnd_root_t *root, uint32_t client_id)
{
assert(root != NULL);
xrt_result_t r = ipc_call_system_get_client_info(&root->ipc_c, client_id, &root->app_state);
if (r != XRT_SUCCESS) {
PE("Failed to get client info for client id: %u.\n", client_id);
return MND_ERROR_INVALID_VALUE;
}
return MND_SUCCESS;
}
/*
*
* API API.
*
*/
void
mnd_api_get_version(uint32_t *out_major, uint32_t *out_minor, uint32_t *out_patch)
{
*out_major = MND_API_VERSION_MAJOR;
*out_minor = MND_API_VERSION_MINOR;
*out_patch = MND_API_VERSION_PATCH;
}
/*
*
* Root API
*
*/
mnd_result_t
mnd_root_create(mnd_root_t **out_root)
{
CHECK_NOT_NULL(out_root);
mnd_root_t *r = U_TYPED_CALLOC(mnd_root_t);
struct xrt_instance_info info = {0};
snprintf(info.application_name, sizeof(info.application_name), "%s", "libmonado");
xrt_result_t xret = ipc_client_connection_init(&r->ipc_c, U_LOGGING_INFO, &info);
if (xret != XRT_SUCCESS) {
PE("Connection init error '%i'!\n", xret);
free(r);
return MND_ERROR_CONNECTING_FAILED;
}
*out_root = r;
return MND_SUCCESS;
}
void
mnd_root_destroy(mnd_root_t **root_ptr)
{
if (root_ptr == NULL) {
return;
}
mnd_root_t *r = *root_ptr;
if (r == NULL) {
return;
}
ipc_client_connection_fini(&r->ipc_c);
free(r);
*root_ptr = NULL;
return;
}
mnd_result_t
mnd_root_update_client_list(mnd_root_t *root)
{
CHECK_NOT_NULL(root);
xrt_result_t r = ipc_call_system_get_clients(&root->ipc_c, &root->clients);
if (r != XRT_SUCCESS) {
PE("Failed to get client list.\n");
return MND_ERROR_OPERATION_FAILED;
}
return MND_SUCCESS;
}
mnd_result_t
mnd_root_get_number_clients(mnd_root_t *root, uint32_t *out_num)
{
CHECK_NOT_NULL(root);
CHECK_NOT_NULL(out_num);
*out_num = root->clients.id_count;
return MND_SUCCESS;
}
mnd_result_t
mnd_root_get_client_id_at_index(mnd_root_t *root, uint32_t index, uint32_t *out_client_id)
{
CHECK_NOT_NULL(root);
CHECK_CLIENT_INDEX(index);
*out_client_id = root->clients.ids[index];
return MND_SUCCESS;
}
mnd_result_t
mnd_root_get_client_name(mnd_root_t *root, uint32_t client_id, const char **out_name)
{
CHECK_NOT_NULL(root);
CHECK_CLIENT_ID(client_id);
CHECK_NOT_NULL(out_name);
mnd_result_t mret = get_client_info(root, client_id);
if (mret < 0) {
return mret; // Prints error.
}
*out_name = &(root->app_state.info.application_name[0]);
return MND_SUCCESS;
}
mnd_result_t
mnd_root_get_client_state(mnd_root_t *root, uint32_t client_id, uint32_t *out_flags)
{
CHECK_NOT_NULL(root);
CHECK_CLIENT_ID(client_id);
CHECK_NOT_NULL(out_flags);
mnd_result_t mret = get_client_info(root, client_id);
if (mret < 0) {
return mret; // Prints error.
}
uint32_t flags = 0;
flags |= (root->app_state.primary_application) ? MND_CLIENT_PRIMARY_APP : 0u;
flags |= (root->app_state.session_active) ? MND_CLIENT_SESSION_ACTIVE : 0u;
flags |= (root->app_state.session_visible) ? MND_CLIENT_SESSION_VISIBLE : 0u;
flags |= (root->app_state.session_focused) ? MND_CLIENT_SESSION_FOCUSED : 0u;
flags |= (root->app_state.session_overlay) ? MND_CLIENT_SESSION_OVERLAY : 0u;
flags |= (root->app_state.io_active) ? MND_CLIENT_IO_ACTIVE : 0u;
*out_flags = flags;
return MND_SUCCESS;
}
mnd_result_t
mnd_root_set_client_primary(mnd_root_t *root, uint32_t client_id)
{
CHECK_NOT_NULL(root);
CHECK_CLIENT_ID(client_id);
xrt_result_t r = ipc_call_system_set_primary_client(&root->ipc_c, client_id);
if (r != XRT_SUCCESS) {
PE("Failed to set primary to client id: %u.\n", client_id);
return MND_ERROR_OPERATION_FAILED;
}
return MND_SUCCESS;
}
mnd_result_t
mnd_root_set_client_focused(mnd_root_t *root, uint32_t client_id)
{
CHECK_NOT_NULL(root);
CHECK_CLIENT_ID(client_id);
xrt_result_t r = ipc_call_system_set_focused_client(&root->ipc_c, client_id);
if (r != XRT_SUCCESS) {
PE("Failed to set focused to client id: %u.\n", client_id);
return MND_ERROR_OPERATION_FAILED;
}
return MND_SUCCESS;
}
mnd_result_t
mnd_root_toggle_client_io_active(mnd_root_t *root, uint32_t client_id)
{
CHECK_NOT_NULL(root);
CHECK_CLIENT_ID(client_id);
xrt_result_t r = ipc_call_system_toggle_io_client(&root->ipc_c, client_id);
if (r != XRT_SUCCESS) {
PE("Failed to toggle io for client id: %u.\n", client_id);
return MND_ERROR_OPERATION_FAILED;
}
return MND_SUCCESS;
}
mnd_result_t
mnd_root_get_device_count(mnd_root_t *root, uint32_t *out_device_count)
{
CHECK_NOT_NULL(root);
CHECK_NOT_NULL(out_device_count);
*out_device_count = root->ipc_c.ism->isdev_count;
return MND_SUCCESS;
}
mnd_result_t
mnd_root_get_device_info(mnd_root_t *root, uint32_t device_index, uint32_t *out_device_id, const char **out_dev_name)
{
CHECK_NOT_NULL(root);
CHECK_NOT_NULL(out_device_id);
CHECK_NOT_NULL(out_dev_name);
if (device_index >= root->ipc_c.ism->isdev_count) {
PE("Invalid device index (%u)", device_index);
return MND_ERROR_INVALID_VALUE;
}
const struct ipc_shared_device *shared_device = &root->ipc_c.ism->isdevs[device_index];
*out_device_id = shared_device->name;
*out_dev_name = shared_device->str;
return MND_SUCCESS;
}
mnd_result_t
mnd_root_get_device_from_role(mnd_root_t *root, const char *role_name, int32_t *out_device_id)
{
CHECK_NOT_NULL(root);
CHECK_NOT_NULL(role_name);
CHECK_NOT_NULL(out_device_id);
#ifndef MND_PP_DEV_ID_FROM_ROLE
#define MND_PP_DEV_ID_FROM_ROLE(role) \
if (strcmp(role_name, #role) == 0) { \
*out_device_id = (int32_t)root->ipc_c.ism->roles.role; \
return MND_SUCCESS; \
}
#endif
MND_PP_DEV_ID_FROM_ROLE(head)
MND_PP_DEV_ID_FROM_ROLE(left)
MND_PP_DEV_ID_FROM_ROLE(right)
MND_PP_DEV_ID_FROM_ROLE(gamepad)
MND_PP_DEV_ID_FROM_ROLE(eyes)
#undef MND_PP_DEV_ID_FROM_ROLE
if (strcmp(role_name, "hand-tracking-left") == 0) {
*out_device_id = root->ipc_c.ism->roles.hand_tracking.left;
return MND_SUCCESS;
}
if (strcmp(role_name, "hand-tracking-right") == 0) {
*out_device_id = root->ipc_c.ism->roles.hand_tracking.right;
return MND_SUCCESS;
}
PE("Invalid role name (%s)", role_name);
return MND_ERROR_INVALID_VALUE;
}

View file

@ -0,0 +1,247 @@
// Copyright 2019-2023, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Interface of libmonado
* @author Jakob Bornecrantz <jakob@collabora.com>
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
*/
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
*
* Enums, defines and objects.
*
*/
//! Major version of the API.
#define MND_API_VERSION_MAJOR 1
//! Minor version of the API.
#define MND_API_VERSION_MINOR 0
//! Patch version of the API.
#define MND_API_VERSION_PATCH 0
/*!
* Result codes for operations, negative are errors, zero or positives are
* success.
*/
typedef enum mnd_result
{
MND_SUCCESS = 0,
MND_ERROR_INVALID_VERSION = -1,
MND_ERROR_INVALID_VALUE = -2,
MND_ERROR_CONNECTING_FAILED = -3,
MND_ERROR_OPERATION_FAILED = -4,
} mnd_result_t;
/*!
* Bitflags for client application state.
*/
typedef enum mnd_client_flags
{
MND_CLIENT_PRIMARY_APP = (1u << 0u),
MND_CLIENT_SESSION_ACTIVE = (1u << 1u),
MND_CLIENT_SESSION_VISIBLE = (1u << 2u),
MND_CLIENT_SESSION_FOCUSED = (1u << 3u),
MND_CLIENT_SESSION_OVERLAY = (1u << 4u),
MND_CLIENT_IO_ACTIVE = (1u << 5u),
} mnd_client_flags_t;
/*!
* Opaque type for libmonado state
*/
typedef struct mnd_root mnd_root_t;
/*
*
* Functions
*
*/
/*!
* Returns the version of the API (not Monado itself), follows the versioning
* semantics of https://semver.org/ standard. In short if the major version
* mismatch then the interface is incompatible.
*
* @param[out] out_major Major version number, must be valid pointer.
* @param[out] out_minor Minor version number, must be valid pointer.
* @param[out] out_patch Patch version number, must be valid pointer.
*
* Always succeeds, or crashes if any pointer isn't valid.
*/
void
mnd_api_get_version(uint32_t *out_major, uint32_t *out_minor, uint32_t *out_patch);
/*!
* Create libmonado state and connect to service
*
* @param[out] out_root Address to populate with the opaque state type.
* @return MND_SUCCESS on success
*/
mnd_result_t
mnd_root_create(mnd_root_t **out_root);
/*!
* Destroy libmonado state, disconnecting from the service, and zeroing the
* pointer.
*
* @param root_ptr Pointer to your libmonado state. Null-checked, will be set to null.
*/
void
mnd_root_destroy(mnd_root_t **root_ptr);
/*!
* Update our local cached copy of the client list
*
* @param root The libmonado state.
* @return MND_SUCCESS on success
*/
mnd_result_t
mnd_root_update_client_list(mnd_root_t *root);
/*!
* Get the number of active clients
*
* This value only changes on calls to @ref mnd_root_update_client_list
*
* @param root The libmonado state.
* @param[out] out_num Pointer to value to populate with the number of clients.
*
* @pre Called @ref mnd_root_update_client_list at least once
*
* @return MND_SUCCESS on success
*/
mnd_result_t
mnd_root_get_number_clients(mnd_root_t *root, uint32_t *out_num);
/*!
* Get the id from the current client list.
*
* @param root The libmonado state.
* @param index Index to retrieve id for.
* @param[out] out_client_id Pointer to value to populate with the id at the given index.
*/
mnd_result_t
mnd_root_get_client_id_at_index(mnd_root_t *root, uint32_t index, uint32_t *out_client_id);
/*!
* Get the name of the client at the given index.
*
* The string returned is only valid until the next call into libmonado.
*
* @param root The libmonado state.
* @param client_id ID of client to retrieve name from.
* @param[out] out_name Pointer to populate with the client name.
*
* @pre Called @ref mnd_root_update_client_list at least once
*
* @return MND_SUCCESS on success
*/
mnd_result_t
mnd_root_get_client_name(mnd_root_t *root, uint32_t client_id, const char **out_name);
/*!
* Get the state flags of the client at the given index.
*
* This result only changes on calls to @ref mnd_root_update_client_list
*
* @param root The libmonado state.
* @param client_id ID of client to retrieve flags from.
* @param[out] out_flags Pointer to populate with the flags, a bitwise combination of @ref mnd_client_flags.
*
* @pre Called @ref mnd_root_update_client_list at least once
*
* @return MND_SUCCESS on success
*/
mnd_result_t
mnd_root_get_client_state(mnd_root_t *root, uint32_t client_id, uint32_t *out_flags);
/*!
* Set the client at the given index as "primary".
*
* @param root The libmonado state.
* @param client_id ID of the client set as primary.
*
* @pre Called @ref mnd_root_update_client_list at least once
*
* @return MND_SUCCESS on success
*/
mnd_result_t
mnd_root_set_client_primary(mnd_root_t *root, uint32_t client_id);
/*!
* Set the client at the given index as "focused".
*
* @param root The libmonado state.
* @param client_id ID of the client set as focused.
*
* @pre Called @ref mnd_root_update_client_list at least once
*
* @return MND_SUCCESS on success
*/
mnd_result_t
mnd_root_set_client_focused(mnd_root_t *root, uint32_t client_id);
/*!
* Toggle io activity for the client at the given index.
*
* @param root The libmonado state.
* @param client_id ID of the client to toggle IO for.
*
* @pre Called @ref mnd_root_update_client_list at least once
*
* @return MND_SUCCESS on success
*/
mnd_result_t
mnd_root_toggle_client_io_active(mnd_root_t *root, uint32_t client_id);
/*!
* Get the number of devices
*
* @param root The libmonado state.
* @param[out] out_device_count Pointer to value to populate with the number of devices.
*
* @return MND_SUCCESS on success
*/
mnd_result_t
mnd_root_get_device_count(mnd_root_t *root, uint32_t *out_device_count);
/*!
* Get device info at the given index.
*
* @param root The libmonado state.
* @param device_index Index of device to retrieve name from.
* @param[out] out_device_id Pointer to value to populate with the device id at the given index.
* @param[out] out_dev_name Pointer to populate with the device name.
*
* @return MND_SUCCESS on success
*/
mnd_result_t
mnd_root_get_device_info(mnd_root_t *root, uint32_t device_index, uint32_t *out_device_id, const char **out_dev_name);
/*!
* Get the device index associated for a given role name.
*
* @param root The libmonado state.
* @param role_name Name of the role, one-of
* "head","left","right"."gamepad","eyes","hand-tracking-left","hand-tracking-right"
* @param[out] out_device_id Pointer to value to populate with the device id associated with given role name, -1 if not
* role is set.
*
* @return MND_SUCCESS on success
*/
mnd_result_t
mnd_root_get_device_from_role(mnd_root_t *root, const char *role_name, int32_t *out_device_id);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,211 @@
# Copyright 2020-2023, Collabora, Ltd.
# SPDX-License-Identifier: BSL-1.0
# Author: Jakob Bornecrantz <jakob@collabora.com>
# Author: Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
# Author: Korcan Hussein <korcan.hussein@collabora.com>
import os
from cffi import FFI
from pathlib import Path
def preprocess_ffi(lines: list[str]) -> str:
out_lines = []
skipping = False
for line in lines:
if line.startswith("#ifdef"):
skipping = True
continue
elif line.startswith("#endif"):
skipping = False
continue
elif line.startswith("#include"):
continue
if skipping:
continue
out_lines.append(line)
return "\n".join(out_lines)
class MonadoHeaderNotFoundError(Exception):
pass
def find_monado_header() -> Path:
search_paths = [
# Try script directory first, in case running uninstalled
Path(__file__).parent / "monado.h",
# path from installed package
Path("/usr/include/monado/monado.h"),
# path from local install
Path("/usr/local/include/monado/monado.h")
]
# path from ENV
if os.getenv('MONADO_HEADER_PATH'):
search_paths.append(Path(os.getenv('MONADO_HEADER_PATH')))
for path in search_paths:
if path.is_file():
return path
# Give up
raise MonadoHeaderNotFoundError("Could not find monado.h. Define the path by setting MONADO_HEADER_PATH.")
def load_ffi() -> FFI:
ffi = FFI()
header_path = find_monado_header()
with header_path.open("r") as f:
header_lines = f.readlines()
header = preprocess_ffi(header_lines)
ffi.cdef(header)
return ffi
class Device:
def __init__(self, ident, name):
self.ident = ident
self.name = name
class Client:
def __init__(self, ident, name, primary, focused, visible, active, overlay, io_active):
self.ident = ident
self.name = name
self.primary = primary
self.focused = focused
self.visible = visible
self.active = active
self.overlay = overlay
self.io_active = io_active
class MonadoLibraryNotFoundError(Exception):
pass
class Monado:
def __init__(self):
self.ffi = load_ffi()
try:
self.lib = self.ffi.dlopen("libmonado.so")
except OSError:
raise MonadoLibraryNotFoundError("Could not load libmonado.so.")
self.root_ptr = self.ffi.new("mnd_root_t **")
ret = self.lib.mnd_root_create(self.root_ptr)
if ret != 0:
raise Exception("Could not create root")
self.root = self.root_ptr[0]
self.client_count = 0
self.client_count_ptr = self.ffi.new("uint32_t *")
self.name_ptr = self.ffi.new("char **")
self.flags_ptr = self.ffi.new("uint32_t *")
self.client_id_ptr = self.ffi.new("uint32_t *")
self.device_id_ptr = self.ffi.new("uint32_t *")
self.device_name_ptr = self.ffi.new("char **")
self.device_count_ptr = self.ffi.new("uint32_t *")
def update_clients(self):
ret = self.lib.mnd_root_update_client_list(self.root)
if ret != 0:
raise Exception("Could not update clients")
ret = self.lib.mnd_root_get_number_clients(self.root, self.client_count_ptr)
if ret != 0:
raise Exception("Could not update clients")
self.client_count = self.client_count_ptr[0]
def get_client_id_at_index(self, index):
ret = self.lib.mnd_root_get_client_id_at_index(self.root, index, self.client_id_ptr)
if ret != 0:
raise Exception("Could not get client id at index")
return self.client_id_ptr[0]
def get_client_name(self, client_id):
ret = self.lib.mnd_root_get_client_name(self.root, client_id, self.name_ptr)
if ret != 0:
raise Exception("Could not get client name")
return self.ffi.string(self.name_ptr[0]).decode("utf-8")
def get_client_flags(self, client_id):
ret = self.lib.mnd_root_get_client_state(self.root, client_id, self.flags_ptr)
if ret != 0:
raise Exception("Could not get client state")
return self.flags_ptr[0]
def snapshot_client(self, index):
ident = self.get_client_id_at_index(index)
name = self.get_client_name(ident)
flags = self.get_client_flags(ident)
primary = (flags & self.lib.MND_CLIENT_PRIMARY_APP) != 0
active = (flags & self.lib.MND_CLIENT_SESSION_ACTIVE) != 0
visible = (flags & self.lib.MND_CLIENT_SESSION_VISIBLE) != 0
focused = (flags & self.lib.MND_CLIENT_SESSION_FOCUSED) != 0
overlay = (flags & self.lib.MND_CLIENT_SESSION_OVERLAY) != 0
io_active = (flags & self.lib.MND_CLIENT_IO_ACTIVE) != 0
return Client(ident, name, primary, focused, visible, active, overlay, io_active)
def destroy(self):
self.lib.mnd_root_destroy(self.root_ptr)
self.root = self.root_ptr[0]
def set_primary(self, client_id: int):
ret = self.lib.mnd_root_set_client_primary(self.root, client_id)
if ret != 0:
raise Exception(f"Failed to set primary client id to {client_id}.")
def set_focused(self, client_id: int):
ret = self.lib.mnd_root_set_client_focused(self.root, client_id)
if ret != 0:
raise Exception(f"Failed to set focused client id to {client_id}.")
def toggle_io(self, client_id: int):
ret = self.lib.mnd_root_toggle_client_io_active(self.root, client_id)
if ret != 0:
raise Exception(f"Failed to toggle io for client id {client_id}.")
def get_device_count(self):
ret = self.lib.mnd_root_get_device_count(self.root, self.device_count_ptr)
if ret != 0:
raise Exception("Could not get device count")
return self.device_count_ptr[0]
def get_device_at_index(self, index):
ret = self.lib.mnd_root_get_device_info(self.root, index, self.device_id_ptr, self.device_name_ptr)
if ret != 0:
raise Exception(f"Could not get device at index:{index}")
dev_id = self.device_id_ptr[0]
dev_name = self.ffi.string(self.device_name_ptr[0]).decode("utf-8")
return Device(dev_id, dev_name)
def get_devices(self):
devices = []
dev_count = self.get_device_count()
for i in range(dev_count):
devices.append(self.get_device_at_index(i))
return devices
def get_device_roles(self):
role_map = dict()
device_int_id_ptr = self.ffi.new("int32_t *")
for role_name in ["head", "left", "right", "gamepad", "eyes", "hand-tracking-left", "hand-tracking-right"]:
crole_name = role_name.encode('utf-8')
ret = self.lib.mnd_root_get_device_from_role(self.root, crole_name, device_int_id_ptr)
if ret != 0:
raise Exception(f"Could not get device role: {role_name}")
role_map[role_name] = device_int_id_ptr[0]
return role_map