From 92873b06ed71232457ef5a9107cfb01733eddb25 Mon Sep 17 00:00:00 2001
From: Andreas Johansson <ndreas@users.noreply.github.com>
Date: Tue, 20 Nov 2018 21:21:58 +0100
Subject: [PATCH] Handle removed wl_outputs (#719)

* Move the event managent to the closure

In preparation of more events not relating to the SeatManager being
captured.

* Handle wl_output remove events

In some cases, wl_outputs can be removed without the compositor
notifying the surfaces using leave/enter events. This breaks the DPI and
resize stuff since the windows' list of monitors were not updated.

Now, wl_output removals are handled and windows are updated accordingly.

* Add changelog entry for disappearing wl_outputs

* Clearer changelog message for wl_output removal changes
---
 CHANGELOG.md                             |  1 +
 src/platform/linux/wayland/event_loop.rs | 84 +++++++++++++-----------
 src/platform/linux/wayland/window.rs     | 21 +++++-
 3 files changed, 66 insertions(+), 40 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f23ee23..0fd80b8e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
 - On X11, fixed a segfault when using virtual monitors with XRandR.
 - Derive `Ord` and `PartialOrd` for `VirtualKeyCode` enum.
 - On Windows, fix issue where hovering or dropping a non file item would create a panic.
+- On Wayland, fix resizing and DPI calculation when a `wl_output` is removed without sending a `leave` event to the `wl_surface`, such as disconnecting a monitor from a laptop.
 
 # Version 0.18.0 (2018-11-07)
 
diff --git a/src/platform/linux/wayland/event_loop.rs b/src/platform/linux/wayland/event_loop.rs
index edf35f07..3f9ac7d2 100644
--- a/src/platform/linux/wayland/event_loop.rs
+++ b/src/platform/linux/wayland/event_loop.rs
@@ -123,11 +123,26 @@ impl EventsLoop {
             },
         };
 
+        let cb_store = store.clone();
+
         let env = Environment::from_display_with_cb(
             &display,
             &mut event_queue,
             move |event, registry| { 
-                seat_manager.receive(event, registry)
+                match event {
+                    GlobalEvent::New { id, ref interface, version } => {
+                        if interface == "wl_seat" {
+                            seat_manager.add_seat(id, version, registry)
+                        }
+                    },
+                    GlobalEvent::Removed { id, ref interface } => {
+                        if interface == "wl_seat" {
+                            seat_manager.remove_seat(id)
+                        } else if interface == "wl_output" {
+                            cb_store.lock().unwrap().remove_output(id);
+                        }
+                    },
+                }
             },
         ).unwrap();
 
@@ -286,47 +301,38 @@ struct SeatManager {
 }
 
 impl SeatManager {
-    fn receive(&mut self, evt: GlobalEvent, registry: Proxy<wl_registry::WlRegistry>) {
+    fn add_seat(&mut self, id: u32, version: u32, registry: Proxy<wl_registry::WlRegistry>) {
         use self::wl_registry::RequestsTrait as RegistryRequests;
-        use self::wl_seat::RequestsTrait as SeatRequests;
-        match evt {
-            GlobalEvent::New {
-                id,
-                ref interface,
-                version,
-            } if interface == "wl_seat" =>
-            {
-                use std::cmp::min;
+        use std::cmp::min;
 
-                let mut seat_data = SeatData {
-                    sink: self.sink.clone(),
-                    store: self.store.clone(),
-                    pointer: None,
-                    keyboard: None,
-                    touch: None,
-                    events_loop_proxy: self.events_loop_proxy.clone(),
-                    modifiers_tracker: Arc::new(Mutex::new(ModifiersState::default())),
-                };
-                let seat = registry
-                    .bind(min(version, 5), id, move |seat| {
-                        seat.implement(move |event, seat| {
-                            seat_data.receive(event, seat)
-                        }, ())
-                    })
-                    .unwrap();
-                self.store.lock().unwrap().new_seat(&seat);
-                self.seats.lock().unwrap().push((id, seat));
+        let mut seat_data = SeatData {
+            sink: self.sink.clone(),
+            store: self.store.clone(),
+            pointer: None,
+            keyboard: None,
+            touch: None,
+            events_loop_proxy: self.events_loop_proxy.clone(),
+            modifiers_tracker: Arc::new(Mutex::new(ModifiersState::default())),
+        };
+        let seat = registry
+            .bind(min(version, 5), id, move |seat| {
+                seat.implement(move |event, seat| {
+                    seat_data.receive(event, seat)
+                }, ())
+            })
+            .unwrap();
+        self.store.lock().unwrap().new_seat(&seat);
+        self.seats.lock().unwrap().push((id, seat));
+    }
+
+    fn remove_seat(&mut self, id: u32) {
+        use self::wl_seat::RequestsTrait as SeatRequests;
+        let mut seats = self.seats.lock().unwrap();
+        if let Some(idx) = seats.iter().position(|&(i, _)| i == id) {
+            let (_, seat) = seats.swap_remove(idx);
+            if seat.version() >= 5 {
+                seat.release();
             }
-            GlobalEvent::Removed { id, ref interface } if interface == "wl_seat" => {
-                let mut seats = self.seats.lock().unwrap();
-                if let Some(idx) = seats.iter().position(|&(i, _)| i == id) {
-                    let (_, seat) = seats.swap_remove(idx);
-                    if seat.version() >= 5 {
-                        seat.release();
-                    }
-                }
-            }
-            _ => (),
         }
     }
 }
diff --git a/src/platform/linux/wayland/window.rs b/src/platform/linux/wayland/window.rs
index b7d5d6d9..01c6de12 100644
--- a/src/platform/linux/wayland/window.rs
+++ b/src/platform/linux/wayland/window.rs
@@ -148,6 +148,7 @@ impl Window {
             frame: Arc::downgrade(&frame),
             current_dpi: 1,
             new_dpi: None,
+            monitors: monitor_list.clone(),
         });
         evlp.evq.borrow_mut().sync_roundtrip().unwrap();
 
@@ -330,7 +331,8 @@ struct InternalWindow {
     kill_switch: Arc<Mutex<bool>>,
     frame: Weak<Mutex<SWindow<ConceptFrame>>>,
     current_dpi: i32,
-    new_dpi: Option<i32>
+    new_dpi: Option<i32>,
+    monitors: Arc<Mutex<MonitorList>>
 }
 
 pub struct WindowStore {
@@ -376,6 +378,18 @@ impl WindowStore {
         }
     }
 
+    pub fn remove_output(&mut self, output: u32) {
+        for window in &mut self.windows {
+            let dpi = window.monitors.lock().unwrap().output_disappeared(output);
+            if window.surface.version() >= 3 {
+                // without version 3 we can't be dpi aware
+                window.new_dpi = Some(dpi);
+                window.surface.set_buffer_scale(dpi);
+                window.need_refresh = true;
+            }
+        }
+    }
+
     fn dpi_change(&mut self, surface: &Proxy<wl_surface::WlSurface>, new: i32) {
         for window in &mut self.windows {
             if surface.equals(&window.surface) {
@@ -456,4 +470,9 @@ impl MonitorList {
             None
         }
     }
+
+    fn output_disappeared(&mut self, id: u32) -> i32 {
+        self.monitors.retain(|m| m.get_native_identifier() != id);
+        self.compute_hidpi_factor()
+    }
 }