aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-01-29 23:46:05 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-01-30 12:30:57 +0400
commitfefb1cccd6c3c7b92f8d4021fe5e38609760d1e4 (patch)
tree7fdd33d40a4532cc66e10428bbae0b5eb3f6c289
parentdeef52519a237a290bee6ded8aec7dbaac1e8e9a (diff)
downloadniri-fefb1cccd6c3c7b92f8d4021fe5e38609760d1e4.tar.gz
niri-fefb1cccd6c3c7b92f8d4021fe5e38609760d1e4.tar.bz2
niri-fefb1cccd6c3c7b92f8d4021fe5e38609760d1e4.zip
foreign_toplevel: Update the focused window last
-rw-r--r--src/protocols/foreign_toplevel.rs225
1 files changed, 128 insertions, 97 deletions
diff --git a/src/protocols/foreign_toplevel.rs b/src/protocols/foreign_toplevel.rs
index f628dc0d..f1e37fd2 100644
--- a/src/protocols/foreign_toplevel.rs
+++ b/src/protocols/foreign_toplevel.rs
@@ -12,7 +12,9 @@ use smithay::reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
};
use smithay::wayland::compositor::with_states;
-use smithay::wayland::shell::xdg::{ToplevelStateSet, XdgToplevelSurfaceData};
+use smithay::wayland::shell::xdg::{
+ ToplevelStateSet, XdgToplevelSurfaceData, XdgToplevelSurfaceRoleAttributes,
+};
use wayland_protocols_wlr::foreign_toplevel::v1::server::{
zwlr_foreign_toplevel_handle_v1, zwlr_foreign_toplevel_manager_v1,
};
@@ -89,6 +91,10 @@ pub fn refresh(state: &mut State) {
});
// Handle new and existing windows.
+ //
+ // Save the focused window for last, this way when the focus changes, we will first deactivate
+ // the previous window and only then activate the newly focused window.
+ let mut focused = None;
state.niri.layout.with_windows(|window, output| {
let wl_surface = window.toplevel().wl_surface();
@@ -100,125 +106,150 @@ pub fn refresh(state: &mut State) {
.lock()
.unwrap();
- let has_focus = state.niri.keyboard_focus.as_ref() == Some(wl_surface);
- let states = to_state_vec(&role.current.states, has_focus);
-
- match protocol_state.toplevels.entry(wl_surface.clone()) {
- Entry::Occupied(entry) => {
- // Existing window, check if anything changed.
- let data = entry.into_mut();
-
- let mut new_title = None;
- if data.title != role.title {
- data.title = role.title.clone();
- new_title = role.title.as_deref();
+ if state.niri.keyboard_focus.as_ref() == Some(wl_surface) {
+ focused = Some((window.clone(), output.cloned()));
+ } else {
+ refresh_toplevel(protocol_state, wl_surface, &role, output, false);
+ }
+ });
+ });
- if new_title.is_none() {
- error!("toplevel title changed to None");
- }
- }
+ // Finally, refresh the focused window.
+ if let Some((window, output)) = focused {
+ let wl_surface = window.toplevel().wl_surface();
- let mut new_app_id = None;
- if data.app_id != role.app_id {
- data.app_id = role.app_id.clone();
- new_app_id = role.app_id.as_deref();
+ with_states(wl_surface, |states| {
+ let role = states
+ .data_map
+ .get::<XdgToplevelSurfaceData>()
+ .unwrap()
+ .lock()
+ .unwrap();
- if new_app_id.is_none() {
- error!("toplevel app_id changed to None");
- }
- }
+ refresh_toplevel(protocol_state, wl_surface, &role, output.as_ref(), true);
+ });
+ }
+}
- let mut states_changed = false;
- if data.states != states {
- data.states = states;
- states_changed = true;
- }
+fn refresh_toplevel(
+ protocol_state: &mut ForeignToplevelManagerState,
+ wl_surface: &WlSurface,
+ role: &XdgToplevelSurfaceRoleAttributes,
+ output: Option<&Output>,
+ has_focus: bool,
+) {
+ let states = to_state_vec(&role.current.states, has_focus);
+
+ match protocol_state.toplevels.entry(wl_surface.clone()) {
+ Entry::Occupied(entry) => {
+ // Existing window, check if anything changed.
+ let data = entry.into_mut();
+
+ let mut new_title = None;
+ if data.title != role.title {
+ data.title = role.title.clone();
+ new_title = role.title.as_deref();
+
+ if new_title.is_none() {
+ error!("toplevel title changed to None");
+ }
+ }
- let mut output_changed = false;
- if data.output.as_ref() != output {
- data.output = output.cloned();
- output_changed = true;
- }
+ let mut new_app_id = None;
+ if data.app_id != role.app_id {
+ data.app_id = role.app_id.clone();
+ new_app_id = role.app_id.as_deref();
- let something_changed = new_title.is_some()
- || new_app_id.is_some()
- || states_changed
- || output_changed;
+ if new_app_id.is_none() {
+ error!("toplevel app_id changed to None");
+ }
+ }
- if something_changed {
- for (instance, outputs) in &mut data.instances {
- if let Some(new_title) = new_title {
- instance.title(new_title.to_owned());
- }
- if let Some(new_app_id) = new_app_id {
- instance.app_id(new_app_id.to_owned());
- }
- if states_changed {
- instance.state(
- data.states.iter().flat_map(|x| x.to_ne_bytes()).collect(),
- );
- }
- if output_changed {
- for wl_output in outputs.drain(..) {
- instance.output_leave(&wl_output);
- }
- if let Some(output) = &data.output {
- if let Some(client) = instance.client() {
- for wl_output in output.client_outputs(&client) {
- instance.output_enter(&wl_output);
- outputs.push(wl_output);
- }
- }
- }
- }
- }
- }
+ let mut states_changed = false;
+ if data.states != states {
+ data.states = states;
+ states_changed = true;
+ }
- for (instance, outputs) in &mut data.instances {
- // Clean up dead wl_outputs.
- outputs.retain(|x| x.is_alive());
+ let mut output_changed = false;
+ if data.output.as_ref() != output {
+ data.output = output.cloned();
+ output_changed = true;
+ }
- let mut send_done = something_changed;
+ let something_changed =
+ new_title.is_some() || new_app_id.is_some() || states_changed || output_changed;
- // If the client bound any more wl_outputs, send enter for them.
+ if something_changed {
+ for (instance, outputs) in &mut data.instances {
+ if let Some(new_title) = new_title {
+ instance.title(new_title.to_owned());
+ }
+ if let Some(new_app_id) = new_app_id {
+ instance.app_id(new_app_id.to_owned());
+ }
+ if states_changed {
+ instance.state(data.states.iter().flat_map(|x| x.to_ne_bytes()).collect());
+ }
+ if output_changed {
+ for wl_output in outputs.drain(..) {
+ instance.output_leave(&wl_output);
+ }
if let Some(output) = &data.output {
if let Some(client) = instance.client() {
for wl_output in output.client_outputs(&client) {
- if !outputs.iter().any(|x| x == &wl_output) {
- instance.output_enter(&wl_output);
- outputs.push(wl_output);
- send_done = true;
- }
+ instance.output_enter(&wl_output);
+ outputs.push(wl_output);
}
}
}
-
- if send_done {
- instance.done();
- }
}
}
- Entry::Vacant(entry) => {
- // New window, start tracking it.
- let mut data = ToplevelData {
- title: role.title.clone(),
- app_id: role.app_id.clone(),
- states,
- output: output.cloned(),
- instances: HashMap::new(),
- };
-
- for manager in &protocol_state.instances {
- if let Some(client) = manager.client() {
- data.add_instance::<State>(&protocol_state.display, &client, manager);
+ }
+
+ for (instance, outputs) in &mut data.instances {
+ // Clean up dead wl_outputs.
+ outputs.retain(|x| x.is_alive());
+
+ let mut send_done = something_changed;
+
+ // If the client bound any more wl_outputs, send enter for them.
+ if let Some(output) = &data.output {
+ if let Some(client) = instance.client() {
+ for wl_output in output.client_outputs(&client) {
+ if !outputs.iter().any(|x| x == &wl_output) {
+ instance.output_enter(&wl_output);
+ outputs.push(wl_output);
+ send_done = true;
+ }
}
}
+ }
- entry.insert(data);
+ if send_done {
+ instance.done();
}
}
- });
- });
+ }
+ Entry::Vacant(entry) => {
+ // New window, start tracking it.
+ let mut data = ToplevelData {
+ title: role.title.clone(),
+ app_id: role.app_id.clone(),
+ states,
+ output: output.cloned(),
+ instances: HashMap::new(),
+ };
+
+ for manager in &protocol_state.instances {
+ if let Some(client) = manager.client() {
+ data.add_instance::<State>(&protocol_state.display, &client, manager);
+ }
+ }
+
+ entry.insert(data);
+ }
+ }
}
impl ToplevelData {