Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion native/opennow-streamer/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub trait NativeStreamerBackend {
fn send_input(&mut self, command: CommandEnvelope) -> BackendReply;
fn update_render_surface(&mut self, command: CommandEnvelope) -> BackendReply;
fn update_bitrate_limit(&mut self, command: CommandEnvelope) -> BackendReply;
fn update_shortcuts(&mut self, command: CommandEnvelope) -> BackendReply;
fn stop(&mut self, command: CommandEnvelope) -> BackendReply;
}

Expand Down Expand Up @@ -460,6 +461,11 @@ impl NativeStreamerBackend for StubBackend {
}
}

fn update_shortcuts(&mut self, command: CommandEnvelope) -> BackendReply {
// Stub backend has no native window; accept the command without applying it.
BackendReply::response(Response::Ok { id: command.id })
}

fn stop(&mut self, command: CommandEnvelope) -> BackendReply {
self.active_context = None;
let message = command
Expand Down Expand Up @@ -492,7 +498,7 @@ fn preferred_hevc_profile_id(color_quality: ColorQuality) -> u8 {
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::{ColorQuality, SessionInfo, StreamSettings};
use crate::protocol::{ColorQuality, NativeStreamerShortcutBindings, SessionInfo, StreamSettings};

fn context(resolution: &str) -> NativeStreamerSessionContext {
NativeStreamerSessionContext {
Expand All @@ -516,6 +522,7 @@ mod tests {
enable_cloud_gsync: false,
native_transition_diagnostics: None,
},
shortcuts: NativeStreamerShortcutBindings::default(),
}
}

Expand Down
15 changes: 15 additions & 0 deletions native/opennow-streamer/src/gstreamer_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::gstreamer_config::{
resolve_d3d_fullscreen_sink, resolve_present_max_fps, NATIVE_D3D_FULLSCREEN_ENV,
NATIVE_PRESENT_MAX_FPS_ENV, PRESENT_LIMITER_AUTO_SENTINEL,
};
use crate::gstreamer_platform::{clear_native_shortcut_bindings, set_native_shortcut_bindings};
use crate::gstreamer_pipeline::{
current_platform_label, init_gstreamer, native_video_backend_capabilities, GstreamerPipeline,
};
Expand Down Expand Up @@ -124,6 +125,7 @@ impl NativeStreamerBackend for GstreamerBackend {
}
}

set_native_shortcut_bindings(&context.shortcuts);
self.active_context = Some(context);
self.pending_remote_ice.clear();
self.remote_description_set = false;
Expand Down Expand Up @@ -192,6 +194,7 @@ impl NativeStreamerBackend for GstreamerBackend {

let present_max_fps = resolve_present_max_fps(context.settings.fps);
let d3d_fullscreen_sink = resolve_d3d_fullscreen_sink(context.settings.enable_cloud_gsync);
set_native_shortcut_bindings(&context.shortcuts);
pipeline.set_present_max_fps(present_max_fps);
pipeline.set_d3d_fullscreen_sink(d3d_fullscreen_sink);
pipeline.configure_stats(&context, prepared.nvst_params.max_bitrate_kbps);
Expand Down Expand Up @@ -373,10 +376,22 @@ impl NativeStreamerBackend for GstreamerBackend {
}
}

fn update_shortcuts(&mut self, command: CommandEnvelope) -> BackendReply {
let Some(shortcuts) = command.shortcuts else {
return BackendReply::response(missing_field(&command.id, "shortcuts"));
};
set_native_shortcut_bindings(&shortcuts);
if let Some(context) = self.active_context.as_mut() {
context.shortcuts = shortcuts;
}
BackendReply::response(Response::Ok { id: command.id })
}

fn stop(&mut self, command: CommandEnvelope) -> BackendReply {
self.active_context = None;
self.pending_remote_ice.clear();
self.remote_description_set = false;
clear_native_shortcut_bindings();
if let Some(pipeline) = self.pipeline.take() {
if let Err(message) = pipeline.stop() {
return BackendReply {
Expand Down
41 changes: 38 additions & 3 deletions native/opennow-streamer/src/gstreamer_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use crate::input::{
GamepadInput, KeyboardPayload, MouseButtonPayload, MouseMovePayload, MouseWheelPayload,
GAMEPAD_MAX_CONTROLLERS, PARTIALLY_RELIABLE_GAMEPAD_MASK_ALL,
};
#[cfg(target_os = "windows")]
use crate::protocol::NativeStreamerShortcutAction;
use crate::protocol::Event;
use gst::glib;
use gst::prelude::*;
Expand Down Expand Up @@ -98,6 +100,9 @@ impl GstreamerInputState {
#[cfg(target_os = "windows")]
#[derive(Debug, Clone, Copy)]
pub(crate) enum NativeWindowInputEvent {
Shortcut {
action: NativeStreamerShortcutAction,
},
Key {
pressed: bool,
keycode: u16,
Expand Down Expand Up @@ -321,6 +326,7 @@ impl NativeWindowInputBridge {
send_native_window_input_events(
&input_thread_state,
&input_thread_channels,
&thread_sender,
&pending_events,
);
if disconnected {
Expand Down Expand Up @@ -377,9 +383,31 @@ impl Drop for NativeWindowInputBridge {
fn send_native_window_input_events(
input_state: &GstreamerInputState,
input_channels: &GstreamerInputChannels,
event_sender: &Option<Sender<Event>>,
events: &[NativeWindowInputEvent],
) {
if events.is_empty() || !input_state.ready.load(Ordering::SeqCst) {
if events.is_empty() {
return;
}

// Forward shortcuts immediately (before input readiness check)
// Shortcuts are local control and don't need the stream channel
let mut other_events = Vec::new();
for event in events.iter().copied() {
match event {
NativeWindowInputEvent::Shortcut { action } => {
if let Some(sender) = event_sender.as_ref() {
let _ = sender.send(Event::Shortcut { action });
}
}
_ => {
other_events.push(event);
}
}
}

// Only process non-shortcut events if input is ready
if other_events.is_empty() || !input_state.ready.load(Ordering::SeqCst) {
return;
}

Expand All @@ -388,7 +416,7 @@ fn send_native_window_input_events(
};

let mut pending_mouse_move: Option<(i32, i32, u64)> = None;
for event in events.iter().copied() {
for event in other_events.iter().copied() {
if let NativeWindowInputEvent::MouseMove {
dx,
dy,
Expand All @@ -404,7 +432,7 @@ fn send_native_window_input_events(
}

flush_pending_mouse_move(&encoder, input_channels, &mut pending_mouse_move);
send_encoded_native_window_input_event(&encoder, input_channels, event);
send_encoded_native_window_input_event(&encoder, input_channels, event_sender, event);
}
flush_pending_mouse_move(&encoder, input_channels, &mut pending_mouse_move);
}
Expand Down Expand Up @@ -437,9 +465,16 @@ fn flush_pending_mouse_move(
fn send_encoded_native_window_input_event(
encoder: &InputEncoder,
input_channels: &GstreamerInputChannels,
event_sender: &Option<Sender<Event>>,
event: NativeWindowInputEvent,
) {
let (payload, partially_reliable) = match event {
NativeWindowInputEvent::Shortcut { action } => {
if let Some(sender) = event_sender.as_ref() {
let _ = sender.send(Event::Shortcut { action });
}
return;
}
NativeWindowInputEvent::Key {
pressed,
keycode,
Expand Down
Loading
Loading