Skip to content

Commit

Permalink
feat: windows支持app_name
Browse files Browse the repository at this point in the history
  • Loading branch information
nashaofu committed Jan 29, 2024
1 parent afc7b46 commit 9decf3a
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 33 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ keywords = ["screen", "monitor", "window", "capture", "image"]

[dependencies]
image = "0.24"
log = "0.4"
thiserror = "1.0"

[target.'cfg(target_os = "macos")'.dependencies]
Expand All @@ -26,6 +27,9 @@ windows = { version = "0.52", features = [
"Win32_Graphics_Dwm",
"Win32_UI_WindowsAndMessaging",
"Win32_Storage_Xps",
"Win32_System_Threading",
"Win32_System_ProcessStatus",
"Win32_Storage_FileSystem",
] }

[target.'cfg(target_os="linux")'.dependencies]
Expand Down
5 changes: 3 additions & 2 deletions examples/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ fn main() {

for monitor in monitors {
println!(
"Monitor: {} {} {:?} {:?}",
"Monitor:\n id: {}\n name: {}\n position: {:?}\n size: {:?}\n state:{:?}\n",
monitor.id(),
monitor.name(),
(monitor.x(), monitor.y(), monitor.width(), monitor.height()),
(monitor.x(), monitor.y()),
(monitor.width(), monitor.height()),
(
monitor.rotation(),
monitor.scale_factor(),
Expand Down
5 changes: 3 additions & 2 deletions examples/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ fn main() {

for window in windows {
println!(
"Window: {} {} {} {:?} {:?} {:?}",
"Window:\n id: {}\n title: {}\n app_name: {}\n monitor: {:?}\n position: {:?}\n size {:?}\n state {:?}\n",
window.id(),
window.title(),
window.app_name(),
window.current_monitor().name(),
(window.x(), window.y(), window.width(), window.height()),
(window.x(), window.y()),
(window.width(), window.height()),
(window.is_minimized(), window.is_maximized())
);
}
Expand Down
13 changes: 7 additions & 6 deletions src/utils/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ use image::RgbaImage;
use crate::error::{XCapError, XCapResult};

pub fn vec_to_rgba_image(width: u32, height: u32, buf: Vec<u8>) -> XCapResult<RgbaImage> {
RgbaImage::from_vec(width, height, buf).ok_or_else(|| XCapError::new("buffer not big enough"))
RgbaImage::from_raw(width, height, buf)
.ok_or_else(|| XCapError::new("RgbaImage::from_raw failed"))
}

#[cfg(any(target_os = "windows", target_os = "macos", test))]
pub fn bgra_to_rgba_image(width: u32, height: u32, buf: Vec<u8>) -> XCapResult<RgbaImage> {
let mut rgba_buf = buf.clone();

for (src, dst) in buf.chunks_exact(4).zip(rgba_buf.chunks_exact_mut(4)) {
dst[0] = src[2];
dst[1] = src[1];
dst[2] = src[0];
dst[3] = 255;
for src in rgba_buf.chunks_exact_mut(4) {
src.swap(0, 2);
// fix https://github.com/nashaofu/xcap/issues/92#issuecomment-1910014951
src[3] = 255;
}

vec_to_rgba_image(width, height, rgba_buf)
}

Expand Down
50 changes: 46 additions & 4 deletions src/windows/boxed.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use log::error;
use std::{ops::Deref, ptr};
use windows::{
core::PCWSTR,
Win32::{
Foundation::HWND,
Foundation::{CloseHandle, HANDLE, HWND},
Graphics::Gdi::{CreateDCW, DeleteDC, DeleteObject, GetWindowDC, ReleaseDC, HBITMAP, HDC},
System::Threading::{OpenProcess, PROCESS_ACCESS_RIGHTS},
},
};

use crate::XCapResult;

#[derive(Debug)]
pub(super) struct BoxHDC {
hdc: HDC,
hwnd: Option<HWND>,
Expand All @@ -25,9 +30,13 @@ impl Drop for BoxHDC {
// https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-releasedc
unsafe {
if let Some(hwnd) = self.hwnd {
ReleaseDC(hwnd, self.hdc);
if ReleaseDC(hwnd, self.hdc) != 1 {
error!("ReleaseDC {:?} failed", self)
}
} else {
DeleteDC(self.hdc);
if !DeleteDC(self.hdc).as_bool() {
error!("DeleteDC {:?} failed", self)
}
}
};
}
Expand Down Expand Up @@ -66,6 +75,7 @@ impl From<HWND> for BoxHDC {
}
}

#[derive(Debug)]
pub(super) struct BoxHBITMAP(HBITMAP);

impl Deref for BoxHBITMAP {
Expand All @@ -79,7 +89,9 @@ impl Drop for BoxHBITMAP {
fn drop(&mut self) {
// https://learn.microsoft.com/zh-cn/windows/win32/api/wingdi/nf-wingdi-createcompatiblebitmap
unsafe {
DeleteObject(self.0);
if !DeleteObject(self.0).as_bool() {
error!("DeleteObject {:?} failed", self)
}
};
}
}
Expand All @@ -89,3 +101,33 @@ impl BoxHBITMAP {
BoxHBITMAP(h_bitmap)
}
}

#[derive(Debug)]
pub(super) struct BoxProcessHandle(HANDLE);

impl Deref for BoxProcessHandle {
type Target = HANDLE;
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl Drop for BoxProcessHandle {
fn drop(&mut self) {
unsafe {
CloseHandle(self.0).unwrap_or_else(|_| error!("CloseHandle {:?} failed", self));
};
}
}

impl BoxProcessHandle {
pub fn open(
dw_desired_access: PROCESS_ACCESS_RIGHTS,
b_inherit_handle: bool,
dw_process_id: u32,
) -> XCapResult<Self> {
let h_process = unsafe { OpenProcess(dw_desired_access, b_inherit_handle, dw_process_id)? };

Ok(BoxProcessHandle(h_process))
}
}
136 changes: 123 additions & 13 deletions src/windows/impl_window.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
use core::slice;
use image::RgbaImage;
use std::{ffi::c_void, mem};
use windows::Win32::{
Foundation::{BOOL, HWND, LPARAM, TRUE},
Graphics::{
Dwm::{DwmGetWindowAttribute, DWMWA_CLOAKED, DWM_CLOAKED_SHELL},
Gdi::{MonitorFromWindow, MONITOR_DEFAULTTONEAREST},
},
UI::WindowsAndMessaging::{
EnumWindows, GetAncestor, GetLastActivePopup, GetWindowInfo, GetWindowLongW,
GetWindowTextLengthW, GetWindowTextW, IsIconic, IsWindowVisible, IsZoomed, GA_ROOTOWNER,
GWL_EXSTYLE, WINDOWINFO, WINDOW_EX_STYLE, WS_EX_TOOLWINDOW,
use std::{ffi::c_void, mem, ptr};
use windows::{
core::{HSTRING, PCWSTR},
Win32::{
Foundation::{BOOL, HMODULE, HWND, LPARAM, MAX_PATH, TRUE},
Graphics::{
Dwm::{DwmGetWindowAttribute, DWMWA_CLOAKED, DWM_CLOAKED_SHELL},
Gdi::{MonitorFromWindow, MONITOR_DEFAULTTONEAREST},
},
Storage::FileSystem::{GetFileVersionInfoSizeW, GetFileVersionInfoW, VerQueryValueW},
System::{
ProcessStatus::{GetModuleBaseNameW, GetModuleFileNameExW},
Threading::PROCESS_QUERY_LIMITED_INFORMATION,
},
UI::WindowsAndMessaging::{
EnumWindows, GetAncestor, GetLastActivePopup, GetWindowInfo, GetWindowLongW,
GetWindowTextLengthW, GetWindowTextW, GetWindowThreadProcessId, IsIconic,
IsWindowVisible, IsZoomed, GA_ROOTOWNER, GWL_EXSTYLE, WINDOWINFO, WINDOW_EX_STYLE,
WS_EX_TOOLWINDOW,
},
},
};

use crate::error::XCapResult;
use crate::{error::XCapResult, platform::boxed::BoxProcessHandle};

use super::{capture::capture_window, impl_monitor::ImplMonitor, utils::wide_string_to_string};

Expand Down Expand Up @@ -103,6 +113,104 @@ unsafe extern "system" fn enum_windows_proc(hwnd: HWND, state: LPARAM) -> BOOL {
TRUE
}

#[derive(Debug, Default)]
struct LangCodePage {
pub w_language: u16,
pub w_code_page: u16,
}

fn get_app_name(hwnd: HWND) -> XCapResult<String> {
unsafe {
let mut lp_dw_process_id = 0;
GetWindowThreadProcessId(hwnd, Some(&mut lp_dw_process_id));

let box_process_handle =
BoxProcessHandle::open(PROCESS_QUERY_LIMITED_INFORMATION, false, lp_dw_process_id)?;

let mut filename = [0; MAX_PATH as usize];
GetModuleFileNameExW(*box_process_handle, HMODULE::default(), &mut filename);

let pcw_filename = PCWSTR::from_raw(filename.as_ptr());

let file_version_info_size_w = GetFileVersionInfoSizeW(pcw_filename, None);

let mut file_version_info = vec![0_u16; file_version_info_size_w as usize];

GetFileVersionInfoW(
pcw_filename,
0,
file_version_info_size_w,
file_version_info.as_mut_ptr().cast(),
)?;

let mut lang_code_pages_ptr = ptr::null_mut();
let mut lang_code_pages_length = 0;

VerQueryValueW(
file_version_info.as_ptr().cast(),
&HSTRING::from("\\VarFileInfo\\Translation"),
&mut lang_code_pages_ptr,
&mut lang_code_pages_length,
)
.ok()?;

let lang_code_pages: &[LangCodePage] =
slice::from_raw_parts(lang_code_pages_ptr.cast(), lang_code_pages_length as usize);

// 按照 keys 的顺序读取文件的属性值
// 优先读取 ProductName
let keys = [
"ProductName",
"FileDescription",
"ProductShortName",
"InternalName",
"OriginalFilename",
];

for key in keys {
for lang_code_page in lang_code_pages {
let query_key = HSTRING::from(format!(
"\\StringFileInfo\\{:04x}{:04x}\\{}",
lang_code_page.w_language, lang_code_page.w_code_page, key
));

let mut value_ptr = ptr::null_mut();
let mut value_length: u32 = 0;

let is_success = VerQueryValueW(
file_version_info.as_ptr().cast(),
&query_key,
&mut value_ptr,
&mut value_length,
)
.as_bool();

if !is_success {
continue;
}

let value = slice::from_raw_parts(value_ptr.cast(), value_length as usize);
let attr = wide_string_to_string(value)?;
let attr = attr.trim();

if !attr.trim().is_empty() {
return Ok(attr.to_string());
}
}
}

// 默认使用 module_basename
let mut module_base_name_w = [0; MAX_PATH as usize];
GetModuleBaseNameW(
*box_process_handle,
HMODULE::default(),
&mut module_base_name_w,
);

wide_string_to_string(&module_base_name_w)
}
}

impl ImplWindow {
fn new(hwnd: HWND) -> XCapResult<ImplWindow> {
unsafe {
Expand All @@ -118,6 +226,8 @@ impl ImplWindow {
wide_string_to_string(&wide_buffer)?
};

let app_name = get_app_name(hwnd)?;

let hmonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
let rc_client = window_info.rcClient;
let is_minimized = IsIconic(hwnd).as_bool();
Expand All @@ -128,7 +238,7 @@ impl ImplWindow {
window_info,
id: hwnd.0 as u32,
title,
app_name: String::from("Unsupported"),
app_name,
current_monitor: ImplMonitor::new(hmonitor)?,
x: rc_client.left,
y: rc_client.top,
Expand Down
13 changes: 7 additions & 6 deletions src/windows/utils.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::error::{XCapError, XCapResult};
use crate::error::XCapResult;

pub(super) fn wide_string_to_string(wide_string: &[u16]) -> XCapResult<String> {
if let Some(null_pos) = wide_string.iter().position(|pos| *pos == 0) {
let string = String::from_utf16(&wide_string[..null_pos])?;
return Ok(string);
}
let string = if let Some(null_pos) = wide_string.iter().position(|pos| *pos == 0) {
String::from_utf16(&wide_string[..null_pos])?
} else {
String::from_utf16(&wide_string)?
};

Err(XCapError::new("Convert wide string to string failed"))
Ok(string.trim().to_string())
}

0 comments on commit 9decf3a

Please sign in to comment.