fangorn/sunshine-qemu
public
ref:main
/**
* @file src/platform/linux/qemu/session.h
* @brief Declarations for the QEMU D-Bus display session.
* @details The session connects to the D-Bus bus that a QEMU started with `-display dbus` owns
* `org.qemu` on, discovers the VM consoles, and registers peer-to-peer display listeners. It only
* depends on GLib/GIO and POSIX so it can be reused by other Unix hosts.
*/
#pragma once
// standard includes
#include <chrono>
#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
namespace qemu {
using namespace std::literals;
/**
* @brief Pixman format codes used by QEMU display scanouts.
* @details Values follow pixman's `PIXMAN_FORMAT(bpp, type, a, r, g, b)` encoding. They are
* defined here so the capture backend does not need to link against pixman.
*/
namespace pixman_format {
inline constexpr std::uint32_t x8r8g8b8 = 0x20020888; ///< 32 bpp, B G R X byte order on little-endian hosts.
inline constexpr std::uint32_t a8r8g8b8 = 0x20028888; ///< 32 bpp, B G R A byte order on little-endian hosts.
inline constexpr std::uint32_t x8b8g8r8 = 0x20030888; ///< 32 bpp, R G B X byte order on little-endian hosts.
inline constexpr std::uint32_t a8b8g8r8 = 0x20038888; ///< 32 bpp, R G B A byte order on little-endian hosts.
} // namespace pixman_format
/**
* @brief Move-only owner of a POSIX file descriptor.
*/
class fd_t {
public:
fd_t() = default;
/**
* @brief Take ownership of a file descriptor.
*
* @param fd File descriptor to own, or -1 for none.
*/
explicit fd_t(int fd);
/**
* @brief Transfer ownership from another descriptor owner.
*
* @param other Owner to take the descriptor from.
*/
fd_t(fd_t &&other) noexcept;
/**
* @brief Replace the owned descriptor with the one held by another owner.
*
* @param other Owner to take the descriptor from.
* @return This owner.
*/
fd_t &operator=(fd_t &&other) noexcept;
fd_t(const fd_t &) = delete;
fd_t &operator=(const fd_t &) = delete;
~fd_t();
/**
* @brief Access the owned descriptor without releasing it.
*
* @return The owned descriptor, or -1 when empty.
*/
[[nodiscard]] int get() const {
return fd;
}
/**
* @brief Release ownership of the descriptor.
*
* @return The descriptor, which the caller must close.
*/
int release();
private:
int fd {-1}; ///< Owned descriptor, or -1 when empty.
};
/**
* @brief Snapshot of one QEMU console.
*/
struct console_info_t {
std::uint32_t id {0}; ///< Console id, as used in `/org/qemu/Display1/Console_<id>`.
std::string label; ///< User-friendly console name, for example "VGA".
std::string type; ///< Console type, "Graphic" or "Text".
std::uint32_t head {0}; ///< Graphical device head number.
std::uint32_t width {0}; ///< Console width in pixels.
std::uint32_t height {0}; ///< Console height in pixels.
std::vector<std::string> interfaces; ///< Extra interfaces advertised by the console object.
/**
* @brief Report whether this console shows graphics.
*
* @return True when the console type is "Graphic".
*/
[[nodiscard]] bool is_graphic() const {
return type == "Graphic";
}
};
/**
* @brief Snapshot of the VM exported by QEMU.
*/
struct vm_info_t {
std::string name; ///< VM name (`-name`).
std::string uuid; ///< VM UUID.
std::vector<console_info_t> consoles; ///< Consoles listed by `ConsoleIDs`, in QEMU order.
};
/**
* @brief Receiver for the `org.qemu.Display1.Listener` calls QEMU makes.
* @details All methods are invoked on the session's listener thread. Implementations must return
* quickly and must not call back into the session synchronously from a different thread while
* holding locks that the caller of the session may also take.
*/
class display_listener_t {
public:
virtual ~display_listener_t() = default;
/**
* @brief Handle a full-frame scanout carried in the message.
*
* @param width Display width in pixels.
* @param height Display height in pixels.
* @param stride Bytes per row in `data`.
* @param format Pixman format code of `data`.
* @param data Pixel data, valid only for the duration of the call.
*/
virtual void scanout(std::uint32_t width, std::uint32_t height, std::uint32_t stride, std::uint32_t format, std::span<const std::uint8_t> data) = 0;
/**
* @brief Handle a partial update carried in the message.
*
* @param x Left edge of the updated rectangle.
* @param y Top edge of the updated rectangle.
* @param width Width of the updated rectangle.
* @param height Height of the updated rectangle.
* @param stride Bytes per row in `data`.
* @param format Pixman format code of `data`.
* @param data Pixel data for the rectangle, valid only for the duration of the call.
*/
virtual void update(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height, std::uint32_t stride, std::uint32_t format, std::span<const std::uint8_t> data) = 0;
/**
* @brief Handle a scanout backed by shared memory.
*
* @param fd Shared memory descriptor; ownership moves to the listener.
* @param offset Offset of the first pixel in the mapping, in bytes.
* @param width Display width in pixels.
* @param height Display height in pixels.
* @param stride Bytes per row.
* @param format Pixman format code.
*/
virtual void scanout_map(fd_t fd, std::uint32_t offset, std::uint32_t width, std::uint32_t height, std::uint32_t stride, std::uint32_t format) = 0;
/**
* @brief Handle damage on the current shared memory scanout.
*
* @param x Left edge of the damaged rectangle.
* @param y Top edge of the damaged rectangle.
* @param width Width of the damaged rectangle.
* @param height Height of the damaged rectangle.
*/
virtual void update_map(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) = 0;
/**
* @brief Handle the display being turned off.
*/
virtual void disable() = 0;
/**
* @brief Handle a cursor position or visibility change.
*
* @param x Cursor X position in pixels.
* @param y Cursor Y position in pixels.
* @param visible Whether the cursor is visible.
*/
virtual void mouse_set(std::int32_t x, std::int32_t y, bool visible) {
}
/**
* @brief Handle a new cursor shape.
*
* @param width Cursor width in pixels.
* @param height Cursor height in pixels.
* @param hot_x Hot-spot X position.
* @param hot_y Hot-spot Y position.
* @param data ARGB32 cursor pixels, valid only for the duration of the call.
*/
virtual void cursor_define(std::int32_t width, std::int32_t height, std::int32_t hot_x, std::int32_t hot_y, std::span<const std::uint8_t> data) {
}
/**
* @brief Handle the peer-to-peer connection to QEMU closing.
* @details Called at most once. No other method is called afterwards.
*/
virtual void disconnected() = 0;
};
/**
* @brief Registration of a display listener; destroying it unregisters the listener.
* @details After the destructor returns, no method of the listener is invoked again.
*/
class listener_registration_t {
public:
virtual ~listener_registration_t() = default;
};
/**
* @brief Connection to the D-Bus display of one QEMU VM.
* @details All public methods are thread-safe.
*/
class session_t {
public:
/**
* @brief Connect to a bus where QEMU owns `org.qemu` and discover its consoles.
*
* @param address D-Bus address of the bus, or empty for the session bus.
* @param timeout Timeout applied to each D-Bus call.
* @return Connected session, or nullptr when the bus or QEMU is unreachable.
*/
static std::shared_ptr<session_t> connect(const std::string &address, std::chrono::milliseconds timeout = 5s);
virtual ~session_t() = default;
/**
* @brief Snapshot the VM properties, including the current console sizes.
*
* @return VM information.
*/
[[nodiscard]] virtual vm_info_t vm() const = 0;
/**
* @brief Report whether QEMU is still reachable.
*
* @return False once the bus connection closed or `org.qemu` lost its owner.
*/
[[nodiscard]] virtual bool alive() const = 0;
/**
* @brief Register a display listener on a console.
*
* @param console_id Id of the console to listen to.
* @param listener Receiver for the display calls; kept alive by the registration.
* @return Registration handle, or nullptr when QEMU refused the listener.
*/
virtual std::unique_ptr<listener_registration_t> register_listener(std::uint32_t console_id, std::shared_ptr<display_listener_t> listener) = 0;
};
/**
* @brief Get the live session for an address, connecting when there is none.
* @details Capture, input, and audio share one session while any of them holds it. A session
* whose QEMU went away is replaced by a new connection attempt.
*
* @param address D-Bus address, or empty for the session bus.
* @return Live session, or nullptr when QEMU is unreachable.
*/
std::shared_ptr<session_t> shared_session(const std::string &address);
/**
* @brief Find a console by id or label.
*
* @param vm VM information to search.
* @param name Console id, label, or empty for the first graphical console.
* @return Matching console, or nothing when no console matches.
*/
std::optional<console_info_t> find_console(const vm_info_t &vm, std::string_view name);
/**
* @brief List the names Sunshine uses for the graphical consoles.
*
* @param vm VM information to list.
* @return Console ids as strings, for graphical consoles only.
*/
std::vector<std::string> graphic_console_names(const vm_info_t &vm);
} // namespace qemu