Skip to content

Commit d8aeefd

Browse files
committed
Draggable Overlay
1 parent 8cdd72e commit d8aeefd

File tree

1 file changed

+142
-31
lines changed

1 file changed

+142
-31
lines changed

src/overlay.rs

Lines changed: 142 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,92 @@
1-
use crate::Config;
2-
use crate::Media;
1+
use crate::{Config, Media};
32
use clap::Arg;
3+
use num_rational::Ratio;
4+
use std::{sync::Arc, thread, time};
5+
46
use gstreamer::{
57
caps::Caps, event, message::MessageView, prelude::*, BusSyncReply, Element, ElementFactory,
68
Pipeline, State,
79
};
810
use gstreamer_video::{prelude::VideoOverlayExtManual, VideoOverlay};
9-
use num_rational::Ratio;
10-
use std::{thread, time};
11+
1112
use x11rb::{
1213
connection::Connection,
13-
protocol::xproto::{
14-
ConnectionExt, // Trait
15-
CreateWindowAux,
16-
EventMask,
17-
WindowClass,
14+
protocol::{
15+
xproto::{
16+
ConfigureWindowAux,
17+
ConnectionExt, // Trait
18+
CreateWindowAux,
19+
EventMask,
20+
WindowClass,
21+
},
22+
Event,
1823
},
1924
wrapper::ConnectionExt as ConnectionExtTrait,
2025
};
26+
27+
/* Utils */
28+
struct Dimension2D<T> {
29+
width: T,
30+
height: T,
31+
}
32+
33+
struct Coordinate2D<T> {
34+
x: T,
35+
y: T,
36+
}
37+
38+
impl<T> Dimension2D<T> {
39+
fn new(width: T, height: T) -> Self {
40+
Dimension2D::<T> { width, height }
41+
}
42+
}
43+
44+
impl<T> Coordinate2D<T> {
45+
fn new(x: T, y: T) -> Self {
46+
Coordinate2D::<T> { x, y }
47+
}
48+
}
49+
50+
#[allow(dead_code)] // todo: implement cli option for default overlay position
51+
enum InitialPosition {
52+
TopLeft,
53+
TopRight,
54+
BottomLeft,
55+
BottomRight,
56+
}
57+
58+
impl Default for InitialPosition {
59+
fn default() -> Self {
60+
InitialPosition::BottomRight
61+
}
62+
}
63+
64+
fn coordinates_for_initial_overlay(
65+
screen: &Dimension2D<u16>,
66+
window: &Dimension2D<u16>,
67+
padding: &Coordinate2D<u16>,
68+
position: InitialPosition,
69+
) -> Coordinate2D<i16> {
70+
match position {
71+
InitialPosition::TopLeft => Coordinate2D::<i16>::new(
72+
(screen.width + padding.x) as i16,
73+
(screen.height + padding.y) as i16,
74+
),
75+
InitialPosition::TopRight => Coordinate2D::<i16>::new(
76+
(screen.width - window.width - padding.x) as i16,
77+
(screen.height + padding.y) as i16,
78+
),
79+
InitialPosition::BottomLeft => Coordinate2D::<i16>::new(
80+
(screen.width + padding.x) as i16,
81+
(screen.height - window.height - padding.y) as i16,
82+
),
83+
InitialPosition::BottomRight => Coordinate2D::<i16>::new(
84+
(screen.width - window.width - padding.x) as i16,
85+
(screen.height - window.height - padding.y) as i16,
86+
),
87+
}
88+
}
89+
2190
pub fn create_arg<'a>() -> Arg<'a> {
2291
Arg::new("overlay")
2392
.long("overlay")
@@ -28,6 +97,8 @@ pub fn create_arg<'a>() -> Arg<'a> {
2897
}
2998

3099
pub fn default() -> bool {
100+
// todo: convert to default trait implementation
101+
// Could seperate overlay into overlay_pipeline and overlay_option
31102
true
32103
}
33104

@@ -62,7 +133,7 @@ impl Media for CameraPreview {
62133
fn stop_stream(&self) {
63134
match &self.pipeline {
64135
Some(pipeline) => {
65-
pipeline.send_event(event::Eos::new());
136+
pipeline.send_event(event::Eos::new()); // todo: add handler
66137
thread::sleep(time::Duration::from_secs(5)); // Hacky: but there should be a better way}
67138
pipeline
68139
.set_state(State::Null)
@@ -76,43 +147,43 @@ impl Media for CameraPreview {
76147

77148
fn cancel_stream(&self) {}
78149
fn create_pipeline(&mut self) {
79-
let rate = Ratio::new(self.config.framerate as i32, 1);
80-
const WIDTH: i32 = 400;
81-
// const WIDTH: i32 = 800;
82-
const HEIGHT: i32 = 300;
83-
// const HEIGHT: i32 = 600;
84-
const PADDING: i32 = 15;
150+
// let window_dimensions = Dimension2D::<u16>::new(800, 600);
151+
let window_dimensions = Dimension2D::<u16>::new(400, 300);
152+
let padding = Coordinate2D::<u16>::new(15, 15);
85153
const FORMAT: &str = "YUV2";
154+
let rate = Ratio::new(self.config.framerate as i32, 1);
155+
86156
/* Window creation */
87157
let (conn, screen_num) = x11rb::connect(None).unwrap();
158+
let conn = Arc::new(conn); // will be shared with gstreamer xvimagesink
88159
let screen = &conn.setup().roots[screen_num];
89160
let win_id = conn.generate_id().unwrap();
90161

91-
let screen_width = screen.width_in_pixels as u16;
92-
let screen_height = screen.height_in_pixels as u16;
93-
94-
let window_width = WIDTH as u16;
95-
let window_height = HEIGHT as u16;
162+
let screen_dimensions =
163+
Dimension2D::<u16>::new(screen.width_in_pixels, screen.height_in_pixels);
96164

97165
let win_aux = CreateWindowAux::new()
98166
.event_mask(
99-
EventMask::EXPOSURE
100-
| EventMask::STRUCTURE_NOTIFY
101-
| EventMask::BUTTON1_MOTION
102-
| EventMask::NO_EVENT,
167+
EventMask::BUTTON1_MOTION, // EventMask::STRUCTURE_NOTIFY // todo: implement resizing
103168
)
104169
.override_redirect(true as u32)
105170
.border_pixel(None)
106171
.background_pixel(screen.black_pixel);
107172

173+
let window_coordinates = coordinates_for_initial_overlay(
174+
&screen_dimensions,
175+
&window_dimensions,
176+
&padding,
177+
InitialPosition::default(),
178+
);
108179
conn.create_window(
109180
screen.root_depth,
110181
win_id,
111182
screen.root,
112-
(screen_width - window_width - (PADDING as u16)) as i16,
113-
(screen_height - window_height - (PADDING as u16)) as i16,
114-
window_width,
115-
window_height,
183+
window_coordinates.x,
184+
window_coordinates.y,
185+
window_dimensions.width,
186+
window_dimensions.height,
116187
0,
117188
WindowClass::INPUT_OUTPUT,
118189
0,
@@ -123,6 +194,8 @@ impl Media for CameraPreview {
123194
conn.map_window(win_id).unwrap();
124195

125196
/* Gstreamer pipline message handler */
197+
let conn1 = conn.clone();
198+
let win_id1 = win_id; // todo: figure out how to specify move, and referrence to variables in closures
126199
let sync_handler_closure = move |_bus: &gstreamer::Bus, msg: &gstreamer::Message| {
127200
match msg.view() {
128201
MessageView::Element(element) => {
@@ -133,9 +206,9 @@ impl Media for CameraPreview {
133206
.dynamic_cast::<VideoOverlay>()
134207
.unwrap();
135208

136-
conn.sync().unwrap();
209+
let _ = &conn1.sync().unwrap();
137210
unsafe {
138-
video_overlay.set_window_handle(win_id as _);
211+
video_overlay.set_window_handle(win_id1 as _);
139212
}
140213
BusSyncReply::Drop
141214
}
@@ -144,6 +217,44 @@ impl Media for CameraPreview {
144217
}
145218
};
146219

220+
/* X11 window event handler */
221+
// todo: add cleanup for join handle
222+
thread::spawn(move || {
223+
loop {
224+
let original_pointer_position =
225+
&conn.query_pointer(win_id).unwrap().reply().unwrap();
226+
227+
let new_window_coordinates = Coordinate2D::<i16>::new(
228+
original_pointer_position.root_x - original_pointer_position.win_x,
229+
original_pointer_position.root_y - original_pointer_position.win_y,
230+
);
231+
if let Ok(event) = &conn.wait_for_event() {
232+
match event {
233+
/* Dragging Window */
234+
Event::MotionNotify(motion_event) => {
235+
let deltax = original_pointer_position.win_x - motion_event.event_x;
236+
let deltay = original_pointer_position.win_y - motion_event.event_y;
237+
238+
// Hacky, but works
239+
if deltax.abs() < 75 && deltay.abs() < 75 {
240+
let mut new_attributes = ConfigureWindowAux::new();
241+
/* ConfigureWindowAux methods .x() and .y() do not work */
242+
new_attributes.x = Some((new_window_coordinates.x - deltax) as i32);
243+
new_attributes.y = Some((new_window_coordinates.y - deltay) as i32);
244+
245+
let _ = &conn.configure_window(win_id, &new_attributes).unwrap();
246+
247+
conn.map_window(win_id).unwrap();
248+
}
249+
}
250+
251+
_ => println!("Unwanted event recieved, please report this issue"),
252+
}
253+
};
254+
conn.flush().unwrap();
255+
}
256+
});
257+
147258
/* Pipeline creation */
148259
gstreamer::init().expect("cannot start gstreamer");
149260
let main_pipeline = Pipeline::new(Some("test-pipeline"));

0 commit comments

Comments
 (0)