Skip to content

Commit df6bf87

Browse files
committed
Show a preview of the desktop
1 parent 0f1a220 commit df6bf87

File tree

5 files changed

+175
-31
lines changed

5 files changed

+175
-31
lines changed

Cargo.lock

Lines changed: 53 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

injector/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ egui_extras = "0.32.0"
1212
image = { version = "0.25.6", default-features = false, features = ["ico"]}
1313
wgpu = { version = "25.0.0", default-features = true }
1414
windows = { version = "0.61.3", features = ["Win32_UI_WindowsAndMessaging" , "Win32_Graphics_Dwm", "Win32_System_Console"] }
15+
windows-capture = "1.5.0"
1516

1617
[build-dependencies]
1718
winresource = "0.1.23"

injector/src/gui.rs

Lines changed: 108 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,54 @@
11
use crate::injector::{self, WindowInfo};
22
use eframe::{
33
Renderer,
4-
egui::{self, IconData},
4+
egui::{self, ColorImage, IconData},
55
};
66
use image::{GenericImageView, ImageFormat, ImageReader};
77
use std::sync::{Arc, Mutex};
88
use std::thread;
99
use std::{io::Cursor, mem};
10+
use windows_capture::capture::{Context, GraphicsCaptureApiHandler};
11+
use windows_capture::frame::Frame;
12+
use windows_capture::monitor::Monitor;
13+
use windows_capture::settings::{
14+
ColorFormat, CursorCaptureSettings, DirtyRegionSettings, DrawBorderSettings,
15+
MinimumUpdateIntervalSettings, SecondaryWindowSettings, Settings,
16+
};
17+
18+
struct ScreenCapture {
19+
capture_sender: crossbeam_channel::Sender<ColorImage>,
20+
}
21+
22+
impl GraphicsCaptureApiHandler for ScreenCapture {
23+
type Flags = crossbeam_channel::Sender<ColorImage>;
24+
25+
type Error = Box<dyn std::error::Error + Send + Sync>;
26+
27+
fn new(ctx: Context<Self::Flags>) -> Result<Self, Self::Error> {
28+
Ok(ScreenCapture {
29+
capture_sender: ctx.flags,
30+
})
31+
}
32+
33+
fn on_frame_arrived(
34+
&mut self,
35+
frame: &mut Frame,
36+
_capture_control: windows_capture::graphics_capture_api::InternalCaptureControl,
37+
) -> Result<(), Self::Error> {
38+
let width = frame.width();
39+
let height = frame.height();
40+
if let Ok(mut buffer) = frame.buffer() {
41+
if let Ok(no_pad_buffer) = buffer.as_nopadding_buffer() {
42+
let img = ColorImage::from_rgba_unmultiplied(
43+
[width as usize, height as usize],
44+
&no_pad_buffer,
45+
);
46+
let _ = self.capture_sender.try_send(img);
47+
}
48+
}
49+
Ok(())
50+
}
51+
}
1052

1153
#[derive(Debug)]
1254
enum WorkerEvents {
@@ -15,11 +57,12 @@ enum WorkerEvents {
1557
SHOW(u32, u32, bool),
1658
}
1759

18-
#[derive(Debug)]
1960
struct Gui {
2061
windows: Arc<Mutex<Vec<WindowInfo>>>,
21-
sender: crossbeam_channel::Sender<WorkerEvents>,
22-
hide_taskbar_icons: bool,
62+
event_sender: crossbeam_channel::Sender<WorkerEvents>,
63+
capture_receiver: crossbeam_channel::Receiver<ColorImage>,
64+
hide_from_taskbar: bool,
65+
capture_tex: Option<egui::TextureHandle>,
2366
}
2467

2568
impl Gui {
@@ -50,10 +93,39 @@ impl Gui {
5093
}
5194
});
5295

