Provide structures to define projector geometry

pull/10/head
Dominik Nakamura 3 years ago
parent 34479517d7
commit 8325bcccf7
No known key found for this signature in database
GPG Key ID: E4C6A749B2491910

@ -1,7 +1,7 @@
use serde::Serialize;
use super::Client;
use crate::requests::{KeyModifier, Projector, RequestType};
use crate::requests::{KeyModifier, Projector, ProjectorInternal, QtGeometry, RequestType};
use crate::responses;
use crate::{Error, Result};
@ -82,7 +82,12 @@ impl<'a> General<'a> {
/// Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer.
pub async fn open_projector(&self, projector: Projector<'_>) -> Result<()> {
self.client
.send_message(RequestType::OpenProjector(projector))
.send_message(RequestType::OpenProjector(ProjectorInternal {
ty: projector.ty,
monitor: projector.monitor,
geometry: projector.geometry.map(QtGeometry::serialize).as_deref(),
name: projector.name,
}))
.await
}

@ -2,6 +2,7 @@
use std::path::Path;
use bitflags::bitflags;
use chrono::Duration;
use either::Either;
use serde::Serialize;
@ -47,7 +48,7 @@ pub(crate) enum RequestType<'a> {
data: &'a serde_json::Value,
},
GetVideoInfo,
OpenProjector(Projector<'a>),
OpenProjector(ProjectorInternal<'a>),
#[serde(rename_all = "camelCase")]
TriggerHotkeyByName {
/// Unique name of the hotkey, as defined when registering the hotkey (e.g.
@ -450,19 +451,17 @@ pub(crate) enum RequestType<'a> {
}
/// Request information for [`open_projector`](crate::client::General::open_projector).
#[skip_serializing_none]
#[derive(Debug, Default, Serialize)]
#[derive(Debug, Default)]
pub struct Projector<'a> {
/// Type of projector: `Preview` (default), `Source`, `Scene`, `StudioProgram`, or `Multiview`
/// (case insensitive).
#[serde(rename = "type")]
pub ty: Option<ProjectorType>,
/// Monitor to open the projector on. If -1 or omitted, opens a window.
pub monitor: Option<i64>,
/// Size and position of the projector window (only if monitor is -1). Encoded in Base64 using
/// [Qt's geometry encoding](https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to
/// OBS's saved projectors.
pub geometry: Option<&'a str>,
pub geometry: Option<&'a QtGeometry>,
/// Name of the source or scene to be displayed (ignored for other projector types).
pub name: Option<&'a str>,
}
@ -483,6 +482,26 @@ pub enum ProjectorType {
Multiview,
}
/// Internal version of [`Projector`] which is the one that's actually send to the API. This allows
/// to let the user define a typed version of the geometry while sending the encoded version through
/// this struct.
#[skip_serializing_none]
#[derive(Debug, Default, Serialize)]
pub(crate) struct ProjectorInternal<'a> {
/// Type of projector: `Preview` (default), `Source`, `Scene`, `StudioProgram`, or `Multiview`
/// (case insensitive).
#[serde(rename = "type")]
pub ty: Option<ProjectorType>,
/// Monitor to open the projector on. If -1 or omitted, opens a window.
pub monitor: Option<i64>,
/// Size and position of the projector window (only if monitor is -1). Encoded in Base64 using
/// [Qt's geometry encoding](https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to
/// OBS's saved projectors.
pub geometry: Option<&'a str>,
/// Name of the source or scene to be displayed (ignored for other projector types).
pub name: Option<&'a str>,
}
/// Request information for
/// [`trigger_hotkey_by_sequence`](crate::client::General::trigger_hotkey_by_sequence).
#[derive(Debug, Default, Serialize)]
@ -1092,3 +1111,154 @@ pub struct Transition<'a> {
#[serde(serialize_with = "ser::duration_millis_opt")]
pub duration: Option<Duration>,
}
/// Request information for [`open_projector`](crate::client::General::open_projector) as part of
/// [`Projector`].
///
/// This describes a position on the screen starting from the top left corner with 0.
///
/// ```txt
/// Screen
/// ┌────────────────────── X
/// │
/// │ top
/// │ ┌────────┐
/// │ left │ Rect │ right
/// │ └────────┘
/// │ bottom
/// │
/// Y
///
#[derive(Clone, Copy, Debug, Default)]
pub struct QtRect {
/// Left or X/horizontal position of the rectangle.
pub left: i32,
/// Top or Y/vertical position of the rectangle.
pub top: i32,
/// The right side of a rectangle counted from the left. For example with left = 100 and right =
/// 300 the width would be 200.
pub right: i32,
/// Bottom side of a rectangle counted from the top. For example with top = 100 and bottom = 300
/// the height would be 200.
pub bottom: i32,
}
bitflags! {
/// Request information for [`open_projector`](crate::client::General::open_projector) as part of
/// [`Projector`].
#[derive(Default)]
pub struct QtWindowState: u32 {
/// Window with maximum size, taking up as much space as possible but still showing
/// the window frame.
const MAXIMIZED = 2;
/// Show the window in fullscreen mode, taking up the whole display.
const FULLSCREEN = 4;
}
}
impl QtWindowState {
/// Convert the state into a byte array for usage in [`QtGeometry::serialize`] .
fn to_be_bytes(&self) -> [u8; 2] {
[
if self.contains(Self::MAXIMIZED) { 1 } else { 0 },
if self.contains(Self::FULLSCREEN) {
1
} else {
0
},
]
}
}
/// Request information for [`open_projector`](crate::client::General::open_projector) as part of
/// [`Projector`].
#[derive(Debug)]
pub struct QtGeometry {
/// The screen number to display a widget or [`Self::DEFAULT_SCREEN`] to let OBS pick the
/// default.
pub screen_number: i32,
/// Additional window state like maximized or fullscreen.
pub window_state: QtWindowState,
/// The width of the screen. Seems to have no specific effect but is used for some internal
/// calculations in Qt.
pub screen_width: i32,
/// The target position and size for a widget to display at.
pub rect: QtRect,
}
impl QtGeometry {
/// Value indicating to use the default screen.
pub const DEFAULT_SCREEN: i32 = -1;
/// Create a new geometry instance without only size information set.
pub fn new(rect: QtRect) -> Self {
Self {
rect,
..Self::default()
}
}
/// Serialize this instance into a base64 encoded byte array.
///
/// The exact format can be found in the
/// [Qt source code](https://code.woboq.org/qt5/qtbase/src/widgets/kernel/qwidget.cpp.html#_ZNK7QWidget12saveGeometryEv).
///
/// | Length | Content |
/// |--------|----------------------------------------------------|
/// | 4 | Magic number |
/// | 2 | Major format version |
/// | 2 | Minor format version |
/// | 16 | Frame rect (lef, top, right, bottom) 4 bytes each |
/// | 16 | Normal rect (lef, top, right, bottom) 4 bytes each |
/// | 4 | Screen number |
/// | 1 | Window maximized (1 or 0) |
/// | 1 | Window fullscreen (1 or 0) |
/// | 4 | Screen width |
/// | 16 | Main rect (lef, top, right, bottom) 4 bytes each |
pub(crate) fn serialize(&self) -> String {
/// Indicator for serialized Qt geometry data.
const MAGIC_NUMBER: u32 = 0x1D9D0CB;
/// Major version of this format.
const MAJOR_VERSION: u16 = 3;
/// Minor version of this format.
const MINOR_VERSION: u16 = 0;
/// Output data length BEFORE base64 encoding. This allows to reduce allocations in the
/// byte buffer and must be updated whenever the format changes.
const DATA_LENGTH: usize = 66;
fn serialize_rect(data: &mut Vec<u8>, rect: &QtRect) {
data.extend(&rect.left.to_be_bytes());
data.extend(&rect.top.to_be_bytes());
data.extend(&rect.right.to_be_bytes());
data.extend(&rect.bottom.to_be_bytes());
}
let mut data = Vec::<u8>::with_capacity(DATA_LENGTH);
data.extend(&MAGIC_NUMBER.to_be_bytes());
data.extend(&MAJOR_VERSION.to_be_bytes());
data.extend(&MINOR_VERSION.to_be_bytes());
serialize_rect(&mut data, &self.rect); // frame geometry
serialize_rect(&mut data, &self.rect); // normal geometry
data.extend(&self.screen_number.to_be_bytes());
data.extend(&self.window_state.to_be_bytes());
data.extend(&self.screen_width.to_be_bytes());
serialize_rect(&mut data, &self.rect);
base64::encode(data)
}
}
impl Default for QtGeometry {
fn default() -> Self {
Self {
screen_number: Self::DEFAULT_SCREEN,
window_state: QtWindowState::default(),
screen_width: 0,
rect: QtRect::default(),
}
}
}

@ -1,7 +1,7 @@
#![cfg(feature = "test-integration")]
use anyhow::Result;
use obws::requests::{Projector, ProjectorType};
use obws::requests::{Projector, ProjectorType, QtGeometry, QtRect};
use serde_json::json;
mod common;
@ -31,6 +31,12 @@ async fn main() -> Result<()> {
client
.open_projector(Projector {
ty: Some(ProjectorType::Multiview),
geometry: Some(&QtGeometry::new(QtRect {
left: 100,
top: 100,
right: 300,
bottom: 300,
})),
..Default::default()
})
.await?;

Loading…
Cancel
Save