fix: close remaining gaps found by the completeness audit
Addresses the partial ports listed in docs/rewrite/notes/review-completeness-codex.md (details in docs/rewrite/notes/gaps-X2.md): - Flatpak: the exit watcher no longer consumes the host command; a shared FlatpakSignalHandle lets closing a surface still SIGHUP the host process group. - Child environment is byte-preserving end to end (ghostty_os::EnvMap of OsStrings) and Snap paths are filtered like filterSnapPaths. - argv handling no longer panics on non-UTF-8 arguments. - URI percent-decoding keeps raw bytes; OSC 7 pwd is passed through without lossy conversion. - Keybinding comparison/hashing uses full Unicode case folding from a table generated from CaseFolding.txt (Unicode 17.0.0). - Link regexes set Oniguruma's whole-search retry limit through the new minimal ghostty-onig crate (one SAFETY-commented FFI call). - OSC 9 NUL-capacity check ordering matches Zig. - Wayland registry binds/clears globals advertised after startup. - Custom shaders may pass sampler2D to helper functions (naga adapter). - +list-themes preview renders OSC 8 links, styled underlines and follows color-scheme reports. Also includes the inspector's integration edits in shared files (renderer state hook, termio VT recording, core/apprt-gtk wiring), X1's config/input/cli additions for completion generation, and a fix for a race in the termio backpressure test (it wrote before `stty raw` applied, so the cooked line discipline dropped bytes past MAX_CANON). Test Plan: - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings
This commit is contained in:
1 parent
d1a5e55198
commit
2aa2a12de0
61 files changed
+4210
-298
No files matched your search
@@ -10,6 +10,7 @@ ghostty-apprt.workspace = true
|
||||
ghostty-config.workspace = true
|
||||
ghostty-input.workspace = true
|
||||
ghostty-datastruct.workspace = true
|
||||
ghostty-inspector.workspace = true
|
||||
ghostty-os.workspace = true
|
||||
ghostty-vt.workspace = true
|
||||
log = "0.4"
|
||||
@@ -28,13 +29,14 @@ half = "2"
|
||||
libc = "0.2.190"
|
||||
signal-hook = "0.4.5"
|
||||
libloading = "0.9.0"
|
||||
dear-imgui-glow = "0.19"
|
||||
glow = "0.18"
|
||||
|
||||
[build-dependencies]
|
||||
glib-build-tools = "0.22"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.11"
|
||||
glow = "0.18"
|
||||
|
||||
[lints.rust]
|
||||
unsafe_code = "deny"
|
||||
|
||||
@@ -9,6 +9,9 @@ pub mod config_errors_dialog;
|
||||
pub mod debug_warning;
|
||||
pub mod dialog;
|
||||
pub mod global_shortcuts;
|
||||
pub mod imgui_widget;
|
||||
pub mod inspector_widget;
|
||||
pub mod inspector_window;
|
||||
pub mod key_state_overlay;
|
||||
pub mod resize_overlay;
|
||||
pub mod search_overlay;
|
||||
|
||||
@@ -38,6 +38,7 @@ use gtk::{gdk, gio, glib};
|
||||
use crate::class::application::Application;
|
||||
use crate::class::clipboard_confirmation_dialog::ClipboardConfirmationDialog;
|
||||
use crate::class::config::Config;
|
||||
use crate::class::inspector_window::InspectorWindow;
|
||||
use crate::class::key_state_overlay::KeyStateOverlay;
|
||||
use crate::class::resize_overlay::ResizeOverlay;
|
||||
use crate::class::search_overlay::SearchOverlay;
|
||||
@@ -246,18 +247,66 @@ impl Surface {
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Show/hide the inspector.
|
||||
///
|
||||
/// TODO(inspector): the inspector window is deferred; this is a no-op
|
||||
/// that reports success like Zig does after (not) creating a window.
|
||||
/// Show/hide the inspector (Zig: `controlInspector`).
|
||||
pub fn control_inspector(&self, value: Inspector) -> bool {
|
||||
log::debug!("inspector action {value:?} ignored (inspector deferred)");
|
||||
// Let's see if we have an inspector already.
|
||||
let priv_ = self.imp();
|
||||
if let Some(inspector) = priv_.inspector.upgrade() {
|
||||
match value {
|
||||
Inspector::Show => {}
|
||||
// Our weak ref will set our private value to null
|
||||
Inspector::Toggle | Inspector::Hide => inspector.destroy(),
|
||||
}
|
||||
} else {
|
||||
match value {
|
||||
Inspector::Toggle | Inspector::Show => {
|
||||
let inspector = InspectorWindow::new(self);
|
||||
inspector.present_window();
|
||||
priv_.inspector.set(Some(&inspector));
|
||||
}
|
||||
Inspector::Hide => {}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Redraw our inspector, if there is one associated with this surface.
|
||||
/// TODO(inspector): deferred.
|
||||
pub fn redraw_inspector(&self) {}
|
||||
pub fn redraw_inspector(&self) {
|
||||
if let Some(v) = self.imp().inspector.upgrade() {
|
||||
v.queue_render();
|
||||
}
|
||||
}
|
||||
|
||||
/// Debug input replay: click in the inspector window. Returns false if
|
||||
/// there is no inspector.
|
||||
pub fn debug_inspector_click(&self, x: f64, y: f64) -> bool {
|
||||
let Some(v) = self.imp().inspector.upgrade() else {
|
||||
return false;
|
||||
};
|
||||
v.debug_click(x, y);
|
||||
true
|
||||
}
|
||||
|
||||
/// Debug input replay: scroll in the inspector window. Returns false if
|
||||
/// there is no inspector.
|
||||
pub fn debug_inspector_scroll(&self, x: f64, y: f64, dy: f64) -> bool {
|
||||
let Some(v) = self.imp().inspector.upgrade() else {
|
||||
return false;
|
||||
};
|
||||
v.debug_scroll(x, y, dy);
|
||||
true
|
||||
}
|
||||
|
||||
/// Debug input replay: write the next rendered frame of the inspector
|
||||
/// window to `path` (PNG). Returns false if there is no inspector.
|
||||
pub fn debug_inspector_screenshot(&self, path: &str) -> bool {
|
||||
let Some(v) = self.imp().inspector.upgrade() else {
|
||||
return false;
|
||||
};
|
||||
v.debug_screenshot(path);
|
||||
true
|
||||
}
|
||||
|
||||
/// Handle a key sequence action from the apprt.
|
||||
pub fn key_sequence_action(&self, value: &KeySequence) {
|
||||
@@ -963,33 +1012,38 @@ impl Surface {
|
||||
self.imp().cursor_pos.get()
|
||||
}
|
||||
|
||||
pub fn default_termio_env(&self) -> Vec<(String, String)> {
|
||||
pub fn default_termio_env(&self) -> Vec<(std::ffi::OsString, std::ffi::OsString)> {
|
||||
let application = app();
|
||||
let mut env: Vec<(String, String)> = std::env::vars().collect();
|
||||
let remove = |env: &mut Vec<(String, String)>, k: &str| env.retain(|(n, _)| n != k);
|
||||
// Byte-preserving environment (Zig `internal_os.getEnvMap`).
|
||||
let mut env = ghostty_os::env::get_env_map();
|
||||
|
||||
remove(&mut env, "LANG");
|
||||
if let Some(language) = application.saved_language() {
|
||||
env.push(("LANG".to_owned(), language));
|
||||
env.insert("LANG", language);
|
||||
} else {
|
||||
env.remove("LANG");
|
||||
}
|
||||
|
||||
// Don't leak these GTK environment variables to child processes.
|
||||
remove(&mut env, "GDK_DEBUG");
|
||||
remove(&mut env, "GDK_DISABLE");
|
||||
remove(&mut env, "GSK_RENDERER");
|
||||
env.remove("GDK_DEBUG");
|
||||
env.remove("GDK_DISABLE");
|
||||
env.remove("GSK_RENDERER");
|
||||
|
||||
// Remove some environment variables that are set when Ghostty is
|
||||
// launched from a `.desktop` file, by D-Bus activation, or systemd.
|
||||
remove(&mut env, "GIO_LAUNCHED_DESKTOP_FILE");
|
||||
remove(&mut env, "GIO_LAUNCHED_DESKTOP_FILE_PID");
|
||||
remove(&mut env, "DBUS_STARTER_ADDRESS");
|
||||
remove(&mut env, "DBUS_STARTER_BUS_TYPE");
|
||||
remove(&mut env, "INVOCATION_ID");
|
||||
remove(&mut env, "JOURNAL_STREAM");
|
||||
remove(&mut env, "NOTIFY_SOCKET");
|
||||
env.remove("GIO_LAUNCHED_DESKTOP_FILE");
|
||||
env.remove("GIO_LAUNCHED_DESKTOP_FILE_PID");
|
||||
env.remove("DBUS_STARTER_ADDRESS");
|
||||
env.remove("DBUS_STARTER_BUS_TYPE");
|
||||
env.remove("INVOCATION_ID");
|
||||
env.remove("JOURNAL_STREAM");
|
||||
env.remove("NOTIFY_SOCKET");
|
||||
|
||||
// Snap path filtering (`build_config.snap`) is a build-time option
|
||||
// of the Zig build and is not supported here.
|
||||
// Unset environment varies set by snaps if we're running in a snap.
|
||||
// This allows Ghostty to further launch additional snaps. (Zig gates
|
||||
// this on the `snap` build option; running inside a snap implies it.)
|
||||
if env.get("SNAP").is_some() {
|
||||
ghostty_os::filter_snap_paths(&mut env);
|
||||
}
|
||||
|
||||
// This is a hack because it ties ourselves (optionally) to the
|
||||
// Window class. The right solution we should do is emit a signal
|
||||
@@ -999,16 +1053,20 @@ impl Surface {
|
||||
.ancestor(Window::static_type())
|
||||
.and_then(|w| w.downcast::<Window>().ok())
|
||||
{
|
||||
if let Err(e) = window.winproto().add_subprocess_env(&mut env) {
|
||||
// winproto only ever adds (UTF-8) variables.
|
||||
let mut extra: Vec<(String, String)> = Vec::new();
|
||||
if let Err(e) = window.winproto().add_subprocess_env(&mut extra) {
|
||||
log::warn!("winproto add_subprocess_env failed: {e}");
|
||||
}
|
||||
for (k, v) in extra {
|
||||
env.insert(k, v);
|
||||
}
|
||||
if window.is_quick_terminal() {
|
||||
env.retain(|(n, _)| n != "GHOSTTY_QUICK_TERMINAL");
|
||||
env.push(("GHOSTTY_QUICK_TERMINAL".to_owned(), "1".to_owned()));
|
||||
env.insert("GHOSTTY_QUICK_TERMINAL", "1");
|
||||
}
|
||||
}
|
||||
|
||||
env
|
||||
env.into_iter().collect()
|
||||
}
|
||||
|
||||
pub fn clipboard_request(&self, clipboard_type: Clipboard, state: ClipboardRequest) -> bool {
|
||||
@@ -1939,7 +1997,7 @@ impl RtSurface for RtSurfaceHandle {
|
||||
}
|
||||
}
|
||||
|
||||
fn default_termio_env(&self) -> Vec<(String, String)> {
|
||||
fn default_termio_env(&self) -> Vec<(std::ffi::OsString, std::ffi::OsString)> {
|
||||
self.gobj()
|
||||
.map(|s| s.default_termio_env())
|
||||
.unwrap_or_default()
|
||||
@@ -2365,6 +2423,9 @@ mod imp {
|
||||
/// The source that handles setting our child property.
|
||||
pub idle_rechild: Cell<Option<glib::SourceId>>,
|
||||
|
||||
/// A weak reference to an inspector window.
|
||||
pub inspector: glib::WeakRef<InspectorWindow>,
|
||||
|
||||
pub action_group: RefCell<Option<gio::SimpleActionGroup>>,
|
||||
pub vadj_handler: RefCell<Option<(gtk::Adjustment, glib::SignalHandlerId)>>,
|
||||
|
||||
@@ -2457,6 +2518,7 @@ mod imp {
|
||||
precision_scroll: Cell::new(false),
|
||||
progress_bar_timer: Cell::new(None),
|
||||
idle_rechild: Cell::new(None),
|
||||
inspector: glib::WeakRef::new(),
|
||||
action_group: RefCell::new(None),
|
||||
vadj_handler: RefCell::new(None),
|
||||
context: Cell::new(NewSurfaceContext::Window),
|
||||
@@ -2632,6 +2694,15 @@ mod imp {
|
||||
}
|
||||
|
||||
fn dispose(&self) {
|
||||
// The inspector window holds on to our core surface to
|
||||
// deactivate the inspector; Zig does that from a weak notify
|
||||
// during our dispose. Closing the window here makes the inspector
|
||||
// widget deactivate the inspector while the core surface is
|
||||
// still alive.
|
||||
if let Some(inspector) = self.inspector.upgrade() {
|
||||
inspector.destroy();
|
||||
}
|
||||
|
||||
if let Some((adj, id)) = self.vadj_handler.take() {
|
||||
adj.disconnect(id);
|
||||
}
|
||||
|
||||
@@ -1733,10 +1733,10 @@ impl Window {
|
||||
|
||||
/// Toggle the Ghostty inspector for the active surface.
|
||||
fn toggle_inspector(&self) {
|
||||
// TODO(inspector): deferred; the inspector is not ported yet.
|
||||
if let Some(surface) = self.get_active_surface() {
|
||||
log::debug!("toggle-inspector requested for {surface:?} (inspector deferred)");
|
||||
}
|
||||
let Some(surface) = self.get_active_surface() else {
|
||||
return;
|
||||
};
|
||||
surface.control_inspector(ghostty_apprt::action::Inspector::Toggle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
//! text hello world # IM commit of the rest of the line
|
||||
//! wait 200 # milliseconds
|
||||
//! screenshot /abs/out.png # dump the renderer's last frame (PNG)
|
||||
//! inspector-screenshot /abs/out.png # dump the next frame of the terminal
|
||||
//! # inspector window (PNG)
|
||||
//! inspector-click 100 20 # left click in the inspector (logical px)
|
||||
//! inspector-scroll 100 200 -3 # mouse wheel in the inspector
|
||||
//! quit # quit the application
|
||||
//! ```
|
||||
//!
|
||||
@@ -34,12 +38,15 @@ use crate::class::surface::Surface;
|
||||
use crate::class::window::Window;
|
||||
|
||||
/// One parsed command.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Step {
|
||||
Key(String),
|
||||
Text(String),
|
||||
Wait(u64),
|
||||
Screenshot(String),
|
||||
InspectorScreenshot(String),
|
||||
InspectorClick(f64, f64),
|
||||
InspectorScroll(f64, f64, f64),
|
||||
Quit,
|
||||
}
|
||||
|
||||
@@ -59,6 +66,31 @@ pub fn parse(script: &str) -> Result<Vec<Step>, String> {
|
||||
"text" if !rest.is_empty() => Step::Text(rest.to_owned()),
|
||||
"wait" => Step::Wait(rest.parse().map_err(|_| err("wait needs milliseconds"))?),
|
||||
"screenshot" if !rest.is_empty() => Step::Screenshot(rest.to_owned()),
|
||||
"inspector-screenshot" if !rest.is_empty() => {
|
||||
Step::InspectorScreenshot(rest.to_owned())
|
||||
}
|
||||
"inspector-click" => {
|
||||
let v: Vec<f64> = rest
|
||||
.split_whitespace()
|
||||
.map(str::parse)
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|_| err("inspector-click needs x y"))?;
|
||||
let [x, y] = v[..] else {
|
||||
return Err(err("inspector-click needs x y"));
|
||||
};
|
||||
Step::InspectorClick(x, y)
|
||||
}
|
||||
"inspector-scroll" => {
|
||||
let v: Vec<f64> = rest
|
||||
.split_whitespace()
|
||||
.map(str::parse)
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|_| err("inspector-scroll needs x y dy"))?;
|
||||
let [x, y, dy] = v[..] else {
|
||||
return Err(err("inspector-scroll needs x y dy"));
|
||||
};
|
||||
Step::InspectorScroll(x, y, dy)
|
||||
}
|
||||
"quit" => Step::Quit,
|
||||
_ => return Err(err(&format!("bad command {line:?}"))),
|
||||
});
|
||||
@@ -190,6 +222,30 @@ impl Runner {
|
||||
Some(s) => s.debug_screenshot(&path),
|
||||
None => log::warn!("debug input: no active surface"),
|
||||
},
|
||||
Step::InspectorClick(x, y) => match self.active_surface() {
|
||||
Some(s) => {
|
||||
if !s.debug_inspector_click(x, y) {
|
||||
log::warn!("debug input: the surface has no inspector");
|
||||
}
|
||||
}
|
||||
None => log::warn!("debug input: no active surface"),
|
||||
},
|
||||
Step::InspectorScroll(x, y, dy) => match self.active_surface() {
|
||||
Some(s) => {
|
||||
if !s.debug_inspector_scroll(x, y, dy) {
|
||||
log::warn!("debug input: the surface has no inspector");
|
||||
}
|
||||
}
|
||||
None => log::warn!("debug input: no active surface"),
|
||||
},
|
||||
Step::InspectorScreenshot(path) => match self.active_surface() {
|
||||
Some(s) => {
|
||||
if !s.debug_inspector_screenshot(&path) {
|
||||
log::warn!("debug input: the surface has no inspector");
|
||||
}
|
||||
}
|
||||
None => log::warn!("debug input: no active surface"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,8 +284,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_script() {
|
||||
let s =
|
||||
"# c\n\nkey ctrl+shift+t\ntext hello world\nwait 200\nscreenshot /tmp/a.png\nquit\n";
|
||||
let s = "# c\n\nkey ctrl+shift+t\ntext hello world\nwait 200\nscreenshot /tmp/a.png\ninspector-screenshot /tmp/i.png\ninspector-click 10 20\ninspector-scroll 1 2 -3\nquit\n";
|
||||
assert_eq!(
|
||||
parse(s).expect("valid script"),
|
||||
vec![
|
||||
@@ -237,6 +292,9 @@ mod tests {
|
||||
Step::Text("hello world".into()),
|
||||
Step::Wait(200),
|
||||
Step::Screenshot("/tmp/a.png".into()),
|
||||
Step::InspectorScreenshot("/tmp/i.png".into()),
|
||||
Step::InspectorClick(10.0, 20.0),
|
||||
Step::InspectorScroll(1.0, 2.0, -3.0),
|
||||
Step::Quit
|
||||
]
|
||||
);
|
||||
|
||||
@@ -81,6 +81,69 @@ struct State {
|
||||
xdg_wm_dialog_present: bool,
|
||||
/// Activation tokens requested but not yet `done`.
|
||||
pending_tokens: usize,
|
||||
/// The registry name of each global we bound (interface -> name), used
|
||||
/// to unset the global when the compositor removes it.
|
||||
bound_names: std::collections::HashMap<&'static str, u32>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Bind a newly advertised global if it is one we support
|
||||
/// (Zig `registryListener` `.global`).
|
||||
fn registry_global(
|
||||
&mut self,
|
||||
registry: &WlRegistry,
|
||||
qh: &QueueHandle<State>,
|
||||
name: u32,
|
||||
interface: &str,
|
||||
) {
|
||||
log::debug!("found global {interface}");
|
||||
|
||||
// We don't actually do anything with this other than checking
|
||||
// for its existence, so we process this separately.
|
||||
if interface == "xdg_wm_dialog_v1" {
|
||||
self.xdg_wm_dialog_present = true;
|
||||
return;
|
||||
}
|
||||
|
||||
macro_rules! try_bind {
|
||||
($field:ident, $t:ty) => {
|
||||
if interface == <$t as Proxy>::interface().name {
|
||||
log::debug!("matched {}", interface);
|
||||
self.$field = Some(registry.bind::<$t, _, _>(name, 1, qh, ()));
|
||||
self.bound_names
|
||||
.insert(<$t as Proxy>::interface().name, name);
|
||||
}
|
||||
};
|
||||
}
|
||||
try_bind!(kde_blur_manager, OrgKdeKwinBlurManager);
|
||||
try_bind!(kde_decoration_manager, OrgKdeKwinServerDecorationManager);
|
||||
try_bind!(kde_slide_manager, OrgKdeKwinSlideManager);
|
||||
try_bind!(xdg_activation, XdgActivationV1);
|
||||
}
|
||||
|
||||
/// A global is suddenly no longer available: destroy and unset it as the
|
||||
/// protocol mandates (Zig `registryListener` `.global_remove`).
|
||||
fn registry_global_remove(&mut self, name: u32) {
|
||||
macro_rules! try_remove {
|
||||
($field:ident, $t:ty) => {
|
||||
let iface = <$t as Proxy>::interface().name;
|
||||
if self.bound_names.get(iface) == Some(&name) {
|
||||
self.bound_names.remove(iface);
|
||||
// Dropping the proxy releases it client-side.
|
||||
self.$field = None;
|
||||
}
|
||||
};
|
||||
}
|
||||
try_remove!(kde_blur_manager, OrgKdeKwinBlurManager);
|
||||
try_remove!(kde_decoration_manager, OrgKdeKwinServerDecorationManager);
|
||||
try_remove!(kde_slide_manager, OrgKdeKwinSlideManager);
|
||||
if self.bound_names.get("xdg_activation_v1") == Some(&name) {
|
||||
self.bound_names.remove("xdg_activation_v1");
|
||||
if let Some(a) = self.xdg_activation.take() {
|
||||
a.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Shared {
|
||||
@@ -106,6 +169,14 @@ impl Shared {
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface names of the globals we bind.
|
||||
const BOUND_INTERFACES: [&str; 4] = [
|
||||
"org_kde_kwin_blur_manager",
|
||||
"org_kde_kwin_server_decoration_manager",
|
||||
"org_kde_kwin_slide_manager",
|
||||
"xdg_activation_v1",
|
||||
];
|
||||
|
||||
fn bind<I: Proxy + 'static>(
|
||||
globals: &wayland_client::globals::GlobalList,
|
||||
qh: &QueueHandle<State>,
|
||||
@@ -162,6 +233,22 @@ impl App {
|
||||
state.kde_decoration_manager = bind(&globals, &qh);
|
||||
state.kde_slide_manager = bind(&globals, &qh);
|
||||
state.xdg_activation = bind(&globals, &qh);
|
||||
globals.contents().with_list(|list| {
|
||||
for g in list {
|
||||
let bound = match g.interface.as_str() {
|
||||
"org_kde_kwin_blur_manager" => state.kde_blur_manager.is_some(),
|
||||
"org_kde_kwin_server_decoration_manager" => {
|
||||
state.kde_decoration_manager.is_some()
|
||||
}
|
||||
"org_kde_kwin_slide_manager" => state.kde_slide_manager.is_some(),
|
||||
"xdg_activation_v1" => state.xdg_activation.is_some(),
|
||||
_ => false,
|
||||
};
|
||||
if bound && let Some(name) = BOUND_INTERFACES.iter().find(|n| **n == g.interface) {
|
||||
state.bound_names.insert(name, g.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Do another round-trip to get the default decoration mode
|
||||
queue
|
||||
@@ -530,14 +617,21 @@ fn entered_monitor(host: &dyn WindowHost, monitor: &gdk::Monitor) {
|
||||
|
||||
impl Dispatch<WlRegistry, GlobalListContents> for State {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
_: &WlRegistry,
|
||||
_: wayland_client::protocol::wl_registry::Event,
|
||||
state: &mut Self,
|
||||
registry: &WlRegistry,
|
||||
event: wayland_client::protocol::wl_registry::Event,
|
||||
_: &GlobalListContents,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
qh: &QueueHandle<Self>,
|
||||
) {
|
||||
// Globals appearing/disappearing after init are not tracked.
|
||||
use wayland_client::protocol::wl_registry::Event;
|
||||
match event {
|
||||
Event::Global {
|
||||
name, interface, ..
|
||||
} => state.registry_global(registry, qh, name, &interface),
|
||||
Event::GlobalRemove { name } => state.registry_global_remove(name),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user