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
21 changes: 0 additions & 21 deletions electron-app/magnifier/magnifier-main-rust.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,7 @@ class MagnifyingColorPicker {
try {
// Pre-start the sampler to trigger permission dialogs BEFORE showing magnifier
// This is critical on Wayland where the permission dialog needs to be clickable
console.log(
'[Magnifying Color Picker] Pre-starting sampler for permission check...'
);
await this.samplerManager.ensureStarted(this.gridSize, 15);
console.log('[Magnifying Color Picker] Sampler ready, showing magnifier');

await this.createMagnifierWindow();
return await this.startColorPicking();
} catch (error) {
Expand Down Expand Up @@ -166,20 +161,12 @@ class MagnifyingColorPicker {
const newDiameter = getNextDiameter(this.magnifierDiameter, delta);

if (newDiameter !== this.magnifierDiameter) {
console.log(
`[Magnifier] Diameter change: ${this.magnifierDiameter} → ${newDiameter}`
);
this.magnifierDiameter = newDiameter;
const oldGridSize = this.gridSize;
this.gridSize = calculateGridSize(
this.magnifierDiameter,
this.squareSize
);

console.log(
`[Magnifier] Grid size change: ${oldGridSize} → ${this.gridSize}`
);

// Update grid size in Rust sampler
this.samplerManager.updateGridSize(this.gridSize);
}
Expand All @@ -189,20 +176,12 @@ class MagnifyingColorPicker {
const newSquareSize = adjustSquareSize(this.squareSize, delta);

if (newSquareSize !== this.squareSize) {
console.log(
`[Magnifier] Square size change: ${this.squareSize} → ${newSquareSize}`
);
this.squareSize = newSquareSize;
const oldGridSize = this.gridSize;
this.gridSize = calculateGridSize(
this.magnifierDiameter,
this.squareSize
);

console.log(
`[Magnifier] Grid size change: ${oldGridSize} → ${this.gridSize}`
);

// Update grid size in Rust sampler
this.samplerManager.updateGridSize(this.gridSize);
}
Expand Down
33 changes: 0 additions & 33 deletions electron-app/magnifier/magnifier-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,6 @@ class MagnifyingColorPicker {
const cursorPos = screen.getCursorScreenPoint();
const display = screen.getDisplayNearestPoint(cursorPos);

console.log('[DEBUG] Cursor position:', cursorPos);
console.log('[DEBUG] Display info:', {
id: display.id,
bounds: display.bounds,
size: display.size,
scaleFactor: display.scaleFactor,
});

const sources = await desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: {
Expand All @@ -109,24 +101,6 @@ class MagnifyingColorPicker {
},
});

console.log(
'[DEBUG] desktopCapturer.getSources() returned:',
sources.length,
'sources'
);
sources.forEach((s, index) => {
console.log(`[DEBUG] Source ${index}:`, {
id: s.id,
name: s.name,
display_id: s.display_id,
appIcon: s.appIcon ? 'present' : 'null',
thumbnail: s.thumbnail
? `${s.thumbnail.getSize().width}x${s.thumbnail.getSize().height}`
: 'null',
});
});
console.log('[DEBUG] Looking for display_id:', display.id.toString());

// Find the source that matches the display under the cursor
let source = sources.find((s) => s.display_id === display.id.toString());

Expand All @@ -148,13 +122,6 @@ class MagnifyingColorPicker {
throw new Error(`No screen source found for display ${display.id}`);
}

console.log(
'[DEBUG] Using source:',
source.id,
'display_id:',
source.display_id
);

const nativeImage = source.thumbnail;
const bitmap = nativeImage.toBitmap();

Expand Down
7 changes: 0 additions & 7 deletions electron-app/magnifier/rust-sampler-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,22 +181,18 @@ export class RustSamplerManager {
}

updateGridSize(gridSize: number): void {
console.log(`[RustSampler] Sending update_grid command: ${gridSize}`);
const command = {
command: 'update_grid',
grid_size: gridSize,
};
this.sendCommand(command);
console.log(`[RustSampler] Command sent:`, JSON.stringify(command));
}

stop(): Promise<void> {
if (!this.process) {
return Promise.resolve();
}

console.log('[RustSampler] Stopping process');

const proc = this.process;
this.process = null;
this.dataCallback = null;
Expand All @@ -205,7 +201,6 @@ export class RustSamplerManager {
return new Promise<void>((resolve) => {
// Set up exit handler
const onExit = () => {
console.log('[RustSampler] Process exited');
if (this.forceKillTimeout) {
clearTimeout(this.forceKillTimeout);
this.forceKillTimeout = null;
Expand Down Expand Up @@ -259,9 +254,7 @@ export class RustSamplerManager {

try {
const json = JSON.stringify(command);
console.log('[RustSampler] Writing to stdin:', json);
this.process.stdin.write(json + '\n');
console.log('[RustSampler] Write successful');
} catch (e) {
console.error('[RustSampler] Failed to send command:', e);
}
Expand Down
2 changes: 1 addition & 1 deletion electron-app/magnifier/rust-sampler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ x11 = { version = "2.21", features = ["xlib"], optional = true }
dirs = { version = "5.0", optional = true }
# Optional dependencies for Wayland support (requires libpipewire-0.3-dev)
ashpd = { version = "0.9", optional = true }
pipewire = { version = "0.7.2", features = ["v0_3_41"], optional = true }
pipewire = { version = "0.9", optional = true }
tokio = { version = "1", features = ["rt", "sync", "macros"], optional = true }
futures = { version = "0.3", optional = true }

Expand Down
83 changes: 43 additions & 40 deletions electron-app/magnifier/rust-sampler/src/sampler/wayland_portal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,21 +170,21 @@ impl WaylandPortalSampler {

// Initialize PipeWire
pw::init();

// Create PipeWire main loop
let mainloop = pw::main_loop::MainLoop::new(None)
let mainloop = pw::main_loop::MainLoopRc::new(None)
.map_err(|_| "Failed to create PipeWire main loop".to_string())?;

// Create PipeWire context
let context = pw::context::Context::new(&mainloop)
let context = pw::context::ContextRc::new(&mainloop, None)
.map_err(|_| "Failed to create PipeWire context".to_string())?;

// Connect to PipeWire core
let core = context.connect(None)
let core = context.connect_rc(None)
.map_err(|_| "Failed to connect to PipeWire".to_string())?;

// Create a stream
let stream = pw::stream::Stream::new(
let stream = pw::stream::StreamBox::new(
&core,
"swach-screenshot",
pw::properties::properties! {
Expand All @@ -194,20 +194,31 @@ impl WaylandPortalSampler {
},
).map_err(|_| "Failed to create PipeWire stream".to_string())?;

// Buffer to store the screenshot
let screenshot_buffer = Arc::clone(&self.screenshot_buffer);
let frame_captured = Arc::new(std::sync::atomic::AtomicBool::new(false));
let frame_captured_clone = Arc::clone(&frame_captured);

// Video format info
let video_info: Arc<Mutex<Option<(u32, u32, usize)>>> = Arc::new(Mutex::new(None));
let video_info_clone = Arc::clone(&video_info);
let video_info_process = Arc::clone(&video_info);


// Frame captured flag
let frame_captured = Arc::new(std::sync::atomic::AtomicBool::new(false));

// User data for callbacks
struct CallbackData {
screenshot_buffer: Arc<Mutex<Option<ScreenshotBuffer>>>,
frame_captured: Arc<std::sync::atomic::AtomicBool>,
video_info: Arc<Mutex<Option<(u32, u32, usize)>>>,
mainloop: pw::main_loop::MainLoopRc,
}

let callback_data = Arc::new(CallbackData {
screenshot_buffer: Arc::clone(&self.screenshot_buffer),
frame_captured: Arc::clone(&frame_captured),
video_info: Arc::clone(&video_info),
mainloop: mainloop.clone(),
});

// Add listener to receive one frame
let _listener = stream
.add_local_listener_with_user_data(screenshot_buffer)
.param_changed(move |_stream, _user_data, id, param| {
.add_local_listener_with_user_data(callback_data)
.param_changed(move |_stream, user_data, id, param| {
use pw::spa::param::ParamType;

if id != ParamType::Format.as_raw() {
Expand All @@ -218,14 +229,14 @@ impl WaylandPortalSampler {
use pw::spa::param::video::VideoInfoRaw;

if let Ok((_media_type, _media_subtype)) = pw::spa::param::format_utils::parse_format(param) {
let mut info = VideoInfoRaw::new();
let mut info = VideoInfoRaw::default();
if let Ok(_) = info.parse(param) {
let size = info.size();
let width = size.width;
let height = size.height;
let stride = width as usize * 4; // BGRA format

if let Ok(mut vi) = video_info_clone.lock() {
if let Ok(mut vi) = user_data.video_info.lock() {
*vi = Some((width, height, stride));
}
}
Expand All @@ -234,49 +245,50 @@ impl WaylandPortalSampler {
})
.process(move |stream, user_data| {
// Only capture one frame
if frame_captured_clone.load(std::sync::atomic::Ordering::SeqCst) {
if user_data.frame_captured.load(std::sync::atomic::Ordering::SeqCst) {
return;
}

match stream.dequeue_buffer() {
None => {}
Some(mut buffer) => {
let datas = buffer.datas_mut();
if datas.is_empty() {
return;
}

let data = &mut datas[0];
let chunk = data.chunk();
let size = chunk.size() as usize;

if size == 0 {
return;
}

// Get video format
let format_info = if let Ok(vi) = video_info_process.lock() {
let format_info = if let Ok(vi) = user_data.video_info.lock() {
*vi
} else {
None
};

// Get frame data
if let Some(slice) = data.data() {
if slice.len() >= size {
let pixel_data = slice[..size].to_vec();

if let Some((width, height, stride)) = format_info {
if width > 0 && height > 0 {
eprintln!("[Wayland] Captured screenshot: {}x{} ({} bytes)", width, height, pixel_data.len());
if let Ok(mut buf) = user_data.lock() {
if let Ok(mut buf) = user_data.screenshot_buffer.lock() {
*buf = Some(ScreenshotBuffer {
data: pixel_data,
width,
height,
stride,
});
frame_captured_clone.store(true, std::sync::atomic::Ordering::SeqCst);
user_data.frame_captured.store(true, std::sync::atomic::Ordering::SeqCst);
user_data.mainloop.quit();
}
}
}
Expand All @@ -297,17 +309,8 @@ impl WaylandPortalSampler {
)
.map_err(|e| format!("Failed to connect stream: {:?}", e))?;

// Iterate mainloop until we capture one frame (timeout after 5 seconds)
let start_time = std::time::Instant::now();
let timeout = std::time::Duration::from_secs(5);

while !frame_captured.load(std::sync::atomic::Ordering::SeqCst) {
if start_time.elapsed() > timeout {
return Err("Timeout waiting for screenshot frame".to_string());
}

let _ = mainloop.loop_().iterate(std::time::Duration::from_millis(10));
}
// Run mainloop until we capture one frame or timeout
mainloop.run();

Ok(())
}
Expand Down
Loading