Skip to content

Commit

Permalink
Add focus stealing prevention to MinimalWindowManager (#3693)
Browse files Browse the repository at this point in the history
Closes #2586.

Adds a new constructor to `FloatingWindowManagerPolicy` to control focus
stealing prevention. This in a nutshell stops new windows from being
focused and raised. When used with xdg-activation-v1, this improves
security as external actors can't just steal focus by opening a new
window (in addition to the niceties xdg-activation-v1 adds to
usability).

TODO:
- [ ] ~Fix window offsetting. This is built off the position of the
focused window, so all windows after the second one open in the same
(x,y) position.~
Split off to #3695
- [ ] ~Fix windows closing in the order of opening instead of front to
back. see
#3693 (comment)
Moved to #3694
- [ ] ~Make sure Xwayland applications work properly. My focus has been
on Wayland applications so far.~
Xwayland doesn't seem to support xdg-activation at the moment?
- [x] Alt + tab predictably broken :/
Have to focus other applications before they work with alt + tab
- [x] Decorations are not pushed behind the focused window
- [ ] ~Need some way to focus applications launched via Mir
(Ctrl-Alt+t/T for example)~
Will be in its own PR (#3703)
  • Loading branch information
tarek-y-ismail authored Jan 20, 2025
2 parents ddc9f49 + a92721d commit 687c38a
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 9 deletions.
4 changes: 4 additions & 0 deletions debian/libmiral7.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -491,3 +491,7 @@ libmiral.so.7 libmiral7 #MINVER#
(c++)"miral::InputConfiguration::touchpad(miral::InputConfiguration::Touchpad const&)@MIRAL_5.1" 5.1.0
(c++)"miral::InputConfiguration::~InputConfiguration()@MIRAL_5.1" 5.1.0
(c++)"miral::WindowManagerTools::move_cursor_to(mir::geometry::generic::Point<float>)@MIRAL_5.1" 5.1.0
MIRAL_5.2@MIRAL_5.2 5.2.0
(c++)"miral::MinimalWindowManager::MinimalWindowManager(miral::WindowManagerTools const&, MirInputEventModifier, miral::FocusStealing)@MIRAL_5.0" 5.2.0
(c++)"miral::MinimalWindowManager::MinimalWindowManager(miral::WindowManagerTools const&, miral::FocusStealing)@MIRAL_5.0" 5.2.0
(c++)"miral::WindowManagementPolicy::~WindowManagementPolicy()@MIRAL_5.0" 5.2.0
12 changes: 11 additions & 1 deletion examples/example-server-lib/floating_window_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,17 @@ FloatingWindowManagerPolicy::FloatingWindowManagerPolicy(
std::shared_ptr<SplashSession> const& spinner,
miral::InternalClientLauncher const& launcher,
std::function<void()>& shutdown_hook) :
MinimalWindowManager(tools),
FloatingWindowManagerPolicy{tools, spinner, launcher, shutdown_hook, FocusStealing::allow}
{
}

FloatingWindowManagerPolicy::FloatingWindowManagerPolicy(
WindowManagerTools const& tools,
std::shared_ptr<SplashSession> const& spinner,
miral::InternalClientLauncher const& launcher,
std::function<void()>& shutdown_hook,
FocusStealing focus_stealing) :
MinimalWindowManager(tools, focus_stealing),
spinner{spinner},
decoration_provider{std::make_unique<DecorationProvider>()}
{
Expand Down
8 changes: 8 additions & 0 deletions examples/example-server-lib/floating_window_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ class FloatingWindowManagerPolicy : public miral::MinimalWindowManager
std::shared_ptr<SplashSession> const& spinner,
miral::InternalClientLauncher const& launcher,
std::function<void()>& shutdown_hook);

FloatingWindowManagerPolicy(
miral::WindowManagerTools const& tools,
std::shared_ptr<SplashSession> const& spinner,
miral::InternalClientLauncher const& launcher,
std::function<void()>& shutdown_hook,
miral::FocusStealing focus_stealing);

~FloatingWindowManagerPolicy();

virtual miral::WindowSpecification place_new_window(
Expand Down
14 changes: 13 additions & 1 deletion examples/miral-shell/shell_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "miral/minimal_window_manager.h"
#include "tiling_window_manager.h"
#include "floating_window_manager.h"
#include "wallpaper_config.h"
Expand Down Expand Up @@ -68,9 +69,10 @@ int main(int argc, char const* argv[])

SpinnerSplash spinner;
InternalClientLauncher launcher;
auto focus_stealing_prevention = FocusStealing::allow;
WindowManagerOptions window_managers
{
add_window_manager_policy<FloatingWindowManagerPolicy>("floating", spinner, launcher, shutdown_hook),
add_window_manager_policy<FloatingWindowManagerPolicy>("floating", spinner, launcher, shutdown_hook, focus_stealing_prevention),
add_window_manager_policy<TilingWindowManagerPolicy>("tiling", spinner, launcher),
};

Expand Down Expand Up @@ -132,13 +134,23 @@ int main(int argc, char const* argv[])
}
};

auto const to_focus_stealing = [](bool focus_stealing_prevention)
{
if (focus_stealing_prevention)
return FocusStealing::prevent;
else
return FocusStealing::allow;
};

return runner.run_with(
{
CursorTheme{"default:DMZ-White"},
WaylandExtensions{},
X11Support{},
ConfigureDecorations{},
pre_init(ConfigurationOption{[&](bool is_set)
{ focus_stealing_prevention = to_focus_stealing(is_set); },
"focus-stealing-prevention", "prevent focus stealing", false}),
window_managers,
display_configuration_options,
external_client_launcher,
Expand Down
17 changes: 17 additions & 0 deletions include/miral/miral/minimal_window_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,35 @@

namespace miral
{
enum class FocusStealing
{
prevent,
allow
};

/// Minimal implementation of a floating window management policy
/// \remark Since MirAL 2.5
class MinimalWindowManager : public WindowManagementPolicy
{
public:
explicit MinimalWindowManager(WindowManagerTools const& tools);

/// Allows shells to enable or disable focus stealing prevention.
/// \remark Since MirAL 5.2
explicit MinimalWindowManager(WindowManagerTools const& tools, FocusStealing focus_stealing);

/// Allows shells to change the modifer used to identify a window drag gesture
/// The default is mir_input_event_modifier_alt
/// \remark Since MirAL 3.7
MinimalWindowManager(WindowManagerTools const& tools, MirInputEventModifier pointer_drag_modifier);

/// Allows shells to to change the modifer used to identify a window drag
/// gesture and enable or disable focus stealing prevention.
/// The default drag modifier is mir_input_event_modifier_alt.
/// \remark Since MirAL 5.2
MinimalWindowManager(
WindowManagerTools const& tools, MirInputEventModifier pointer_drag_modifier, FocusStealing focus_stealing);

~MinimalWindowManager();

/// Honours the requested specification
Expand Down
2 changes: 1 addition & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set(MIRPLATFORM_ABI 30)

set(MIRAL_VERSION_MAJOR 5)
set(MIRAL_VERSION_MINOR 1)
set(MIRAL_VERSION_MINOR 2)
set(MIRAL_VERSION_PATCH 0)
set(MIRAL_VERSION ${MIRAL_VERSION_MAJOR}.${MIRAL_VERSION_MINOR}.${MIRAL_VERSION_PATCH})

Expand Down
10 changes: 9 additions & 1 deletion src/miral/application_selector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ void ApplicationSelector::advise_new_window(WindowInfo const& window_info)
focus_list.push_back(window_info.window());
}

void ApplicationSelector::advise_new_window(WindowInfo const& window_info, bool focused)
{
if(!focused && !focus_list.empty())
focus_list.insert(focus_list.end() - 1, window_info.window());
else
advise_new_window(window_info);
}

void ApplicationSelector::select(miral::Window const& window)
{
if (selected)
Expand Down Expand Up @@ -281,4 +289,4 @@ auto ApplicationSelector::find(Window window) -> std::vector<Window>::iterator
{
return window == other;
});
}
}
2 changes: 2 additions & 0 deletions src/miral/application_selector.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class ApplicationSelector
/// Called when a window is created
void advise_new_window(WindowInfo const&);

void advise_new_window(WindowInfo const&, bool focused);

/// Called when focus is given to a window.
void advise_focus_gained(WindowInfo const&);

Expand Down
10 changes: 10 additions & 0 deletions src/miral/basic_window_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,16 @@ void miral::BasicWindowManager::surface_ready(std::shared_ptr<scene::Surface> co
{
Locker lock{this};
policy->handle_window_ready(info_for(surface));

// Need to push onto the MRU list for alt+tab to work correctly.
//
// Originally, applications were pushed only when selected, but due to
// focus stealing prevention, they were not activated and thus weren't
// selected nor added to mru_active_windows, breaking alt+tab
auto window = info_for(surface).window();
auto const top = mru_active_windows.top();
if(top && top != window)
mru_active_windows.insert_below_top(window);
}

void miral::BasicWindowManager::modify_surface(
Expand Down
59 changes: 54 additions & 5 deletions src/miral/minimal_window_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,14 @@ auto touch_center(MirTouchEvent const* event) -> mir::geometry::Point

struct miral::MinimalWindowManager::Impl
{
Impl(WindowManagerTools const& tools, MirInputEventModifier pointer_drag_modifier) :
tools{tools}, application_selector(tools), pointer_drag_modifier{pointer_drag_modifier} {}
Impl(WindowManagerTools const& tools, MirInputEventModifier pointer_drag_modifier, FocusStealing focus_stealing) :
tools{tools},
application_selector(tools),
pointer_drag_modifier{pointer_drag_modifier},
focus_stealing{focus_stealing}
{
}

WindowManagerTools tools;

Gesture gesture = Gesture::none;
Expand Down Expand Up @@ -98,17 +104,37 @@ struct miral::MinimalWindowManager::Impl

void apply_resize_by(Displacement movement);

// Returns true if the window should NOT be in focus
// Relevant for focus stealing prevention where windows are focused on
// launch in specific circumstances.
bool should_prevent_focus(WindowInfo const& info);

bool advise_new_window(WindowInfo const& info);

MirInputEventModifier const pointer_drag_modifier;
FocusStealing const focus_stealing;
};

miral::MinimalWindowManager::MinimalWindowManager(WindowManagerTools const& tools) :
MinimalWindowManager{tools, mir_input_event_modifier_alt}
{
}

miral::MinimalWindowManager::MinimalWindowManager(WindowManagerTools const& tools, FocusStealing focus_stealing) :
tools{tools},
self{new Impl{tools, mir_input_event_modifier_alt, focus_stealing}}
{
}

miral::MinimalWindowManager::MinimalWindowManager(WindowManagerTools const& tools, MirInputEventModifier pointer_drag_modifier):
tools{tools},
self{new Impl{tools, pointer_drag_modifier}}
self{new Impl{tools, pointer_drag_modifier, FocusStealing::allow}}
{
}

miral::MinimalWindowManager::MinimalWindowManager(WindowManagerTools const& tools, MirInputEventModifier pointer_drag_modifier, FocusStealing focus_stealing):
tools{tools},
self{new Impl{tools, pointer_drag_modifier, focus_stealing}}
{
}

Expand All @@ -126,7 +152,7 @@ auto miral::MinimalWindowManager::place_new_window(

void miral::MinimalWindowManager::handle_window_ready(WindowInfo& window_info)
{
if (window_info.can_be_active())
if (!self->should_prevent_focus(window_info) && window_info.can_be_active())
{
tools.select_active_window(window_info.window());
}
Expand Down Expand Up @@ -276,7 +302,15 @@ auto miral::MinimalWindowManager::confirm_inherited_move(WindowInfo const& windo

void miral::MinimalWindowManager::advise_new_window(miral::WindowInfo const& window_info)
{
self->application_selector.advise_new_window(window_info);
auto should_receive_focus = self->advise_new_window(window_info);

// If the window is prevented from stealing focus, swap the old focused
// window (now in the back) with the new window in the front.
//
// If it's a legitimate window, it'll be focused and raised via
// xdg-activation later on.
if (!should_receive_focus)
tools.swap_tree_order(tools.active_window(), window_info.window());
}

void miral::MinimalWindowManager::advise_focus_gained(WindowInfo const& window_info)
Expand Down Expand Up @@ -606,3 +640,18 @@ void miral::MinimalWindowManager::Impl::apply_resize_by(Displacement movement)
gesture = Gesture::none;
}
}

bool miral::MinimalWindowManager::Impl::should_prevent_focus(miral::WindowInfo const& info)
{
return (focus_stealing == FocusStealing::prevent) && tools.active_window() &&
info.depth_layer() == mir_depth_layer_application &&
tools.active_window().application() != info.window().application();
}

bool miral::MinimalWindowManager::Impl::advise_new_window(WindowInfo const& info)
{
auto should_receive_focus = !should_prevent_focus(info);
application_selector.advise_new_window(info, should_receive_focus);

return should_receive_focus;
}
9 changes: 9 additions & 0 deletions src/miral/mru_window_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ void miral::MRUWindowList::push(Window const& window)
windows.push_back(window);
}

void miral::MRUWindowList::insert_below_top(Window const& window)
{
windows.erase(remove(begin(windows), end(windows), window), end(windows));
if(!windows.empty())
windows.insert(windows.end()-1, window);
else
windows.push_back(window);
}

void miral::MRUWindowList::erase(Window const& window)
{
windows.erase(remove(begin(windows), end(windows), window), end(windows));
Expand Down
1 change: 1 addition & 0 deletions src/miral/mru_window_list.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class MRUWindowList
public:

void push(Window const& window);
void insert_below_top(Window const& window);
void erase(Window const& window);
auto top() const -> Window;

Expand Down

0 comments on commit 687c38a

Please sign in to comment.