96+
let (capture_sender, capture_receiver) = crossbeam_channel::bounded(1);
97+
98+
thread::spawn(move || {
99+
let primary_monitor = Monitor::primary().expect("There is no primary monitor");
100+
101+
let settings = Settings::new(
102+
// Item to capture
103+
primary_monitor,
104+
// Capture cursor settings
105+
CursorCaptureSettings::Default,
106+
// Draw border settings
107+
DrawBorderSettings::Default,
108+
// Secondary window settings, if you want to include secondary windows in the capture
109+
SecondaryWindowSettings::Default,
110+
// Minimum update interval, if you want to change the frame rate limit (default is 60 FPS or 16.67 ms)
111+
MinimumUpdateIntervalSettings::Default,
112+
// Dirty region settings,
113+
DirtyRegionSettings::Default,
114+
// The desired color format for the captured frame.
115+
ColorFormat::Rgba8,
116+
// Additional flags for the capture settings that will be passed to the user-defined `new` function.
117+
capture_sender,
118+
);
119+
120+
ScreenCapture::start(settings).expect("screen capture failed");
121+
});
122+
53123
Gui {
54124
windows,
55-
sender,
56-
hide_taskbar_icons: false,
125+
event_sender: sender,
126+
capture_receiver,
127+
hide_from_taskbar: false,
128+
capture_tex: None,
57129
}
58130
}
59131
}
@@ -67,17 +139,39 @@ impl eframe::App for Gui {
67139
// `focused` is a bool: true=gained focus, false=lost focus
68140
if focused {
69141
println!("focused");
70-
self.sender.send(WorkerEvents::UPDATE).unwrap();
142+
self.event_sender.send(WorkerEvents::UPDATE).unwrap();
71143
} else {
72144
println!("unfocused");
73145
}
74146
}
75147
}
76148
egui::CentralPanel::default().show(ctx, |ui| {
77-
ui.heading("Hide applications");
78-
ui.add_space(4.0);
79-
80149
egui::ScrollArea::vertical().show(ui, |ui| {
150+
ui.heading("Desktop Preview");
151+
ui.add_space(4.0);
152+
153+
if let Ok(img) = self.capture_receiver.try_recv() {
154+
if let Some(texture_handle) = &mut self.capture_tex {
155+
texture_handle.set(img, egui::TextureOptions::default());
156+
} else {
157+
self.capture_tex = Some(ctx.load_texture(
158+
"screen_capture",
159+
img,
160+
egui::TextureOptions::LINEAR, // or NEAREST if you want crisp pixels
161+
));
162+
}
163+
}
164+
165+
if let Some(tex) = &self.capture_tex {
166+
// Show at native size:
167+
ui.add(egui::Image::from_texture(tex).shrink_to_fit());
168+
// or force a size (e.g. scale to 512x512):
169+
// ui.image((tex.id(), egui::vec2(512.0, 512.0)));
170+
}
171+
ui.add_space(8.0);
172+
173+
ui.heading("Hide applications");
174+
ui.add_space(4.0);
81175
for window_info in self.windows.lock().unwrap().iter_mut() {
82176
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate); // elide with "…"
83177
let checkbox_response =
@@ -87,24 +181,21 @@ impl eframe::App for Gui {
87181
WorkerEvents::HIDE(
88182
window_info.pid,
89183
window_info.hwnd,
90-
!self.hide_taskbar_icons,
184+
self.hide_from_taskbar,
91185
)
92186
} else {
93187
WorkerEvents::SHOW(
94188
window_info.pid,
95189
window_info.hwnd,
96-
!self.hide_taskbar_icons,
190+
self.hide_from_taskbar,
97191
)
98192
};
99-
self.sender.send(event).unwrap();
193+
self.event_sender.send(event).unwrap();
100194
}
101195
}
102196
ui.add_space(10.0);
103197
ui.collapsing("Advanced settings", |ui| {
104-
ui.checkbox(
105-
&mut self.hide_taskbar_icons,
106-
"Hide from Alt+Tab and Taskbar",
107-
)
198+
ui.checkbox(&mut self.hide_from_taskbar, "Hide from Alt+Tab and Taskbar")
108199
});
109200
});
110201
});

injector/src/injector.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ pub fn set_window_props(
114114
target_process: OwnedProcess,
115115
hwnds: &[u32],
116116
hide: bool,
117-
show_on_taskbar: bool,
117+
hide_from_taskbar: bool,
118118
) {
119119
let syringe = Syringe::for_process(target_process);
120120

@@ -125,23 +125,22 @@ pub fn set_window_props(
125125

126126
let remote_proc2 = inject_and_get_remote_proc::<extern "system" fn(HWND, bool) -> bool>(
127127
&syringe,
128-
"ShowOnTaskBar",
128+
"HideFromTaskbar",
129129
);
130130

131131
for hwnd in hwnds {
132132
remote_proc
133133
.call(HWND(hwnd.clone() as *mut _), hide)
134134
.unwrap();
135135

136-
if !show_on_taskbar {
137-
remote_proc2
138-
.call(HWND(hwnd.clone() as *mut _), !hide)
139-
.unwrap();
140-
}
136+
// TODO: only call this method if we have changed it before
137+
remote_proc2
138+
.call(HWND(hwnd.clone() as *mut _), hide && hide_from_taskbar)
139+
.unwrap();
141140
}
142141
}
143142

144-
pub fn set_window_props_with_pid(pid: u32, hwnd: u32, hide: bool, show_on_taskbar: bool) {
143+
pub fn set_window_props_with_pid(pid: u32, hwnd: u32, hide: bool, hide_from_taskbar: bool) {
145144
let target_process = OwnedProcess::from_pid(pid).unwrap();
146-
set_window_props(target_process, &[hwnd], hide, show_on_taskbar);
145+
set_window_props(target_process, &[hwnd], hide, hide_from_taskbar);
147146
}

payload/src/lib.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,17 @@ pub extern "system" fn SetWindowVisibility(hwnd: HWND, hide: bool) -> bool {
1818
}
1919

2020
#[unsafe(no_mangle)]
21-
pub extern "system" fn ShowOnTaskBar(hwnd: HWND, show: bool) -> bool {
21+
pub extern "system" fn HideFromTaskbar(hwnd: HWND, hide: bool) -> bool {
2222
let mut style = unsafe { GetWindowLongW(hwnd, GWL_EXSTYLE) };
2323
if style == 0 {
2424
return false;
2525
}
26-
if show {
27-
style |= WS_EX_APPWINDOW.0 as i32;
28-
style &= (!WS_EX_TOOLWINDOW.0) as i32;
29-
} else {
26+
if hide {
3027
style |= WS_EX_TOOLWINDOW.0 as i32;
3128
style &= (!WS_EX_APPWINDOW.0) as i32;
29+
} else {
30+
style |= WS_EX_APPWINDOW.0 as i32;
31+
style &= (!WS_EX_TOOLWINDOW.0) as i32;
3232
}
3333
unsafe { SetWindowLongW(hwnd, GWL_EXSTYLE, style) };
3434
true

0 commit comments

Comments
 (0)