| // Tests `desktop` by creating a new desktop, spawning a child process onto it |
| // and checking that the child reports back the expected desktop name. |
| |
| //@ run-pass |
| //@ only-windows |
| //@ needs-subprocess |
| //@ edition: 2024 |
| |
| #![feature(windows_process_extensions_desktop)] |
| |
| use std::os::windows::process::CommandExt; |
| use std::process::{Command, Stdio}; |
| use std::{env, io, process}; |
| |
| fn main() { |
| if env::args().skip(1).any(|s| s == "--child") { |
| child(); |
| } else { |
| parent(); |
| } |
| } |
| |
| fn parent() { |
| let exe = env::current_exe().unwrap(); |
| |
| // Create a uniquely named desktop on the current window station and keep the |
| // handle alive so the desktop is not destroyed while the child runs. |
| let desktop_name = format!("rust-test-desktop-{}", process::id()); |
| let desktop_name_wide: Vec<u16> = desktop_name.encode_utf16().chain([0]).collect(); |
| let hdesk = unsafe { |
| CreateDesktopW( |
| desktop_name_wide.as_ptr(), |
| core::ptr::null(), |
| core::ptr::null(), |
| 0, |
| GENERIC_ALL, |
| core::ptr::null(), |
| ) |
| }; |
| assert!(!hdesk.is_null(), "CreateDesktopW failed: {:?}", io::Error::last_os_error()); |
| |
| // Spawning with `.desktop` should place the child on our new desktop. |
| let output = Command::new(&exe) |
| .arg("--child") |
| .desktop(&desktop_name) |
| .stdout(Stdio::piped()) |
| .output() |
| .unwrap(); |
| assert!(output.status.success(), "child failed: {:?}", output); |
| let reported = String::from_utf8(output.stdout).unwrap(); |
| assert!( |
| reported.trim().eq_ignore_ascii_case(&desktop_name), |
| "child ran on unexpected desktop: expected {:?}, got {:?}", |
| desktop_name, |
| reported.trim(), |
| ); |
| |
| // Without `.desktop` the child inherits the parent's desktop, which is |
| // not the one we just created. |
| let output = Command::new(&exe).arg("--child").stdout(Stdio::piped()).output().unwrap(); |
| assert!(output.status.success(), "child failed: {:?}", output); |
| let reported = String::from_utf8(output.stdout).unwrap(); |
| assert!( |
| !reported.trim().eq_ignore_ascii_case(&desktop_name), |
| "child unexpectedly ran on the created desktop {:?} without being asked to", |
| desktop_name, |
| ); |
| |
| unsafe { CloseDesktop(hdesk) }; |
| } |
| |
| /// Prints the name of the desktop the current process is running on. |
| fn child() { |
| let hdesk = unsafe { GetThreadDesktop(GetCurrentThreadId()) }; |
| assert!(!hdesk.is_null(), "GetThreadDesktop failed: {:?}", io::Error::last_os_error()); |
| |
| let mut buffer = [0u16; 256]; |
| let mut needed = 0u32; |
| let ret = unsafe { |
| GetUserObjectInformationW( |
| hdesk, |
| UOI_NAME, |
| buffer.as_mut_ptr().cast(), |
| size_of_val(&buffer) as u32, |
| &raw mut needed, |
| ) |
| }; |
| assert_ne!(ret, 0, "GetUserObjectInformationW failed: {:?}", io::Error::last_os_error()); |
| |
| let len = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len()); |
| let name = String::from_utf16(&buffer[..len]).unwrap(); |
| print!("{name}"); |
| } |
| |
| // Windows API |
| mod winapi { |
| use std::ffi::c_void; |
| use std::os::windows::raw::HANDLE; |
| |
| pub const GENERIC_ALL: u32 = 0x10000000; |
| pub const UOI_NAME: i32 = 2; |
| |
| #[link(name = "user32")] |
| unsafe extern "system" { |
| pub fn CreateDesktopW( |
| lpszDesktop: *const u16, |
| lpszDevice: *const u16, |
| pDevmode: *const c_void, |
| dwFlags: u32, |
| dwDesiredAccess: u32, |
| lpsa: *const c_void, |
| ) -> HANDLE; |
| pub fn CloseDesktop(hDesktop: HANDLE) -> i32; |
| pub fn GetThreadDesktop(dwThreadId: u32) -> HANDLE; |
| pub fn GetUserObjectInformationW( |
| hObj: HANDLE, |
| nIndex: i32, |
| pvInfo: *mut c_void, |
| nLength: u32, |
| lpnLengthNeeded: *mut u32, |
| ) -> i32; |
| } |
| |
| #[link(name = "kernel32")] |
| unsafe extern "system" { |
| pub fn GetCurrentThreadId() -> u32; |
| } |
| } |
| use winapi::*; |