Compare commits

..
4 Commits
Author SHA1 Message Date
haraldandClaude Opus 5.5 9571e2121c chore(release): bump version to 0.3.6
Build & Publish Release / Build Packages & Publish to Gitea (push) Successful in 8m51s
Unter WSL standardmäßig X11 statt Wayland.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:50:24 +02:00
haraldandClaude Opus 5.5 4a7627e461 fix(wsl): unter WSL standardmäßig X11 statt Wayland verwenden
Unter WSLg trennt der Wayland-Compositor die Verbindung, kurz nachdem die App
ein Bild über Vulkan übergibt ("Connection reset by peer", ExitFailure). Über
X11 (Xwayland) läuft die App stabil – deshalb funktionierte sie als root, wo
der Wayland-Socket nicht gefunden wird und winit auf X11 ausweicht.

- WSL-Erkennung (WSL_DISTRO_NAME/WSL_INTEROP, /proc/sys/kernel/osrelease)
- Unter WSL mit gesetztem DISPLAY wird die Ereignisschleife mit X11 erzeugt
- INK_NOTES_DISPLAY=x11|wayland übersteuert die Wahl

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:42:21 +02:00
haraldandClaude Opus 5.5 cb11b1f828 chore(release): bump version to 0.3.5
Build & Publish Release / Build Packages & Publish to Gitea (push) Successful in 9m34s
Kein Absturz mehr ohne nutzbare Grafikkarte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:17:49 +02:00
haraldandClaude Opus 5.5 89005f718e fix(grafik): kein Absturz ohne nutzbare Darstellungsmodi
Ohne Zugriff auf die Grafikkarte (z. B. Benutzer nicht in der Gruppe render)
lieferte EGL/Zink zunächst Darstellungsmodi, später eine leere Liste; die
nächste Neueinrichtung der Surface ließ wgpu abstürzen ("Requested present
mode Mailbox is not in the list of supported present modes: []").

- Adapter der Reihe nach prüfen (GPU, dann Software-Renderer); nur Adapter
  mit Formaten und Darstellungsmodi für das Fenster werden genommen
- Darstellungsmodus: Mailbox, sonst Fifo, sonst der erste angebotene
- Bei Größenänderung/verlorener Surface Fähigkeiten neu abfragen statt blind
  neu einzurichten; ohne Modi wird nicht eingerichtet (einmalige Meldung)
- Ohne nutzbaren Adapter verständliche Meldung (Gruppe render, /dev/dri,
  Software-Renderer, WGPU_BACKEND) statt Panic; WGPU_BACKEND wird beachtet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:14:43 +02:00
7 changed files with 217 additions and 32 deletions

No files matched your search

Generated
+1 -1
View File
@@ -1048,7 +1048,7 @@ dependencies = [
[[package]] [[package]]
name = "ink-notes" name = "ink-notes"
version = "0.3.4" version = "0.3.6"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"egui", "egui",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "ink-notes" name = "ink-notes"
version = "0.3.4" version = "0.3.6"
edition = "2021" edition = "2021"
description = "Latenzoptimierte Vektor-Notizbuch- und Inking-Anwendung für Linux" description = "Latenzoptimierte Vektor-Notizbuch- und Inking-Anwendung für Linux"
repository = "https://gitea.pansi.eu/harald/ink-notes" repository = "https://gitea.pansi.eu/harald/ink-notes"
+2
View File
@@ -3,6 +3,8 @@
Latenzoptimierte Vektor-Notizbuch- und Inking-Anwendung für Linux (Zorin OS 18 / Ubuntu 24.04). Latenzoptimierte Vektor-Notizbuch- und Inking-Anwendung für Linux (Zorin OS 18 / Ubuntu 24.04).
### Highlights: ### Highlights:
- **Stabiler Start**: Kein Absturz mehr, wenn die Grafikkarte nicht nutzbar ist (z. B. fehlende Rechte auf /dev/dri); Software-Renderer als Ausweichlösung und verständliche Fehlermeldung
- **WSL (WSLg)**: Läuft unter WSL automatisch über X11 statt Wayland, da WSLg die Wayland-Verbindung abbricht; übersteuerbar mit `INK_NOTES_DISPLAY=x11|wayland`
- **Wichtige Fehlerbehebung**: Nach einem Neustart auf einer späteren Seite konnte Weiterschreiben den bisherigen Inhalt dieser Seite überschreiben (betrifft v0.2.3 – v0.3.2) - **Wichtige Fehlerbehebung**: Nach einem Neustart auf einer späteren Seite konnte Weiterschreiben den bisherigen Inhalt dieser Seite überschreiben (betrifft v0.2.3 – v0.3.2)
- **Neue Werkzeugleiste**: Schmale senkrechte Leiste mit Symbol-Knöpfen, links oder rechts andockbar; selten genutzte Funktionen im ☰-Menü, das Blatt wird daneben zentriert - **Neue Werkzeugleiste**: Schmale senkrechte Leiste mit Symbol-Knöpfen, links oder rechts andockbar; selten genutzte Funktionen im ☰-Menü, das Blatt wird daneben zentriert
- **Einheitliche Dialoge**: Deckende Fenster ohne doppelte Titel, Seitenauswahl mit Vorschaubildern, größere Bedienelemente in allen Popovern - **Einheitliche Dialoge**: Deckende Fenster ohne doppelte Titel, Seitenauswahl mit Vorschaubildern, größere Bedienelemente in allen Popovern
+92 -27
View File
@@ -87,8 +87,35 @@ pub fn spline_vertices_to_ndc(
} }
/// Verwaltet den GPU-Zustand, Surface, Pipelines und Buffer für das Inking. /// Verwaltet den GPU-Zustand, Surface, Pipelines und Buffer für das Inking.
/// Meldung, wenn kein Grafikadapter das Fenster darstellen kann.
pub const NO_USABLE_GPU_MESSAGE: &str = "Keine nutzbare Grafikschnittstelle gefunden.\n\
Häufigste Ursache: Der Benutzer darf nicht auf die Grafikkarte zugreifen (/dev/dri).\n\
Prüfen mit `ls -l /dev/dri` und `groups`; ggf. `sudo usermod -aG render,video $USER`\n\
und neu anmelden. Alternativ einen Software-Renderer installieren (Paket mesa-vulkan-drivers)\n\
oder die Schnittstelle festlegen, z. B. `WGPU_BACKEND=vulkan ink-notes`.";
/// Wählt den Darstellungsmodus: Mailbox (geringste Latenz), sonst Fifo, sonst den ersten
/// angebotenen. `None`, wenn keiner angeboten wird. `INK_FORCE_FIFO` erzwingt Fifo.
pub fn choose_present_mode(available: &[wgpu::PresentMode]) -> Option<wgpu::PresentMode> {
let force_fifo = std::env::var("INK_FORCE_FIFO").is_ok();
let preferred: &[wgpu::PresentMode] = if force_fifo {
&[wgpu::PresentMode::Fifo]
} else {
&[wgpu::PresentMode::Mailbox, wgpu::PresentMode::Fifo]
};
preferred
.iter()
.copied()
.find(|m| available.contains(m))
.or_else(|| available.first().copied())
}
pub struct CanvasState { pub struct CanvasState {
pub surface: wgpu::Surface<'static>, pub surface: wgpu::Surface<'static>,
/// Verwendeter Grafikadapter (für erneute Abfrage der Surface-Fähigkeiten bei Größenänderung)
adapter: wgpu::Adapter,
/// Meldung „keine Darstellungsmodi“ wurde bereits ausgegeben
presentation_warned: bool,
pub device: wgpu::Device, pub device: wgpu::Device,
pub queue: wgpu::Queue, pub queue: wgpu::Queue,
pub config: wgpu::SurfaceConfiguration, pub config: wgpu::SurfaceConfiguration,
@@ -149,23 +176,47 @@ impl CanvasState {
let viewport = let viewport =
PageViewport::calculate(size.width as f32, size.height as f32, Self::VIEWPORT_MARGIN); PageViewport::calculate(size.width as f32, size.height as f32, Self::VIEWPORT_MARGIN);
// 1. WGPU Instance & Surface // 1. WGPU Instance & Surface (Grafikschnittstelle per WGPU_BACKEND wählbar, z. B. "vulkan")
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), backends: wgpu::util::backend_bits_from_env().unwrap_or(wgpu::Backends::all()),
..Default::default() ..Default::default()
}); });
let surface = instance.create_surface(window.clone())?; let surface = instance.create_surface(window.clone())?;
// 2. Adapter auswählen (hohe Performance bevorzugt) // 2. Adapter auswählen: zuerst die GPU, dann der Software-Renderer (z. B. lavapipe).
let adapter = instance // Ein Adapter taugt nur, wenn er für dieses Fenster Formate und Darstellungsmodi
.request_adapter(&wgpu::RequestAdapterOptions { // anbietet – fehlt z. B. der Zugriff auf /dev/dri, ist die Liste leer.
power_preference: wgpu::PowerPreference::HighPerformance, let mut chosen = None;
compatible_surface: Some(&surface), for force_fallback_adapter in [false, true] {
force_fallback_adapter: false, let Some(adapter) = instance
}) .request_adapter(&wgpu::RequestAdapterOptions {
.await power_preference: wgpu::PowerPreference::HighPerformance,
.ok_or("Kein passender GPU-Adapter gefunden")?; compatible_surface: Some(&surface),
force_fallback_adapter,
})
.await
else {
continue;
};
let info = adapter.get_info();
let caps = surface.get_capabilities(&adapter);
match choose_present_mode(&caps.present_modes) {
Some(mode) if !caps.formats.is_empty() && !caps.alpha_modes.is_empty() => {
eprintln!(
"[Grafik] Adapter: {} ({:?}), Darstellungsmodus {:?}",
info.name, info.backend, mode
);
chosen = Some((adapter, caps, mode));
break;
}
_ => eprintln!(
"[Grafik] Adapter {} ({:?}) bietet für das Fenster keine Darstellung an, versuche den nächsten.",
info.name, info.backend
),
}
}
let (adapter, surface_caps, present_mode) = chosen.ok_or(NO_USABLE_GPU_MESSAGE)?;
// 3. Device & Queue erstellen // 3. Device & Queue erstellen
let (device, queue) = adapter let (device, queue) = adapter
@@ -179,8 +230,7 @@ impl CanvasState {
) )
.await?; .await?;
// 4. Zero-Latency-Pufferung: Mailbox mit Fallback Fifo & Frame-Latency 1 // 4. Zero-Latency-Pufferung (Mailbox, sonst Fifo) & Frame-Latency 1
let surface_caps = surface.get_capabilities(&adapter);
let surface_format = surface_caps let surface_format = surface_caps
.formats .formats
.iter() .iter()
@@ -188,20 +238,6 @@ impl CanvasState {
.find(|f| f.is_srgb()) .find(|f| f.is_srgb())
.unwrap_or(surface_caps.formats[0]); .unwrap_or(surface_caps.formats[0]);
let present_mode = if std::env::var("INK_FORCE_FIFO").is_ok() {
eprintln!("Erzwinge PresentMode::Fifo (via INK_FORCE_FIFO)");
wgpu::PresentMode::Fifo
} else if surface_caps
.present_modes
.contains(&wgpu::PresentMode::Mailbox)
{
eprintln!("Verwende Zero-Latency PresentMode::Mailbox");
wgpu::PresentMode::Mailbox
} else {
eprintln!("Mailbox nicht verfügbar, Fallback auf PresentMode::Fifo");
wgpu::PresentMode::Fifo
};
let config = wgpu::SurfaceConfiguration { let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format, format: surface_format,
@@ -369,6 +405,8 @@ impl CanvasState {
Ok(Self { Ok(Self {
surface, surface,
adapter,
presentation_warned: false,
device, device,
queue, queue,
config, config,
@@ -452,6 +490,23 @@ impl CanvasState {
self.size = new_size; self.size = new_size;
self.config.width = new_size.width; self.config.width = new_size.width;
self.config.height = new_size.height; self.config.height = new_size.height;
// Fähigkeiten neu abfragen: Ein nicht (mehr) angebotener Modus führt in wgpu zum Absturz
let caps = self.surface.get_capabilities(&self.adapter);
if !caps.present_modes.contains(&self.config.present_mode) {
match choose_present_mode(&caps.present_modes) {
Some(mode) => self.config.present_mode = mode,
None => {
if !self.presentation_warned {
eprintln!(
"[Grafik] Fenster kann derzeit nicht dargestellt werden (keine Darstellungsmodi). {}",
NO_USABLE_GPU_MESSAGE
);
self.presentation_warned = true;
}
return;
}
}
}
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
self.recalculate_viewport(); self.recalculate_viewport();
} }
@@ -858,6 +913,16 @@ impl CanvasState {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn test_choose_present_mode() {
use wgpu::PresentMode::{Fifo, Immediate, Mailbox};
assert_eq!(choose_present_mode(&[Fifo, Mailbox]), Some(Mailbox));
assert_eq!(choose_present_mode(&[Fifo, Immediate]), Some(Fifo));
assert_eq!(choose_present_mode(&[Immediate]), Some(Immediate));
// Keine Darstellungsmodi (z. B. kein Zugriff auf /dev/dri): kein Absturz, sondern None
assert_eq!(choose_present_mode(&[]), None);
}
#[test] #[test]
fn test_pixel_to_ndc_mapping() { fn test_pixel_to_ndc_mapping() {
let width = 800.0; let width = 800.0;
+1
View File
@@ -8,6 +8,7 @@ pub mod export;
pub mod filter; pub mod filter;
pub mod geometry; pub mod geometry;
pub mod history; pub mod history;
pub mod platform;
pub mod selection; pub mod selection;
pub mod settings; pub mod settings;
pub mod spline; pub mod spline;
+24 -3
View File
@@ -9,8 +9,9 @@ use winit::{
event::{ event::{
ElementState, Event, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent, ElementState, Event, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent,
}, },
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoopBuilder},
keyboard::{Key, ModifiersState, NamedKey}, keyboard::{Key, ModifiersState, NamedKey},
platform::{wayland::EventLoopBuilderExtWayland, x11::EventLoopBuilderExtX11},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@@ -20,6 +21,7 @@ use ink_notes::canvas::CanvasState;
use ink_notes::document::{Notebook, PageBackground}; use ink_notes::document::{Notebook, PageBackground};
use ink_notes::export::{self, export_notebook_to_pdf, ExportStrategy}; use ink_notes::export::{self, export_notebook_to_pdf, ExportStrategy};
use ink_notes::geometry::{self, RecognizedShape}; use ink_notes::geometry::{self, RecognizedShape};
use ink_notes::platform::{self, DisplayBackend};
use ink_notes::selection::{ use ink_notes::selection::{
render_selection_overlay, DragMode, SelectionHandleHit, SelectionState, render_selection_overlay, DragMode, SelectionHandleHit, SelectionState,
}; };
@@ -137,7 +139,20 @@ fn commit_stroke_to_notebook(
} }
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let event_loop = EventLoop::new()?; // Anzeigesystem wählen: Unter WSL (WSLg) bricht Wayland ab, X11 (Xwayland) läuft stabil
let mut event_loop_builder = EventLoopBuilder::new();
match platform::display_backend_from_env() {
DisplayBackend::X11 => {
eprintln!("[Anzeige] Verwende X11 (unter WSL Standard; übersteuerbar mit INK_NOTES_DISPLAY=wayland)");
event_loop_builder.with_x11();
}
DisplayBackend::Wayland => {
eprintln!("[Anzeige] Verwende Wayland (INK_NOTES_DISPLAY=wayland)");
event_loop_builder.with_wayland();
}
DisplayBackend::Automatic => {}
}
let event_loop = event_loop_builder.build()?;
let window = Arc::new( let window = Arc::new(
WindowBuilder::new() WindowBuilder::new()
.with_title("ink-notes (Latenzoptimiertes Linux-Inking)") .with_title("ink-notes (Latenzoptimiertes Linux-Inking)")
@@ -145,7 +160,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.build(&event_loop)?, .build(&event_loop)?,
); );
let mut canvas = pollster::block_on(CanvasState::new(window.clone()))?; let mut canvas = match pollster::block_on(CanvasState::new(window.clone())) {
Ok(canvas) => canvas,
Err(err) => {
eprintln!("[Grafik-Fehler] {err}");
std::process::exit(1);
}
};
let mut tablet_manager = match octotablet::builder::Builder::default().build_shared(&window) { let mut tablet_manager = match octotablet::builder::Builder::default().build_shared(&window) {
Ok(mgr) => { Ok(mgr) => {
+96
View File
@@ -0,0 +1,96 @@
//! Erkennung der Laufzeitumgebung und Wahl des Anzeigesystems (X11 oder Wayland).
//!
//! Unter WSL (WSLg) trennt der Wayland-Compositor die Verbindung, sobald die App ein Bild
//! über Vulkan übergibt ("Connection reset by peer"). Über X11 (Xwayland) läuft sie stabil,
//! daher wird unter WSL standardmäßig X11 verwendet. `INK_NOTES_DISPLAY=x11|wayland`
//! übersteuert die Wahl.
/// Anzeigesystem, mit dem die Ereignisschleife erzeugt wird.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DisplayBackend {
/// Automatische Wahl von winit (Wayland, falls verfügbar, sonst X11)
Automatic,
X11,
Wayland,
}
/// Wählt das Anzeigesystem.
///
/// - `override_value`: Inhalt von `INK_NOTES_DISPLAY` (`x11` oder `wayland`, sonst ignoriert)
/// - `is_wsl`: läuft unter WSL
/// - `has_x11_display`: `DISPLAY` ist gesetzt (X11/Xwayland erreichbar)
pub fn choose_display_backend(
override_value: Option<&str>,
is_wsl: bool,
has_x11_display: bool,
) -> DisplayBackend {
match override_value
.map(|v| v.trim().to_ascii_lowercase())
.as_deref()
{
Some("x11") => return DisplayBackend::X11,
Some("wayland") => return DisplayBackend::Wayland,
_ => {}
}
if is_wsl && has_x11_display {
DisplayBackend::X11
} else {
DisplayBackend::Automatic
}
}
/// Läuft das Programm unter WSL (Windows-Subsystem für Linux)?
pub fn is_wsl() -> bool {
if std::env::var_os("WSL_DISTRO_NAME").is_some() || std::env::var_os("WSL_INTEROP").is_some() {
return true;
}
std::fs::read_to_string("/proc/sys/kernel/osrelease")
.map(|release| release.to_ascii_lowercase().contains("microsoft"))
.unwrap_or(false)
}
/// Wahl anhand der aktuellen Umgebung.
pub fn display_backend_from_env() -> DisplayBackend {
choose_display_backend(
std::env::var("INK_NOTES_DISPLAY").ok().as_deref(),
is_wsl(),
std::env::var_os("DISPLAY").is_some_and(|d| !d.is_empty()),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_choose_display_backend() {
// WSL mit X11-Display: X11 (Wayland unter WSLg bricht ab)
assert_eq!(
choose_display_backend(None, true, true),
DisplayBackend::X11
);
// WSL ohne X11-Display: winit entscheiden lassen
assert_eq!(
choose_display_backend(None, true, false),
DisplayBackend::Automatic
);
// Natives Linux: winit entscheiden lassen
assert_eq!(
choose_display_backend(None, false, true),
DisplayBackend::Automatic
);
// Übersteuerung
assert_eq!(
choose_display_backend(Some("wayland"), true, true),
DisplayBackend::Wayland
);
assert_eq!(
choose_display_backend(Some(" X11 "), false, true),
DisplayBackend::X11
);
assert_eq!(
choose_display_backend(Some("quatsch"), true, true),
DisplayBackend::X11
);
}
}