Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion crates/chatd/src/adapters/tiny/live.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1162,7 +1162,7 @@ fn heard_frame(ctx: &Context, saved: &Saved, live: &Live, seat: &mut Seat, pendi
who.heard(frame);
let reread = who.reread(&before);
if user == seat.me && before.sharing && !who.sharing {
ctx.tell_media(room, MediaRequest::ShareSend { on: false });
ctx.tell_media(room, MediaRequest::ShareSend { on: false, screen: None });
}
if user == seat.me && before.camera && !who.camera {
ctx.tell_media(room, MediaRequest::Camera { on: false });
Expand Down
6 changes: 3 additions & 3 deletions crates/chatd/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,17 +584,17 @@ fn mediad_exits_when_chatd_is_killed_mid_call() {
fn a_share_is_one_encode_and_one_subscribed_decode() {
let chatd = Daemon::start(standup);

let no_call = chatd.ask(Request::Media(MediaRequest::ShareSend { on: true }));
let no_call = chatd.ask(Request::Media(MediaRequest::ShareSend { on: true, screen: None }));
assert!(matches!(no_call, Response::Error { code: ErrorCode::NoCall, .. }), "{no_call:?}");
assert_eq!(chatd.mediad_pids(), Vec::<u32>::new());

chatd.ask(call_start());
let watch = |source: &str| Request::Media(MediaRequest::ShareWatch { source: Some(source.into()) });
let speaker = |source: &str| Request::Media(MediaRequest::Speaker { source: Some(source.into()) });
chatd.ask(Request::Media(MediaRequest::ShareSend { on: true }));
chatd.ask(Request::Media(MediaRequest::ShareSend { on: true, screen: None }));
chatd.ask(watch("alice"));
chatd.ask(speaker("carol"));
chatd.ask(Request::Media(MediaRequest::ShareSend { on: true }));
chatd.ask(Request::Media(MediaRequest::ShareSend { on: true, screen: None }));
chatd.ask(speaker("dave"));
let last = chatd.ask(watch("bob"));

Expand Down
5 changes: 3 additions & 2 deletions crates/mediad/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ webpki-roots = "1"

[target.'cfg(windows)'.dependencies]
# MMCSS scheduling for the device callbacks and the call's tick, and the 1 ms system timer a
# call's 20 ms tick needs (`rt`). Already in the tree through cpal and mio.
windows-sys = { version = "0.61", features = ["Win32_Media", "Win32_System_Threading", "Win32_Foundation", "Win32_Graphics_Gdi", "Win32_UI_WindowsAndMessaging"] }
# call's 20 ms tick needs (`rt`), and the screens and windows a share can send (`picture/screens`).
# Already in the tree through cpal and mio.
windows-sys = { version = "0.61", features = ["Win32_Media", "Win32_System_Threading", "Win32_Foundation", "Win32_Graphics_Gdi", "Win32_Graphics_Dwm", "Win32_Storage_Xps", "Win32_UI_HiDpi", "Win32_UI_WindowsAndMessaging"] }
# The first camera, through Media Foundation. Pure Rust bindings. A machine with no camera
# says so, and the call stays a voice call.
nokhwa = { version = "0.10.10", default-features = false, features = ["input-native"] }
Expand Down
5 changes: 5 additions & 0 deletions crates/mediad/src/picture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ pub const CODEC_LATER: &str = "OpenH264 is still downloading.";
mod desktop;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub use desktop::*;
/// What a share sends: the picker's list and the one chosen.
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod screens;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub use screens::{choose_screen, screens};
pub mod convert;
/// Built everywhere, so host tests drive it with `platform::fake`.
pub mod phone;
Expand Down
61 changes: 11 additions & 50 deletions crates/mediad/src/picture/desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,12 +261,21 @@ impl Camera {
}
}

/// The primary screen as RGBA or BGRA. The whole screen, which the share then fits.
/// The screen or window chosen for the share ([`super::choose_screen`]), else the primary
/// screen, as RGBA or BGRA. The whole of it, which the share then fits.
pub fn capture_screen() -> Result<Rgba, String> {
capture_source(super::screens::chosen().as_deref())
}

/// One screen or window by its `--screens` id. Only Windows tells them apart; elsewhere every id
/// is the primary screen.
pub(super) fn capture_source(id: Option<&str>) -> Result<Rgba, String> {
#[cfg(windows)]
{
return capture_screen_windows();
return super::screens::capture(id);
}
#[cfg(not(windows))]
let _ = id;
#[cfg(target_os = "macos")]
{
return capture_screen_macos();
Expand All @@ -281,54 +290,6 @@ pub fn capture_screen() -> Result<Rgba, String> {
}
}

#[cfg(windows)]
fn capture_screen_windows() -> Result<Rgba, String> {
use std::mem::size_of;
use windows_sys::Win32::Graphics::Gdi::{
BitBlt, CreateCompatibleBitmap, CreateCompatibleDC, DeleteDC, DeleteObject, GetDC, GetDIBits, ReleaseDC, SelectObject, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, SRCCOPY,
};
use windows_sys::Win32::UI::WindowsAndMessaging::{GetDesktopWindow, GetSystemMetrics, SM_CXSCREEN, SM_CYSCREEN};
unsafe {
let width = GetSystemMetrics(SM_CXSCREEN);
let height = GetSystemMetrics(SM_CYSCREEN);
if width <= 0 || height <= 0 {
return Err("The screen has no size.".to_owned());
}
let desktop = GetDesktopWindow();
let dc = GetDC(desktop);
if dc.is_null() {
return Err("tinyclient can't record the screen.".to_owned());
}
let memory = CreateCompatibleDC(dc);
let bitmap = CreateCompatibleBitmap(dc, width, height);
let previous = SelectObject(memory, bitmap as _);
let copied = BitBlt(memory, 0, 0, width, height, dc, 0, 0, SRCCOPY);
let mut header = BITMAPINFOHEADER {
biSize: size_of::<BITMAPINFOHEADER>() as u32,
biWidth: width,
biHeight: -height,
biPlanes: 1,
biBitCount: 32,
biCompression: BI_RGB,
biSizeImage: 0,
biXPelsPerMeter: 0,
biYPelsPerMeter: 0,
biClrUsed: 0,
biClrImportant: 0,
};
let mut pixels = vec![0u8; (width * height * 4) as usize];
let rows = GetDIBits(memory, bitmap, 0, height as u32, pixels.as_mut_ptr().cast(), (&mut header as *mut BITMAPINFOHEADER).cast(), DIB_RGB_COLORS);
SelectObject(memory, previous);
DeleteObject(bitmap as _);
DeleteDC(memory);
ReleaseDC(desktop, dc);
if copied == 0 || rows == 0 {
return Err("tinyclient can't record the screen.".to_owned());
}
Ok(Rgba { width: width as u32, height: height as u32, pixels, bgra: true })
}
}

#[cfg(target_os = "macos")]
fn capture_screen_macos() -> Result<Rgba, String> {
use core_graphics::display::CGDisplay;
Expand Down
8 changes: 8 additions & 0 deletions crates/mediad/src/picture/phone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,14 @@ impl Incoming {
}
}

/// A phone's share is the system's own picker's: nothing here to choose.
pub fn choose_screen(_id: Option<String>) {}

/// A phone lists nothing: the system asks what to share.
pub fn screens(_folder: Option<&std::path::Path>) -> Vec<protocol::ScreenSource> {
Vec::new()
}

/// Whether the screen can be shared, before the button claims it: a platform that shares.
pub fn probe_screen() -> Result<(), String> {
platform::required().map(|_| ())
Expand Down
Loading
Loading