mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-08 11:29:33 -06:00
Show: Presenter View only with a second display (#1156)
* Show: Presenter View only with a second display With one display the presenter window opened on the same screen as the show. Under Hyprland's focus-follows-mouse, whichever window the pointer was over took full screen, and the hidden one stalled every frame: the show lagged, dropped key presses or froze. One display now shows only the slides, as in PowerPoint. The host counts displays through winit's monitor list when a show starts; hosts that can't tell keep Presenter View. * Show: count displays by MonitorHandle Wayland lists one output twice under the same wl_output id, so comparing handles counts it once, and two separate displays with the same name and geometry still count as two. * Show: docs for Presenter View with a second display slideshow-parity and hardware-parity note that on Linux one display shows only the slides, with status lines and revision history; ROADMAP progress log. display_count collects into a BTreeSet (MonitorHandle's Ord matches its Eq). * Show: docs state the one-display rule exactly Presenter View stays off when winit lists exactly one display; if it lists none, the setting is kept. The host callback covers Unix other than macOS, so the docs say Linux and the BSDs. * Apply review fixes - docs/slideshow-parity.md --------- Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
This commit is contained in:
@@ -156,6 +156,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
|---|---|
|
||||
| 2026-11-04 | Ogg Opus recovery keeps trailing packets without granules in the total duration, matching their recovered positions; valid end trimming stays covered |
|
||||
| 2026-11-03 | Web: the `?host=parent` bridge (as in GridCraft, WordCraft and PdfCraft) lets a same-origin page that frames DeckCraft send a presentation and store its saves; Save writes an opened .pptx back as .pptx under its own name, as on the desktop |
|
||||
| 2026-11-02 | Slide show on Linux and the BSDs: Presenter View stays off when winit lists exactly one display, as in PowerPoint; if it lists none, the setting is kept. On one display the presenter window covered the show, and the hidden window stalled every frame: slide changes took up to ~1.7 s under Hyprland (#1156) |
|
||||
| 2026-11-02 | Native and PPTX saves retain optional table right-to-left direction, including explicit false and omission; two-save round-trip tests keep labeled columns in order |
|
||||
| 2026-11-02 | Text runs preserve alternate-language metadata independently from the main language through native and PPTX saves |
|
||||
| 2026-11-02 | Picture compression measures displayed dimensions through nested group transforms; checklist score unchanged |
|
||||
|
||||
@@ -143,6 +143,14 @@ fn services(native_wayland: bool) -> Services {
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays winit lists for `window`, counting an output Wayland lists twice once. `None` when it
|
||||
/// lists none: Presenter View then stays as it was.
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
fn display_count(window: &winit::window::Window) -> Option<usize> {
|
||||
let n = window.available_monitors().collect::<std::collections::BTreeSet<_>>().len();
|
||||
(n > 0).then_some(n)
|
||||
}
|
||||
|
||||
const APP_ID: &str = "ai.storyteller.deckcraft";
|
||||
|
||||
const USAGE: &str = "\
|
||||
@@ -280,6 +288,11 @@ fn main() -> eframe::Result {
|
||||
Box::new(move |cc| {
|
||||
deckcraft_ui_egui::install_ime_pass_hooks(&cc.egui_ctx);
|
||||
let mut app = SlideApp::new(Session::new(), services(native_wayland));
|
||||
// Presenter View asks winit how many displays there are when a show starts.
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
if let Some(window) = cc.winit_window().cloned() {
|
||||
app.services.displays = Some(Box::new(move || display_count(&window)));
|
||||
}
|
||||
load_prefs(&mut app);
|
||||
app.enable_update_checks();
|
||||
// AutoRecover: reopen what a previous run left unsaved, then keep it current.
|
||||
|
||||
@@ -32,15 +32,24 @@ impl SlideApp {
|
||||
pub fn start_show(&mut self, from: usize, reading: bool) {
|
||||
self.anim_preview = None;
|
||||
self.media.stop_owned(media::Owner::Editor);
|
||||
let presenter = self.ui.presenter_view && !reading && self.second_display();
|
||||
if let Some(d) = self.session.active() {
|
||||
self.show_instance = self.show_instance.wrapping_add(1).max(1);
|
||||
let mut show = show::Show::new(&d.doc, from, reading, self.ui.presenter_view && !reading);
|
||||
let mut show = show::Show::new(&d.doc, from, reading, presenter);
|
||||
show.instance = self.show_instance;
|
||||
show.keep_cursor = self.ui.show_keep_cursor;
|
||||
self.show = Some(show);
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a second display can take Presenter View. Like PowerPoint, one display shows only
|
||||
/// the slides: the presenter window would open on the same screen, and whichever of the two
|
||||
/// windows ends up behind the other stalls the show (the compositor stops pacing a hidden
|
||||
/// window). Hosts that can't count displays keep Presenter View.
|
||||
fn second_display(&mut self) -> bool {
|
||||
self.services.displays.as_mut().and_then(|f| f()).is_none_or(|n| n >= 2)
|
||||
}
|
||||
|
||||
/// Play custom show `name` from its first slide. Set Up Slide Show is left as it was.
|
||||
pub fn start_custom_show(&mut self, name: &str, reading: bool) {
|
||||
let state = self.session.active().and_then(|d| deckcraft_anim::ShowState::play_custom(&d.doc, name));
|
||||
|
||||
@@ -16,3 +16,20 @@ fn show_presenter_answers_on_and_window() {
|
||||
let doc = crate::UI_COMMANDS.iter().find(|c| c.0 == "show.presenter").unwrap().3;
|
||||
assert!(doc.contains("separate window"), "{doc}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn presenter_view_needs_a_second_display() {
|
||||
// One display: the presenter window covered the show (or the show covered it) and the
|
||||
// hidden window stalled every frame.
|
||||
let presenter = |displays: Option<usize>, setting: bool| {
|
||||
let services = Services { displays: Some(Box::new(move || displays)), ..Default::default() };
|
||||
let mut app = SlideApp::new(Session::with_new(), services);
|
||||
app.ui.presenter_view = setting;
|
||||
app.start_show(0, false);
|
||||
app.show.as_ref().map(|s| s.presenter)
|
||||
};
|
||||
assert_eq!(presenter(Some(1), true), Some(false));
|
||||
assert_eq!(presenter(Some(2), true), Some(true));
|
||||
assert_eq!(presenter(None, true), Some(true), "a host that can't count displays keeps Presenter View");
|
||||
assert_eq!(presenter(Some(2), false), Some(false), "the setting still turns it off");
|
||||
}
|
||||
|
||||
@@ -71,4 +71,6 @@ pub struct Services {
|
||||
pub clipboard_set_html: Option<Box<dyn FnMut(&str, &str) -> bool>>,
|
||||
/// Audio output for media playback (cpal on desktop); without one media plays silently.
|
||||
pub audio_out: Option<Box<dyn deckcraft_media::AudioOut>>,
|
||||
/// How many displays are connected, when the host can tell: Presenter View needs a second.
|
||||
pub displays: Option<Box<dyn FnMut() -> Option<usize>>>,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Hardware parity with PowerPoint
|
||||
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (community PR wave 2026-10-14: picture export DPI, Retina pixel grid (#184), twist to rotate, swipe to turn the page, pen pressure; duplicated rows and headers folded; previous: minor, IME input, #101) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365); PowerPoint for Windows where noted
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-11-02 · **Change:** minor (Presenter View only with a second display on Linux and the BSDs, #1156; previous: minor, community PR wave 2026-10-14: picture export DPI, Retina pixel grid (#184), twist to rotate, swipe to turn the page, pen pressure; duplicated rows and headers folded; previous: minor, IME input, #101) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365); PowerPoint for Windows where noted
|
||||
|
||||
What hardware PowerPoint uses and what DeckCraft uses, per platform. Dimension score: **~40%
|
||||
ready** (estimated), **25–45 h** to parity. Performance (unmeasured, ~55%) is tracked here too
|
||||
@@ -34,7 +34,7 @@ because it mostly follows from hardware use.
|
||||
|
||||
| Feature | PowerPoint | DeckCraft |
|
||||
|---|---|---|
|
||||
| Detect the projector, show on it, presenter view on the laptop | yes | **no**: the show goes full screen on the current display; presenter view opens as a normal window (egui viewport) |
|
||||
| Detect the projector, show on it, presenter view on the laptop | yes | **no**: the show goes full screen on the current display; presenter view opens as a normal window (egui viewport). On Linux and the BSDs, when winit lists exactly one display, the show opens without Presenter View, as in PowerPoint; if it lists none, the setting is kept (#1156) |
|
||||
| Swap displays, choose the monitor in Set Up Show | yes | no |
|
||||
| Mirroring-aware behaviour | yes | no |
|
||||
|
||||
@@ -73,6 +73,7 @@ alignment runs once per transition (#67), and the decoders run in software.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-02 | minor | On Linux and the BSDs, Presenter View stays off when winit lists exactly one display: the presenter window covered the show and stalled it. If winit lists none, the setting is kept (#1156) |
|
||||
| 2026-10-14 | minor | PNG and JPEG slide export record 144 dpi at the default 2 pixels per point, so a Retina picture opens at the slide's size |
|
||||
| 2026-10-13 | trivial | Editor slide rasters sit on the physical pixel grid (#184) |
|
||||
| 2026-10-14 | trivial | Community PR wave folded: one row each for pinch/two-finger gestures and pen pressure |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Slide show and presenter view: parity with PowerPoint
|
||||
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-11-02 · **Change:** minor (deleting slides removes them from custom shows; previous: minor, Set Up Slide Show ▸ Keep mouse cursor visible, #1445; previous: minor, Ctrl+wheel zooms the show around the pointer, #1286; previous: major, community PR wave 2026-10-14: interactive decks, laser pointer, See All Slides and zoom, go-to-slide number, custom show playback, presenter timer, clock, notes size and keyboard, Show Media Controls, swipe; breadth 68%; duplicated rows and headers folded; previous: major, created) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-11-02 · **Change:** minor (deleting slides removes them from custom shows; previous: minor, Presenter View only with a second display on Linux and the BSDs, #1156; previous: minor, Set Up Slide Show ▸ Keep mouse cursor visible, #1445; previous: minor, Ctrl+wheel zooms the show around the pointer, #1286; previous: major, community PR wave 2026-10-14: interactive decks, laser pointer, See All Slides and zoom, go-to-slide number, custom show playback, presenter timer, clock, notes size and keyboard, Show Media Controls, swipe; breadth 68%; duplicated rows and headers folded; previous: major, created) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
|
||||
Presenting is the job PowerPoint is hired for. This doc covers the show window, presenter view,
|
||||
rehearsal and recording. Transition and animation playback are in
|
||||
@@ -41,7 +41,7 @@ ready** (estimated), **10–16 h** to parity.
|
||||
|
||||
| Feature | PowerPoint | DeckCraft | Notes |
|
||||
|---|---|---|---|
|
||||
| Separate presenter window with current slide, next slide, notes | yes | yes (an egui viewport, 1100×700); the notes keep their bullets, numbers, indents and bold/italic, as in the notes pane | |
|
||||
| Separate presenter window with current slide, next slide, notes | yes | yes (an egui viewport, 1100×700); the notes keep their bullets, numbers, indents and bold/italic, as in the notes pane | On Linux and the BSDs it stays off if winit lists exactly one display as the show starts, as in PowerPoint: on one display it covered the show, and the hidden window stalled every frame (#1156). If winit lists none, the setting is kept. There is not yet a way to switch to Presenter View by hand during a one-display show. macOS, Windows and the browser are unchanged |
|
||||
| Elapsed timer | yes | yes | |
|
||||
| Pause / reset timer, clock | yes | yes: Pause/Resume and Reset in presenter view (T pauses, R resets); the clock is local time, `--:--` where there is no clock | Pausing the timer does not pause the show, animations or rehearsal |
|
||||
| Slide counter, previous/next/end buttons | yes | yes | |
|
||||
@@ -79,6 +79,7 @@ Reading View in a window with navigation: present (checklist row done).
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-02 | minor | On Linux and the BSDs, Presenter View stays off when winit lists exactly one display; if it lists none, the setting is kept (#1156) |
|
||||
| 2026-10-27 | minor | Set Up Slide Show ▸ Keep mouse cursor visible: full-screen shows keep a still pointer, so clickable choices stay targetable; Reading View unchanged (#1445) |
|
||||
| 2026-10-22 | minor | See All Slides from presenter view opens in presenter view, not on the audience screen (#1130) |
|
||||
| 2026-10-14 | trivial | Community PR wave folded: one media playback row, the presenter pen/laser row matches the laser that exists, estimates keep only the open work (~9–16 h); checklist breadth 68% |
|
||||
|
||||
Reference in New Issue
Block a user