mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 20:27:05 -06:00
Merge pull request #4497 from dylanmtaylor/chore/remove-windows7
Remove the experimental Windows 7 build
This commit is contained in:
@@ -35,7 +35,6 @@ winsafe = { version = "0.0.29", default-features = false, features = ["kernel",
|
||||
# DirectWrite's system font collection and GDI's font list (the desktop app's src/system_fonts.rs,
|
||||
# shared): fonts that font services such as Adobe Fonts load in place (#579, #1258). MIT OR
|
||||
# Apache-2.0.
|
||||
[target.'cfg(all(windows, not(target_vendor = "win7")))'.dependencies]
|
||||
windows = { version = "0.62", features = ["Win32_Graphics_DirectWrite", "Win32_Graphics_Gdi"] }
|
||||
|
||||
# CoreText's font manager (the desktop app's src/mac_fonts.rs, shared): fonts that apps and font
|
||||
|
||||
@@ -46,7 +46,7 @@ mod perf;
|
||||
#[path = "../../vectorcraft/src/prefs_dir.rs"]
|
||||
mod prefs_dir;
|
||||
/// The desktop app's DirectWrite font lister, shared: exports and MCP find the same fonts.
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
#[path = "../../vectorcraft/src/system_fonts.rs"]
|
||||
mod system_fonts;
|
||||
|
||||
@@ -162,7 +162,7 @@ fn help() -> Result<(), String> {
|
||||
|
||||
fn main() -> ExitCode {
|
||||
// Before any font lookup: the fonts font services load (#579).
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
system_fonts::install();
|
||||
#[cfg(target_os = "macos")]
|
||||
mac_fonts::install();
|
||||
|
||||
@@ -13,8 +13,6 @@ workspace = true
|
||||
default = ["wgpu", "accessibility"]
|
||||
wgpu = ["eframe/wgpu", "dep:winsafe"]
|
||||
accessibility = ["eframe/accesskit"]
|
||||
# Build with --no-default-features --features windows7 and the win7 Rust target.
|
||||
windows7 = ["eframe/glow"]
|
||||
|
||||
[dependencies]
|
||||
vectorcraft-engine = { workspace = true, features = ["bundled-fonts"] }
|
||||
@@ -37,9 +35,6 @@ image = { workspace = true }
|
||||
webbrowser = { workspace = true }
|
||||
# The window's monitors, size and position (the same winit eframe runs on).
|
||||
winit = { version = "0.30", default-features = false }
|
||||
|
||||
# The win7 target retains the viewer fallback; craft-print's WinRT PDF renderer needs newer Windows.
|
||||
[target.'cfg(all(not(target_arch = "wasm32"), not(target_vendor = "win7")))'.dependencies]
|
||||
craft-print = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
@@ -56,7 +51,6 @@ winsafe = { version = "0.0.29", default-features = false, features = ["user"], o
|
||||
# Win32_UI_WindowsAndMessaging has `MSG` and `WM_POINTERDOWN`, which src/windows_stylus.rs uses to
|
||||
# read a pen's barrel button from its pointer messages. Win32_UI_Input_Pointer has
|
||||
# `GetPointerPenInfo`, which it uses to read the pen's pressure and which end of the pen is down.
|
||||
[target.'cfg(all(windows, not(target_vendor = "win7")))'.dependencies]
|
||||
windows = { version = "0.62", features = ["Win32_Graphics_DirectWrite", "Win32_Graphics_Gdi", "Win32_UI_Input_Pointer", "Win32_UI_WindowsAndMessaging"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
|
||||
@@ -12,12 +12,12 @@ pub(crate) fn native_input_hook(app: &VectorcraftApp) -> Option<vectorcraft_ui_e
|
||||
app.services.native_menu.is_some().then(|| std::sync::Arc::new(mac_menu::raw_input_hook) as vectorcraft_ui_egui::NativeInputHook)
|
||||
}
|
||||
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn native_input_hook(_: &VectorcraftApp) -> Option<vectorcraft_ui_egui::NativeInputHook> {
|
||||
Some(std::sync::Arc::new(windows_stylus::raw_input_hook))
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", all(windows, not(target_vendor = "win7")))))]
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
pub(crate) fn native_input_hook(_: &VectorcraftApp) -> Option<vectorcraft_ui_egui::NativeInputHook> {
|
||||
None
|
||||
}
|
||||
@@ -43,7 +43,7 @@ impl eframe::App for App {
|
||||
mac_menu::raw_input_hook(raw);
|
||||
}
|
||||
// A pen contact with the barrel button held presses the secondary button (Windows Ink).
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
windows_stylus::raw_input_hook(raw);
|
||||
file_access::unconfined(|| self.app.raw_input_hook(ctx, raw));
|
||||
}
|
||||
@@ -67,15 +67,10 @@ impl eframe::App for App {
|
||||
discard_marked_text();
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "windows7"))]
|
||||
fn on_exit(&mut self) {
|
||||
file_access::unconfined(|| save_prefs(&self.app));
|
||||
end_before_teardown(self.frames);
|
||||
}
|
||||
#[cfg(feature = "windows7")]
|
||||
fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) {
|
||||
file_access::unconfined(|| save_prefs(&self.app));
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
||||
@@ -272,7 +272,7 @@ pub(crate) fn services(parent: Parent, ui: egui::Context) -> Services {
|
||||
#[cfg(target_os = "macos")]
|
||||
stylus: mac_tablet::start(),
|
||||
// Windows reads it from the pen's pointer messages (`windows_stylus`).
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
stylus: Some(windows_stylus::start()),
|
||||
read: Some(Box::new(|p: &str| std::fs::read(p).map_err(|e| e.to_string()))),
|
||||
write: Some(Box::new(write_file)),
|
||||
|
||||
@@ -15,13 +15,6 @@
|
||||
//! keyboard isn't confined.
|
||||
#![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")]
|
||||
|
||||
#[cfg(all(feature = "windows7", any(feature = "wgpu", feature = "accessibility")))]
|
||||
compile_error!("windows7 requires --no-default-features (wgpu and accessibility must be disabled)");
|
||||
#[cfg(all(windows, feature = "windows7", not(target_vendor = "win7")))]
|
||||
compile_error!("windows7 requires --target x86_64-win7-windows-msvc; the ordinary Windows target still imports newer APIs");
|
||||
#[cfg(all(target_vendor = "win7", not(feature = "windows7")))]
|
||||
compile_error!("the win7 target requires --no-default-features --features windows7");
|
||||
|
||||
mod automation_args;
|
||||
mod clipboard;
|
||||
#[cfg(feature = "wgpu")]
|
||||
@@ -50,7 +43,7 @@ mod open_documents;
|
||||
mod portal_save;
|
||||
mod prefs_dir;
|
||||
mod printing;
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
mod system_fonts;
|
||||
mod tablet;
|
||||
mod window;
|
||||
@@ -190,7 +183,7 @@ fn main() -> std::process::ExitCode {
|
||||
let logger = logging::install();
|
||||
vectorcraft_ui_egui::i18n::detect_system_lang_in_background();
|
||||
// Before the first font scan (the app's start): the fonts font services load (#579).
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
system_fonts::install();
|
||||
#[cfg(target_os = "macos")]
|
||||
mac_fonts::install();
|
||||
@@ -295,7 +288,7 @@ fn main() -> std::process::ExitCode {
|
||||
// With Windows Ink, winit reports a pen as a touch and drops its barrel button and which end
|
||||
// is down; the message hook notes each pointer contact and whether it starts with the barrel
|
||||
// button held, and writes the pen's end into the pen feed (`windows_stylus`).
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
let options = {
|
||||
use winit::platform::windows::EventLoopBuilderExtWindows as _;
|
||||
let mut options = options;
|
||||
@@ -436,10 +429,6 @@ fn main() -> std::process::ExitCode {
|
||||
let (loss, ctx) = (graphics_loss.clone(), cc.egui_ctx.clone());
|
||||
rs.device.set_device_lost_callback(move |reason, msg| loss.report(&ctx, format!("{reason:?}: {msg}")));
|
||||
}
|
||||
#[cfg(feature = "windows7")]
|
||||
{
|
||||
app.graphics_adapter = Some("OpenGL (Windows 7 compatibility)".into());
|
||||
}
|
||||
app.custom_titlebar = custom_titlebar;
|
||||
if let Some(port) = control_port {
|
||||
let rx = control_server::start(port, cc.egui_ctx.clone());
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
//! Desktop printer transport. VectorCraft composes every page and copy before submission;
|
||||
//! craft-print owns discovery and spooling. Windows 7 keeps its viewer-based transport because
|
||||
//! Windows.Data.Pdf is unavailable there.
|
||||
#[cfg(not(all(windows, any(feature = "windows7", target_vendor = "win7"))))]
|
||||
//! craft-print owns discovery and spooling.
|
||||
use std::process::Command;
|
||||
use vectorcraft_ui_egui::print::{PrintJob, PrintService, Printer};
|
||||
|
||||
#[cfg(all(windows, any(feature = "windows7", target_vendor = "win7")))]
|
||||
mod windows7;
|
||||
#[cfg(all(windows, any(feature = "windows7", target_vendor = "win7")))]
|
||||
use windows7 as system;
|
||||
|
||||
pub struct SystemPrint;
|
||||
impl PrintService for SystemPrint {
|
||||
fn printers(&mut self) -> Vec<Printer> {
|
||||
@@ -36,7 +29,6 @@ impl PrintService for SystemPrint {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(windows, any(feature = "windows7", target_vendor = "win7"))))]
|
||||
mod system {
|
||||
use super::{Command, PrintJob, Printer};
|
||||
pub fn printers() -> Vec<Printer> {
|
||||
@@ -95,7 +87,7 @@ mod system {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, windows, not(any(feature = "windows7", target_vendor = "win7"))))]
|
||||
#[cfg(all(test, windows))]
|
||||
mod tests {
|
||||
use super::system::setup_command;
|
||||
use std::ffi::OsStr;
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
//! The legacy viewer print verb is needed on Windows 7, where Windows.Data.Pdf is absent.
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use vectorcraft_ui_egui::print::{PrintJob, Printer};
|
||||
|
||||
/// Where a job's PDF waits for the spooler: a fresh file in the temp folder. Files of jobs more
|
||||
/// than an hour old (the print verb reads them after Print returns) are removed first.
|
||||
fn job_file(pdf: &[u8]) -> Result<PathBuf, String> {
|
||||
let dir = std::env::temp_dir().join("vectorcraft-print");
|
||||
std::fs::create_dir_all(&dir).map_err(|e| format!("can't make {}: {e}", dir.display()))?;
|
||||
for e in std::fs::read_dir(&dir).into_iter().flatten().flatten() {
|
||||
let old = e.metadata().and_then(|m| m.modified()).ok().and_then(|t| t.elapsed().ok()).is_some_and(|age| age.as_secs() > 3600);
|
||||
if old {
|
||||
// Another job may still be reading it: leave it for next time.
|
||||
let _ = std::fs::remove_file(e.path());
|
||||
}
|
||||
}
|
||||
let stamp = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_nanos()).unwrap_or_default();
|
||||
let path = dir.join(format!("job-{}-{stamp}.pdf", std::process::id()));
|
||||
// The viewer reads asynchronously, so this cannot use immediate drop cleanup. Never
|
||||
// truncate an existing path, and remove incomplete files or refused launches immediately.
|
||||
use std::io::Write as _;
|
||||
let mut file =
|
||||
std::fs::OpenOptions::new().write(true).create_new(true).open(&path).map_err(|e| format!("can't create {}: {e}", path.display()))?;
|
||||
if let Err(e) = file.write_all(pdf).and_then(|_| file.flush()) {
|
||||
drop(file);
|
||||
let _ = std::fs::remove_file(&path);
|
||||
return Err(format!("can't write {}: {e}", path.display()));
|
||||
}
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Run `c` → its output, or its error message.
|
||||
fn output(c: &mut Command) -> Result<String, String> {
|
||||
let program = c.get_program().to_string_lossy().to_string();
|
||||
let out = c.output().map_err(|e| format!("can't run {program}: {e}"))?;
|
||||
if out.status.success() {
|
||||
return Ok(String::from_utf8_lossy(&out.stdout).into_owned());
|
||||
}
|
||||
let err = String::from_utf8_lossy(&out.stderr).trim().to_string();
|
||||
Err(if err.is_empty() { format!("{program} failed ({})", out.status) } else { err })
|
||||
}
|
||||
|
||||
/// PowerShell's `<Default>\t<Name>` lines → the printers.
|
||||
fn windows_printers(text: &str) -> Vec<Printer> {
|
||||
text.lines()
|
||||
.filter_map(|l| l.trim_end_matches('\r').split_once('\t'))
|
||||
.filter(|(_, name)| !name.trim().is_empty())
|
||||
.map(|(default, name)| Printer { name: name.trim().to_string(), default: default.trim().eq_ignore_ascii_case("true") })
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// `s` as a PowerShell single-quoted string.
|
||||
fn ps_quote(s: &str) -> String {
|
||||
format!("'{}'", s.replace('\'', "''"))
|
||||
}
|
||||
|
||||
fn powershell(script: &str) -> Command {
|
||||
use std::os::windows::process::CommandExt as _;
|
||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||
let mut c = Command::new(
|
||||
std::path::PathBuf::from(std::env::var_os("SystemRoot").unwrap_or_else(|| "C:\\Windows".into()))
|
||||
.join("System32")
|
||||
.join("WindowsPowerShell")
|
||||
.join("v1.0")
|
||||
.join("powershell.exe"),
|
||||
);
|
||||
c.args([
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-Command",
|
||||
&format!("$ErrorActionPreference = 'Stop'; [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false); {script}"),
|
||||
])
|
||||
.creation_flags(CREATE_NO_WINDOW);
|
||||
c
|
||||
}
|
||||
|
||||
pub fn printers() -> Vec<Printer> {
|
||||
let script = "Get-WmiObject Win32_Printer | ForEach-Object { \"$($_.Default)`t$($_.Name)\" }";
|
||||
output(&mut powershell(script)).map(|t| windows_printers(&t)).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn submit(job: &PrintJob, file: &std::path::Path) -> Result<String, String> {
|
||||
// The PDF viewer's print verb reads the file after this returns: it stays for a while.
|
||||
let path = ps_quote(&file.to_string_lossy());
|
||||
let script = match job.printer {
|
||||
Some(p) => format!(
|
||||
"Start-Process -FilePath {path} -Verb PrintTo -ArgumentList {} -WindowStyle Hidden",
|
||||
ps_quote(&format!("\"{}\"", p.replace('"', "")))
|
||||
),
|
||||
None => format!("Start-Process -FilePath {path} -Verb Print -WindowStyle Hidden"),
|
||||
};
|
||||
output(&mut powershell(&script)).map(|_| String::new()).map_err(|e| format!("no app prints PDF files here ({e})"))
|
||||
}
|
||||
|
||||
pub fn setup(_printer: Option<&str>) -> Result<(), String> {
|
||||
let mut command = {
|
||||
let mut command = Command::new("control.exe");
|
||||
command.arg("printers");
|
||||
command
|
||||
};
|
||||
command.spawn().map(|_| ()).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub fn print(job: &PrintJob) -> Result<String, String> {
|
||||
let file = job_file(job.pdf)?;
|
||||
let result = submit(job, &file);
|
||||
if result.is_err() {
|
||||
// No viewer accepted the launch, so no asynchronous reader needs this document.
|
||||
let _ = std::fs::remove_file(file);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn powershell_lines_and_quotes_are_preserved() {
|
||||
assert_eq!(windows_printers("True\tOffice Laser\r\nFalse\t "), [Printer { name: "Office Laser".into(), default: true }]);
|
||||
assert_eq!(ps_quote("it's.pdf"), "'it''s.pdf'");
|
||||
}
|
||||
#[test]
|
||||
fn job_files_hold_the_pdf_for_the_async_viewer() {
|
||||
let path = job_file(b"%PDF-1.7 test").unwrap();
|
||||
assert_eq!(std::fs::read(&path).unwrap(), b"%PDF-1.7 test");
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -115,8 +115,6 @@ pub(crate) fn native_options(custom_titlebar: bool) -> eframe::NativeOptions {
|
||||
.with_title_shown(false)
|
||||
.with_icon(app_icon())
|
||||
.with_app_id("ai.storyteller.vectorcraft"),
|
||||
#[cfg(feature = "windows7")]
|
||||
renderer: eframe::Renderer::Glow,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
//! mouse message that Windows did not make from a pen or a touch
|
||||
//! (<https://learn.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages>)
|
||||
//! clears the feed.
|
||||
#![cfg_attr(not(all(windows, not(target_vendor = "win7"))), allow(dead_code))]
|
||||
#![cfg_attr(not(windows), allow(dead_code))]
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::sync::OnceLock;
|
||||
@@ -68,7 +68,7 @@ pub fn start() -> StylusFeed {
|
||||
/// message it takes from the thread's queue, before dispatching it: notes each pointer contact's id
|
||||
/// and whether the barrel button was held, and writes the pen's pressure and end into the feed.
|
||||
/// It never consumes the message.
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
#[allow(unsafe_code)]
|
||||
pub fn msg_hook(msg: *const std::ffi::c_void) -> bool {
|
||||
use windows::Win32::UI::Input::Pointer::{GetPointerPenInfo, POINTER_FLAG_INCONTACT, POINTER_PEN_INFO};
|
||||
|
||||
Reference in New Issue
Block a user