@@ -141,6 +141,11 @@ impl App { |
| 141 | 141 | let x = monitor.rect.x + (monitor.rect.width as i32 - width as i32) / 2; |
| 142 | 142 | let y = monitor.rect.y + (monitor.rect.height as i32 - height as i32) / 2; |
| 143 | 143 | |
| 144 | + tracing::debug!( |
| 145 | + "Window size: monitor={}x{}, requested={}x{} at ({}, {})", |
| 146 | + monitor.rect.width, monitor.rect.height, width, height, x, y |
| 147 | + ); |
| 148 | + |
| 144 | 149 | // Window title depends on mode |
| 145 | 150 | let title = if picker_config.is_picker() { |
| 146 | 151 | picker_config.title.clone().unwrap_or_else(|| { |
@@ -171,6 +176,11 @@ impl App { |
| 171 | 176 | // Use Dialog window type for picker mode (better focus handling) |
| 172 | 177 | if picker_config.is_picker() { |
| 173 | 178 | window_config = window_config.window_type(gartk_x11::WindowType::Dialog); |
| 179 | + |
| 180 | + // Set transient-for if parent window specified (dialog belongs to parent) |
| 181 | + if let Some(parent) = picker_config.parent_window { |
| 182 | + window_config = window_config.parent_window(parent).modal(true); |
| 183 | + } |
| 174 | 184 | } |
| 175 | 185 | |
| 176 | 186 | let window = Window::create(conn.clone(), window_config)?; |
@@ -494,8 +504,8 @@ impl App { |
| 494 | 504 | pub fn run(&mut self) -> Result<()> { |
| 495 | 505 | let mut event_loop = EventLoop::new(&self.window, EventLoopConfig::default())?; |
| 496 | 506 | |
| 497 | | - // Initial render |
| 498 | | - self.render()?; |
| 507 | + // Don't render before event loop - wait for ConfigureNotify/Expose |
| 508 | + // to get correct window dimensions from WM before first render |
| 499 | 509 | |
| 500 | 510 | event_loop.run(|ev, event| { |
| 501 | 511 | match event { |
@@ -532,7 +542,12 @@ impl App { |
| 532 | 542 | ev.request_redraw(); |
| 533 | 543 | } |
| 534 | 544 | InputEvent::Resize { width, height } => { |
| 535 | | - let _ = self.renderer.resize(width, height); |
| 545 | + tracing::debug!("ConfigureNotify resize: {}x{}", width, height); |
| 546 | + if let Err(e) = self.renderer.resize(width, height) { |
| 547 | + tracing::error!("Failed to resize renderer: {}", e); |
| 548 | + } |
| 549 | + // Update internal rect tracking (don't send X11 request - WM already sized us) |
| 550 | + self.window.set_size(width, height); |
| 536 | 551 | self.update_layout(width, height); |
| 537 | 552 | ev.request_redraw(); |
| 538 | 553 | } |
@@ -744,12 +759,12 @@ impl App { |
| 744 | 759 | } |
| 745 | 760 | PickerToolbarClick::None => {} |
| 746 | 761 | } |
| 747 | | - } else { |
| 748 | | - // Check normal toolbar clicks |
| 749 | | - if let Some(action) = self.toolbar.on_click(pos) { |
| 750 | | - self.handle_toolbar_action(action); |
| 751 | | - return; |
| 752 | | - } |
| 762 | + } |
| 763 | + |
| 764 | + // Check normal toolbar clicks (always, not just when picker_toolbar is None) |
| 765 | + if let Some(action) = self.toolbar.on_click(pos) { |
| 766 | + self.handle_toolbar_action(action); |
| 767 | + return; |
| 753 | 768 | } |
| 754 | 769 | |
| 755 | 770 | // Check pane toolbar clicks (view mode buttons) |
@@ -3379,6 +3394,9 @@ impl App { |
| 3379 | 3394 | let sidebar_w = self.sidebar.width(); |
| 3380 | 3395 | let header_height = TAB_BAR_HEIGHT + TOOLBAR_HEIGHT + BREADCRUMB_HEIGHT; |
| 3381 | 3396 | |
| 3397 | + tracing::trace!("render: size={}x{}, sidebar_w={}, root_pane_bounds={:?}", |
| 3398 | + size.width, size.height, sidebar_w, self.root_pane.bounds()); |
| 3399 | + |
| 3382 | 3400 | // Clear background |
| 3383 | 3401 | self.renderer.clear()?; |
| 3384 | 3402 | |
@@ -3619,26 +3637,111 @@ impl App { |
| 3619 | 3637 | /// Blit the rendered surface to the window. |
| 3620 | 3638 | fn blit_surface(&mut self) -> Result<()> { |
| 3621 | 3639 | let size = self.renderer.size(); |
| 3640 | + let window_size = self.window.size(); |
| 3641 | + let stride = self.renderer.surface().stride() as usize; |
| 3642 | + let row_bytes = size.width as usize * 4; // 4 bytes per pixel (ARGB) |
| 3643 | + |
| 3644 | + // Verify surface matches window size |
| 3645 | + if size.width != window_size.width || size.height != window_size.height { |
| 3646 | + tracing::warn!("Surface size {}x{} doesn't match window size {}x{}, skipping blit", |
| 3647 | + size.width, size.height, window_size.width, window_size.height); |
| 3648 | + return Ok(()); |
| 3649 | + } |
| 3650 | + |
| 3622 | 3651 | let window_id = self.window.id(); |
| 3623 | 3652 | let depth = self.window.depth(); |
| 3624 | 3653 | let gc = self.gc; |
| 3625 | 3654 | let conn = self.window.connection().clone(); |
| 3626 | 3655 | |
| 3627 | | - // Access surface data directly without copying, blit to X11 |
| 3628 | | - self.renderer.surface_mut().with_data(|data| { |
| 3629 | | - let _ = conn.inner().put_image( |
| 3630 | | - ImageFormat::Z_PIXMAP, |
| 3631 | | - window_id, |
| 3632 | | - gc, |
| 3633 | | - size.width as u16, |
| 3634 | | - size.height as u16, |
| 3635 | | - 0, |
| 3636 | | - 0, |
| 3637 | | - 0, |
| 3638 | | - depth, |
| 3639 | | - data, |
| 3640 | | - ); |
| 3641 | | - })?; |
| 3656 | + // Get maximum request size (leave some headroom for request headers) |
| 3657 | + let max_request_bytes = conn.maximum_request_bytes().saturating_sub(1024); |
| 3658 | + let total_bytes = row_bytes * size.height as usize; |
| 3659 | + |
| 3660 | + // Check if we need to chunk the image |
| 3661 | + if total_bytes <= max_request_bytes { |
| 3662 | + // Image fits in a single request |
| 3663 | + tracing::trace!("blit_surface: {}x{} ({} bytes) in single request", |
| 3664 | + size.width, size.height, total_bytes); |
| 3665 | + |
| 3666 | + self.renderer.surface_mut().with_data(|data| { |
| 3667 | + if stride == row_bytes { |
| 3668 | + let _ = conn.inner().put_image( |
| 3669 | + ImageFormat::Z_PIXMAP, |
| 3670 | + window_id, |
| 3671 | + gc, |
| 3672 | + size.width as u16, |
| 3673 | + size.height as u16, |
| 3674 | + 0, |
| 3675 | + 0, |
| 3676 | + 0, |
| 3677 | + depth, |
| 3678 | + data, |
| 3679 | + ); |
| 3680 | + } else { |
| 3681 | + let mut packed = Vec::with_capacity(total_bytes); |
| 3682 | + for y in 0..size.height as usize { |
| 3683 | + let row_start = y * stride; |
| 3684 | + let row_end = row_start + row_bytes; |
| 3685 | + if row_end <= data.len() { |
| 3686 | + packed.extend_from_slice(&data[row_start..row_end]); |
| 3687 | + } |
| 3688 | + } |
| 3689 | + let _ = conn.inner().put_image( |
| 3690 | + ImageFormat::Z_PIXMAP, |
| 3691 | + window_id, |
| 3692 | + gc, |
| 3693 | + size.width as u16, |
| 3694 | + size.height as u16, |
| 3695 | + 0, |
| 3696 | + 0, |
| 3697 | + 0, |
| 3698 | + depth, |
| 3699 | + &packed, |
| 3700 | + ); |
| 3701 | + } |
| 3702 | + })?; |
| 3703 | + } else { |
| 3704 | + // Image too large - split into horizontal bands |
| 3705 | + let rows_per_chunk = max_request_bytes / row_bytes; |
| 3706 | + let rows_per_chunk = rows_per_chunk.max(1); // At least 1 row per chunk |
| 3707 | + |
| 3708 | + tracing::debug!("blit_surface: {}x{} ({} bytes) exceeds max {} bytes, using {} rows per chunk", |
| 3709 | + size.width, size.height, total_bytes, max_request_bytes, rows_per_chunk); |
| 3710 | + |
| 3711 | + self.renderer.surface_mut().with_data(|data| { |
| 3712 | + let mut y_offset = 0u32; |
| 3713 | + while y_offset < size.height { |
| 3714 | + let chunk_height = (size.height - y_offset).min(rows_per_chunk as u32); |
| 3715 | + let chunk_bytes = row_bytes * chunk_height as usize; |
| 3716 | + |
| 3717 | + // Extract this chunk's data |
| 3718 | + let mut chunk_data = Vec::with_capacity(chunk_bytes); |
| 3719 | + for row in 0..chunk_height as usize { |
| 3720 | + let src_y = y_offset as usize + row; |
| 3721 | + let row_start = src_y * stride; |
| 3722 | + let row_end = row_start + row_bytes; |
| 3723 | + if row_end <= data.len() { |
| 3724 | + chunk_data.extend_from_slice(&data[row_start..row_end]); |
| 3725 | + } |
| 3726 | + } |
| 3727 | + |
| 3728 | + let _ = conn.inner().put_image( |
| 3729 | + ImageFormat::Z_PIXMAP, |
| 3730 | + window_id, |
| 3731 | + gc, |
| 3732 | + size.width as u16, |
| 3733 | + chunk_height as u16, |
| 3734 | + 0, |
| 3735 | + y_offset as i16, |
| 3736 | + 0, |
| 3737 | + depth, |
| 3738 | + &chunk_data, |
| 3739 | + ); |
| 3740 | + |
| 3741 | + y_offset += chunk_height; |
| 3742 | + } |
| 3743 | + })?; |
| 3744 | + } |
| 3642 | 3745 | |
| 3643 | 3746 | self.window.connection().flush()?; |
| 3644 | 3747 | |