mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-07 12:12:50 +00:00
Linux: a setting chooses the display server (Wayland / X11) (#3189)
Lands #3189. Co-Authored-By: Yasas-Sri <164534752+Yasas-Sri@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: vangel76 <271991477+vangel76@users.noreply.github.com>
This commit is contained in:
co-authored by
Yasas-Sri
Claude Opus 5.5
Codex
vangel76
parent
35e960c850
commit
28925fac8c
@@ -310,7 +310,10 @@ no audio access and doesn't include `espeak-ng`, `espeak` or Speech Dispatcher.
|
||||
**Wayland:** on a Wayland desktop the app runs through XWayland when it is available, because
|
||||
winit (the windowing library) supports dragging files into the window only on X11. To use the
|
||||
native Wayland backend instead (for example for sharper text with fractional scaling, at the cost
|
||||
of file drag-and-drop), start it with `WORDCRAFT_WAYLAND=1`:
|
||||
of file drag-and-drop), choose **File › Options › Advanced › Linux display server › Wayland**; it
|
||||
applies the next time WordCraft starts, and **X11 (XWayland)** or **Automatic** switches back. The
|
||||
`WORDCRAFT_WAYLAND` environment variable overrides the setting: `1` for native Wayland, `0` for
|
||||
XWayland:
|
||||
|
||||
```sh
|
||||
WORDCRAFT_WAYLAND=1 wordcraft
|
||||
|
||||
+3
-1
File diff suppressed because one or more lines are too long
+72
-12
@@ -253,13 +253,45 @@ fn load_user_fonts() {
|
||||
}
|
||||
}
|
||||
|
||||
/// The saved window geometry, read before the window opens (`load_prefs` runs after).
|
||||
fn saved_window() -> Option<WindowGeometry> {
|
||||
/// The saved preferences, read before the window opens for its geometry and, on Linux and the
|
||||
/// BSDs, the display server (`load_prefs` runs after).
|
||||
fn saved_ui() -> Option<UiState> {
|
||||
if !prefs_enabled() {
|
||||
return None;
|
||||
}
|
||||
let bytes = std::fs::read(prefs_path()?).ok()?;
|
||||
UiState::from_json(&bytes)?.window?.sanitized()
|
||||
UiState::from_json(&bytes)
|
||||
}
|
||||
|
||||
/// Whether the window runs through XWayland (X11) rather than native Wayland, and the log line
|
||||
/// saying why. `WORDCRAFT_WAYLAND` wins (`0`, `false`, `no` or `off` asks for X11, any other value
|
||||
/// for Wayland), then File › Options › Advanced › Linux display server (#2184). Automatic is X11
|
||||
/// whenever there's an X server: winit has no file drag-and-drop on Wayland, so dropping files from
|
||||
/// the file manager showed a "no" cursor. X11 is only forced with an X server (`DISPLAY`), and
|
||||
/// unforced winit picks Wayland when it's there and X11 otherwise, so a choice that isn't
|
||||
/// available falls back to the other instead of failing to start.
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
fn uses_x11(setting: wordcraft_ui_egui::DisplayServer, env: impl Fn(&str) -> Option<std::ffi::OsString>) -> (bool, String) {
|
||||
use wordcraft_ui_egui::DisplayServer;
|
||||
let set = |name: &str| env(name).is_some_and(|v| !v.is_empty());
|
||||
let (wanted, by) = match env("WORDCRAFT_WAYLAND") {
|
||||
Some(v) if matches!(v.to_string_lossy().trim().to_ascii_lowercase().as_str(), "0" | "false" | "no" | "off") => {
|
||||
(DisplayServer::X11, "WORDCRAFT_WAYLAND")
|
||||
}
|
||||
Some(_) => (DisplayServer::Wayland, "WORDCRAFT_WAYLAND"),
|
||||
None => (setting, "the setting"),
|
||||
};
|
||||
let x11 = wanted != DisplayServer::Wayland && set("DISPLAY");
|
||||
let runs = if x11 {
|
||||
"X11"
|
||||
} else if set("WAYLAND_DISPLAY") || set("WAYLAND_SOCKET") {
|
||||
"native Wayland"
|
||||
} else if set("DISPLAY") {
|
||||
"X11 (no Wayland)"
|
||||
} else {
|
||||
"neither (no Wayland or X server found)"
|
||||
};
|
||||
(x11, format!("display: {runs}; {} asks for {}", by, wanted.code()))
|
||||
}
|
||||
|
||||
fn save_prefs(app: &WordApp) {
|
||||
@@ -493,19 +525,21 @@ fn main() -> eframe::Result {
|
||||
load_user_fonts();
|
||||
// Windows opened for further documents start like the first one, before its saved place.
|
||||
let window_builder = options.viewport.clone();
|
||||
let restored = saved_window();
|
||||
let saved = saved_ui();
|
||||
let restored = saved.as_ref().and_then(|ui| ui.window?.sanitized());
|
||||
if let Some(window) = restored {
|
||||
options.viewport = window.apply(options.viewport);
|
||||
}
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
// winit has no file drag-and-drop on Wayland (only on X11), so dropping files from the file
|
||||
// manager showed a "no" cursor. Run through XWayland when it's there; WORDCRAFT_WAYLAND=1
|
||||
// keeps the native Wayland backend.
|
||||
if std::env::var_os("DISPLAY").is_some() && std::env::var_os("WORDCRAFT_WAYLAND").is_none() {
|
||||
options.event_loop_builder = Some(Box::new(|b| {
|
||||
use winit::platform::x11::EventLoopBuilderExtX11;
|
||||
b.with_x11();
|
||||
}));
|
||||
{
|
||||
let (x11, why) = uses_x11(saved.as_ref().map(|ui| ui.display_server).unwrap_or_default(), |name| std::env::var_os(name));
|
||||
log::info!("{why}");
|
||||
if x11 {
|
||||
options.event_loop_builder = Some(Box::new(|b| {
|
||||
use winit::platform::x11::EventLoopBuilderExtX11;
|
||||
b.with_x11();
|
||||
}));
|
||||
}
|
||||
}
|
||||
// Registered before the event loop starts, so it catches the Finder event that launched us as
|
||||
// well as later ones. Lives until the event loop returns; the app creator only borrows it.
|
||||
@@ -619,6 +653,32 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
/// #2184: `WORDCRAFT_WAYLAND` beats the setting, Automatic keeps XWayland where there's an X
|
||||
/// server, and a choice that isn't available falls back to the other.
|
||||
#[test]
|
||||
fn the_display_server_follows_the_variable_then_the_setting() {
|
||||
use wordcraft_ui_egui::DisplayServer::{Auto, Wayland, X11};
|
||||
let x11 = |setting, vars: &[(&str, &str)]| {
|
||||
uses_x11(setting, |name| vars.iter().find(|(k, _)| *k == name).map(|(_, v)| std::ffi::OsString::from(v))).0
|
||||
};
|
||||
let both = [("DISPLAY", ":0"), ("WAYLAND_DISPLAY", "wayland-0")];
|
||||
assert!(x11(Auto, &both), "Automatic keeps XWayland, for file drag-and-drop");
|
||||
assert!(x11(X11, &both));
|
||||
assert!(!x11(Wayland, &both));
|
||||
for (value, wants_x11) in [("1", false), ("", false), ("yes", false), ("0", true), (" Off ", true), ("FALSE", true), ("no", true)] {
|
||||
let vars = [both[0], both[1], ("WORDCRAFT_WAYLAND", value)];
|
||||
for setting in [Auto, Wayland, X11] {
|
||||
assert_eq!(x11(setting, &vars), wants_x11, "WORDCRAFT_WAYLAND={value:?} beats {setting:?}");
|
||||
}
|
||||
}
|
||||
// Without an X server X11 isn't forced: winit connects to Wayland instead of failing.
|
||||
let wayland_only = [("WAYLAND_DISPLAY", "wayland-0"), ("DISPLAY", "")];
|
||||
assert!(!x11(X11, &wayland_only) && !x11(Auto, &wayland_only));
|
||||
// Without Wayland, Wayland isn't forced either: unforced winit picks X11.
|
||||
let (forced, why) = uses_x11(Wayland, |name| (name == "DISPLAY").then(|| ":0".into()));
|
||||
assert!(!forced && why.contains("X11 (no Wayland)"), "{why}");
|
||||
}
|
||||
|
||||
/// A named pipe or a device under such a name is a warning, not a read: the pipe blocked
|
||||
/// start-up until something wrote to it, and `/dev/zero` read until memory ran out.
|
||||
#[test]
|
||||
|
||||
@@ -779,6 +779,18 @@ impl WordApp {
|
||||
}
|
||||
json!({"startScreen": self.ui.start_screen})
|
||||
}
|
||||
"ui.displayServer" => {
|
||||
// File › Options › Advanced › Linux display server (#2184): `value` `auto`,
|
||||
// `wayland` or `x11`; no value reads it. The desktop app reads it at the next launch.
|
||||
match p.get("value") {
|
||||
None => {}
|
||||
Some(v) => match v.as_str().and_then(crate::DisplayServer::from_code) {
|
||||
Some(d) => self.ui.display_server = d,
|
||||
None => return Some(Err(format!("unknown display server {v}; use auto, wayland or x11"))),
|
||||
},
|
||||
}
|
||||
json!({"displayServer": self.ui.display_server.code()})
|
||||
}
|
||||
// Shape Format › Text › Create Link: the next click on an empty text box links the
|
||||
// selected box to it (`shape.link` with that box as the target); Esc cancels.
|
||||
// Picture Format › Size › Crop: crop mode on the selected picture, or off (`crop`).
|
||||
@@ -1036,6 +1048,7 @@ pub(crate) fn works_without_document(id: &str) -> bool {
|
||||
| "ui.discord"
|
||||
| "ui.caretAnimation"
|
||||
| "ui.startScreen"
|
||||
| "ui.displayServer"
|
||||
| "ui.checkForUpdates"
|
||||
) || id.starts_with("help.")
|
||||
}
|
||||
|
||||
@@ -138,6 +138,58 @@ struct AwaitingSave {
|
||||
rev: u64,
|
||||
}
|
||||
|
||||
/// File › Options › Advanced › Linux display server (#2184, `ui.displayServer`): how the desktop
|
||||
/// app connects on Linux and the BSDs. It's read before the window opens, so a change applies at
|
||||
/// the next launch; `WORDCRAFT_WAYLAND` overrides it. An unknown saved value reads as Automatic.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(from = "String", into = "String")]
|
||||
pub enum DisplayServer {
|
||||
/// The app's own choice: XWayland whenever there's an X server, for file drag-and-drop.
|
||||
#[default]
|
||||
Auto,
|
||||
Wayland,
|
||||
X11,
|
||||
}
|
||||
|
||||
impl DisplayServer {
|
||||
/// In menu order.
|
||||
pub const ALL: [DisplayServer; 3] = [DisplayServer::Auto, DisplayServer::Wayland, DisplayServer::X11];
|
||||
|
||||
/// The value used in `ui.json` and by the `ui.displayServer` command.
|
||||
pub fn code(self) -> &'static str {
|
||||
match self {
|
||||
DisplayServer::Auto => "auto",
|
||||
DisplayServer::Wayland => "wayland",
|
||||
DisplayServer::X11 => "x11",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_code(code: &str) -> Option<DisplayServer> {
|
||||
DisplayServer::ALL.into_iter().find(|d| d.code().eq_ignore_ascii_case(code.trim()))
|
||||
}
|
||||
|
||||
/// The English label; only Automatic is translated, the others are names.
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
DisplayServer::Auto => "Automatic",
|
||||
DisplayServer::Wayland => "Wayland",
|
||||
DisplayServer::X11 => "X11 (XWayland)",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for DisplayServer {
|
||||
fn from(s: String) -> Self {
|
||||
DisplayServer::from_code(&s).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DisplayServer> for String {
|
||||
fn from(d: DisplayServer) -> Self {
|
||||
d.code().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// UI state that persists between runs (and that agents can read and set).
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
@@ -283,6 +335,9 @@ pub struct UiState {
|
||||
pub update_fields_before_print: bool,
|
||||
/// The custom dictionary (File › Options › Proofing, Add to Dictionary), kept between runs.
|
||||
pub dictionary: Vec<String>,
|
||||
/// File › Options › Advanced › Linux display server (#2184): read by the desktop app before
|
||||
/// the window opens.
|
||||
pub display_server: DisplayServer,
|
||||
}
|
||||
|
||||
/// The ribbon tab the app opens on, and the one it falls back to when the tab that was showing
|
||||
@@ -358,6 +413,7 @@ impl Default for UiState {
|
||||
print_backgrounds: false,
|
||||
update_fields_before_print: false,
|
||||
dictionary: Vec::new(),
|
||||
display_server: DisplayServer::Auto,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,6 +645,11 @@ fn advanced(app: &mut WordApp, ui: &mut Ui) {
|
||||
{
|
||||
let _ = app.run("ui.caretAnimation", json!({"value": caret}));
|
||||
}
|
||||
if cfg!(all(unix, not(target_os = "macos"))) {
|
||||
ui.add_space(8.0);
|
||||
section(ui, "Linux display server");
|
||||
display_server_picker(app, ui);
|
||||
}
|
||||
// The web app has no folders to name.
|
||||
if !cfg!(target_arch = "wasm32") {
|
||||
ui.add_space(8.0);
|
||||
@@ -779,6 +784,25 @@ fn animations_picker(app: &mut WordApp, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Linux display server (#2184): Automatic, Wayland or X11 (XWayland). The desktop app reads it
|
||||
/// before the window opens, so a change applies at the next launch.
|
||||
fn display_server_picker(app: &mut WordApp, ui: &mut Ui) {
|
||||
use crate::DisplayServer;
|
||||
let name = |d: DisplayServer| if d == DisplayServer::Auto { tl!(d.label()) } else { d.label() };
|
||||
ui.horizontal(|ui| {
|
||||
let label = ui.label(tl!("Display server:"));
|
||||
let combo = craft_ui::ComboBox::from_id_salt("display_server").selected_text(name(app.ui.display_server)).width(220.0).show_ui(ui, |ui| {
|
||||
for d in DisplayServer::ALL {
|
||||
if ui.selectable_label(app.ui.display_server == d, name(d)).clicked() {
|
||||
let _ = app.run("ui.displayServer", json!({"value": d.code()}));
|
||||
}
|
||||
}
|
||||
});
|
||||
combo.response.labelled_by(label.id);
|
||||
});
|
||||
note(ui, "Applies the next time WordCraft starts.");
|
||||
}
|
||||
|
||||
/// Interface language: follow the system (the default) or pick one (#8).
|
||||
fn language_picker(app: &mut WordApp, ui: &mut Ui) {
|
||||
use crate::i18n::{AUTO, Lang};
|
||||
|
||||
@@ -605,6 +605,25 @@ fn caret_animation_is_a_ui_command_and_survives_a_restart() {
|
||||
assert_eq!(a.run("ui.caretAnimation", json!({"value": true})).unwrap()["caretAnimation"], true);
|
||||
}
|
||||
|
||||
/// #2184: the Linux display server is a UI command, kept in `ui.json` for the desktop app to
|
||||
/// read at the next launch; junk is refused, and an unknown saved value reads as Automatic.
|
||||
#[test]
|
||||
fn display_server_is_a_ui_command_and_survives_a_restart() {
|
||||
let mut a = app();
|
||||
assert_eq!(a.run("ui.displayServer", json!({})).unwrap(), json!({"displayServer": "auto"}), "automatic by default; no value reads it");
|
||||
assert_eq!(a.run("ui.displayServer", json!({"value": " X11 "})).unwrap(), json!({"displayServer": "x11"}));
|
||||
for junk in [json!({"value": "mir"}), json!({"value": 1}), json!({"value": null}), json!({"value": ["wayland"]})] {
|
||||
assert!(a.run("ui.displayServer", junk).is_err());
|
||||
}
|
||||
assert_eq!(a.ui.display_server, DisplayServer::X11, "a rejected value keeps the setting");
|
||||
let saved = serde_json::to_string(&a.prefs()).unwrap();
|
||||
assert!(saved.contains(r#""displayServer":"x11""#), "{saved}");
|
||||
assert_eq!(UiState::from_json(saved.as_bytes()).unwrap().display_server, DisplayServer::X11);
|
||||
for old in [r#"{"tab":"Insert"}"#, r#"{"displayServer":"mir"}"#, r#"{"displayServer":7}"#] {
|
||||
assert_eq!(UiState::from_json(old.as_bytes()).unwrap().display_server, DisplayServer::Auto, "{old}");
|
||||
}
|
||||
}
|
||||
|
||||
/// #1945: showing or hiding the Quick Access Toolbar is a UI command (no value toggles it),
|
||||
/// kept between runs; settings saved before it existed show the toolbar; it works with no
|
||||
/// document open, like the other interface settings.
|
||||
|
||||
@@ -269,6 +269,7 @@ impl WordApp {
|
||||
animations: _,
|
||||
legacy_dark: _,
|
||||
nav_tab: _,
|
||||
display_server: _,
|
||||
nav_pane: _,
|
||||
show_discord: _,
|
||||
author: _,
|
||||
|
||||
@@ -54,7 +54,7 @@ and set `WORDCRAFT_CONTROL_KEY` to that app's key.
|
||||
|
||||
| Method | Params | Result |
|
||||
|---|---|---|
|
||||
| `engine.execute` | `command`, `params`, `doc?` | the command's result (any id from `engine.commands`, including UI commands `ui.tab`, `ui.dialog` (`{"name": dialog, "part"?: table style part}`; `part` opens New / Modify Table Style (`newTableStyle` / `modifyTableStyle`) on that part: `wholeTable`, `headerRow`, `firstColumn`, `bandedRows`, `nwCell`… as `table.modifyStyle` names them), `ui.backstage`, `ui.zotero.*` (see `docs/zotero.md`), `ui.language` (`{"value": "auto"\|"en"\|"zh-hans"\|"zh-hant"\|"ja"\|"pt-br"\|"es"\|"uk"\|"sr"\|"sr-latn"\|"et"\|"tr"\|"ru"\|"pl"\|"de"\|"it"\|"cs"\|"hu"\|"fr"}`), `ui.theme` (`{"value": "system"\|"light"\|"dark"}`; `system` follows the OS appearance, light when it reports none), `ui.interfaceSize` (`{"value": "auto"}` or `{"percent": 50–300}`, clamped; no value reads it; `auto` keeps the system display scale, a percentage is absolute, so 100 is one screen pixel per point (#822); the result's `percent` is the size drawn at and `auto` whether it follows the system; document zoom stays `view.zoom`), `ui.animations` (`{"value": "system"\|"on"\|"off"}`; no value reads it; `system` follows the system's animation setting, which the desktop app reads on Windows, macOS, Linux and the BSDs and the web app from `prefers-reduced-motion`, and means on where the system doesn't say; the result's `enabled` is what the setting comes to and `system` what the system reports, `null` when it doesn't say), `ui.fileBrowser` (desktop: `{"purpose": "document"\|"picture"\|"recipients"\|"save"}` opens WordCraft's own file browser, for when the system file dialog doesn't show up, #576), `ui.saveAsTemplate` (File › Save As › Save as Word Template: the Save dialog for a `.dotx`, starting in the user templates folder; the result's `dir` is that folder, `null` on the web, #2852), `ui.caretAnimation` (`{"value": true\|false}`; no value reads it; off, the caret jumps and blinks on and off instead of gliding and fading), `ui.startScreen` (`{"value": true\|false}`; no value reads it; on, the default, the desktop and web apps start on File › Home when launched with nothing to open, #766), `ui.alignmentGuides` (`{"value": true\|false}`; no value reads it; on, the default, moving a picture or shape snaps it to the page, the margins and other objects, with guide lines while dragging; Alt moves freely), `ui.quickAccessToolbar` (`{"value": true\|false}`; no value toggles it; false takes the Quick Access Toolbar out of the title bar, as its menu's Hide Quick Access Toolbar does, and true brings it back, #1945), `ui.focusRibbon` (`{"value": true\|false}`; no value toggles it; in Focus, true drops the ribbon down over the page, as the bar's ⋯ does, and false puts it away, as a click on the page does; outside Focus it stays put away, #1678), `ui.contextMenu` (opens the page's context menu below the caret, as Shift+F10 does), `ui.paneDock` (`{"pane": "navigation"\|"clipboard"\|"styles"\|"styleInspector"\|"accessibility"\|"altText"\|"formatShape"\|"restrictEditing"\|"comments"\|"smartArtText", "dock"?: "left"\|"right"\|"float", "before"?: pane\|null}`; no `dock` reads it; docks or floats a task pane as its menu does, kept between runs; Styles is `view.stylesPaneDock`, #1756; `before` puts the pane directly before that pane in the order, outside it on their edge, as a drop over a docked pane's outer third does, and `null` after every pane; the order is kept between runs)…) |
|
||||
| `engine.execute` | `command`, `params`, `doc?` | the command's result (any id from `engine.commands`, including UI commands `ui.tab`, `ui.dialog` (`{"name": dialog, "part"?: table style part}`; `part` opens New / Modify Table Style (`newTableStyle` / `modifyTableStyle`) on that part: `wholeTable`, `headerRow`, `firstColumn`, `bandedRows`, `nwCell`… as `table.modifyStyle` names them), `ui.backstage`, `ui.zotero.*` (see `docs/zotero.md`), `ui.language` (`{"value": "auto"\|"en"\|"zh-hans"\|"zh-hant"\|"ja"\|"pt-br"\|"es"\|"uk"\|"sr"\|"sr-latn"\|"et"\|"tr"\|"ru"\|"pl"\|"de"\|"it"\|"cs"\|"hu"\|"fr"}`), `ui.theme` (`{"value": "system"\|"light"\|"dark"}`; `system` follows the OS appearance, light when it reports none), `ui.interfaceSize` (`{"value": "auto"}` or `{"percent": 50–300}`, clamped; no value reads it; `auto` keeps the system display scale, a percentage is absolute, so 100 is one screen pixel per point (#822); the result's `percent` is the size drawn at and `auto` whether it follows the system; document zoom stays `view.zoom`), `ui.animations` (`{"value": "system"\|"on"\|"off"}`; no value reads it; `system` follows the system's animation setting, which the desktop app reads on Windows, macOS, Linux and the BSDs and the web app from `prefers-reduced-motion`, and means on where the system doesn't say; the result's `enabled` is what the setting comes to and `system` what the system reports, `null` when it doesn't say), `ui.fileBrowser` (desktop: `{"purpose": "document"\|"picture"\|"recipients"\|"save"}` opens WordCraft's own file browser, for when the system file dialog doesn't show up, #576), `ui.saveAsTemplate` (File › Save As › Save as Word Template: the Save dialog for a `.dotx`, starting in the user templates folder; the result's `dir` is that folder, `null` on the web, #2852), `ui.caretAnimation` (`{"value": true\|false}`; no value reads it; off, the caret jumps and blinks on and off instead of gliding and fading), `ui.startScreen` (`{"value": true\|false}`; no value reads it; on, the default, the desktop and web apps start on File › Home when launched with nothing to open, #766), `ui.displayServer` (`{"value": "auto"\|"wayland"\|"x11"}`; no value reads it; File › Options › Advanced › Linux display server, which the desktop app reads on Linux and the BSDs at its next launch; `WORDCRAFT_WAYLAND` overrides it, #2184), `ui.alignmentGuides` (`{"value": true\|false}`; no value reads it; on, the default, moving a picture or shape snaps it to the page, the margins and other objects, with guide lines while dragging; Alt moves freely), `ui.quickAccessToolbar` (`{"value": true\|false}`; no value toggles it; false takes the Quick Access Toolbar out of the title bar, as its menu's Hide Quick Access Toolbar does, and true brings it back, #1945), `ui.focusRibbon` (`{"value": true\|false}`; no value toggles it; in Focus, true drops the ribbon down over the page, as the bar's ⋯ does, and false puts it away, as a click on the page does; outside Focus it stays put away, #1678), `ui.contextMenu` (opens the page's context menu below the caret, as Shift+F10 does), `ui.paneDock` (`{"pane": "navigation"\|"clipboard"\|"styles"\|"styleInspector"\|"accessibility"\|"altText"\|"formatShape"\|"restrictEditing"\|"comments"\|"smartArtText", "dock"?: "left"\|"right"\|"float", "before"?: pane\|null}`; no `dock` reads it; docks or floats a task pane as its menu does, kept between runs; Styles is `view.stylesPaneDock`, #1756; `before` puts the pane directly before that pane in the order, outside it on their edge, as a drop over a docked pane's outer third does, and `null` after every pane; the order is kept between runs)…) |
|
||||
| `engine.commands` | — | every command: id, label, location, shortcut, params, enabled |
|
||||
| `document.inspect` | `text?`, `doc?` | blocks (text, style, runs, lists, tables), parts, sections, selection, pages |
|
||||
| `ui.inspect` | — | UI state, view, dialog, window size, page rects on screen, caret, comment balloons (`balloons`: id + screen rect; `balloon`: the selected one), perf; `document`: the active document's id; `documents`: the open documents (`id`, `title`, `path`, `dirty`, `active`) |
|
||||
|
||||
+3
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user