Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions metadata/core.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@
<_long>Whether to pass buttons through to the client when switching the focus via clicking</_long>
<default>true</default>
</option>
<option name="focus_on_map" type="bool">
<_short>Focus newly opened views</_short>
<_long>If unset, newly opened views will only be focused by using the xdg-activation protocol.</_long>
<default>true</default>
</option>
<option name="exit" type="key">
<_short>Wayfire Shutdown</_short>
<_long>Calls the shutdown routines for wayfire.</_long>
Expand Down
5 changes: 5 additions & 0 deletions metadata/xdg-activation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
<_long>Whether to reject activation requests if a newer request has arrived since their creation.</_long>
<default>false</default>
</option>
<option name="focus_stealing_prevention" type="bool">
<_short>Prevent stealing focus by an activation request</_short>
<_long>Whether to reject an activation request if the user interacted with a different view since its creation.</_long>
<default>true</default>
</option>
<option name="timeout" type="int">
<_short>Timeout for activation (in seconds)</_short>
<_long>Focus requests will be ignored if at least this amount of time has elapsed between creating and using it.</_long>
Expand Down
221 changes: 210 additions & 11 deletions plugins/protocols/xdg-activation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <wayfire/nonstd/wlroots-full.hpp>
#include <wayfire/window-manager.hpp>
#include <wayfire/util.hpp>
#include <wayfire/seat.hpp>
#include "config.h"

class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
Expand All @@ -28,14 +29,25 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t

xdg_activation_request_activate.connect(&xdg_activation->events.request_activate);
xdg_activation_new_token.connect(&xdg_activation->events.new_token);
wf::get_core().connect(&on_run_command);
}

void fini() override
{
xdg_activation_request_activate.disconnect();
xdg_activation_new_token.disconnect();
xdg_activation_token_destroy.disconnect();
xdg_activation_token_self_destroy.disconnect();
on_view_mapped.disconnect();
last_token = nullptr;
if (last_view)
{
last_view->disconnect(&on_view_unmapped);
// last_view->disconnect(&on_view_deactivated);
last_view = nullptr;
}

wf::get_core().disconnect(&on_run_command);
}

bool is_unloadable() override
Expand All @@ -50,36 +62,67 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
{
auto event = static_cast<const struct wlr_xdg_activation_v1_request_activate_event*>(data);

if (!event->token->seat)
if (event->token != last_self_token)
{
LOGI("Denying focus request, token was rejected at creation");
return;
if (!event->token->seat)
{
LOGI("Denying focus request, token was rejected at creation");
return;
}

if (only_last_token && (event->token != last_token))
{
LOGI("Denying focus request, token is expired");
return;
}
}

if (only_last_token && (event->token != last_token))
last_token = nullptr; // avoid reusing the same token
last_self_token = nullptr;

if (prevent_focus_stealing && (!last_view || last_view != wf::get_core().seat->get_active_view()))
{
LOGI("Denying focus request, token is expired");
LOGI("Denying focus request, requesting view has been deactivated");
return;
}

last_token = nullptr; // avoid reusing the same token

bool should_focus = true;
wayfire_view view = wf::wl_surface_to_wayfire_view(event->surface->resource);
if (!view)
{
LOGE("Could not get view");
return;
should_focus = false;
}

auto toplevel = wf::toplevel_cast(view);
if (!toplevel)
{
LOGE("Could not get toplevel view");
return;
should_focus = false;
}

LOGD("Activating view");
wf::get_core().default_wm->focus_request(toplevel);
if (should_focus)
{
if (toplevel->toplevel()->current().mapped)
{
LOGD("Activating view");
wf::get_core().default_wm->focus_request(toplevel);
} else
{
/* This toplevel is not mapped yet, we want to focus it
* when it it first mapped. */
on_view_mapped.disconnect();
view->connect(&on_view_mapped);
return; // avoid disconnecting last_view's signals
}
}

if (last_view)
{
last_view->disconnect(&on_view_unmapped);
// last_view->disconnect(&on_view_deactivated);
last_view = nullptr;
}
});

xdg_activation_new_token.set_callback([this] (void *data)
Expand All @@ -100,6 +143,24 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
return;
}

// unset any previously saved view
if (last_view)
{
last_view->disconnect(&on_view_unmapped);
// last_view->disconnect(&on_view_deactivated);
last_view = nullptr;
}

wayfire_view view = token->surface ? wf::wl_surface_to_wayfire_view(
token->surface->resource) : nullptr;
if (view)
{
last_view = view;
last_view->connect(&on_view_unmapped);
LOGI("New token for view: ", view.get());
// last_view->connect(&on_view_deactivated);
}

// update our token and connect its destroy signal
last_token = token;
xdg_activation_token_destroy.disconnect();
Expand All @@ -113,6 +174,13 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
xdg_activation_token_destroy.disconnect();
});

xdg_activation_token_self_destroy.set_callback([this] (void *data)
{
last_self_token = nullptr;

xdg_activation_token_self_destroy.disconnect();
});

timeout.set_callback(timeout_changed);
}

Expand All @@ -125,14 +193,145 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
}
};

wf::signal::connection_t<wf::view_unmapped_signal> on_view_unmapped = [this] (auto)
{
last_view->disconnect(&on_view_unmapped);
// last_view->disconnect(&on_view_deactivated);
LOGI("View closed: ", last_view.get());

// handle the case when last_view was a dialog that is closed by user interaction
auto toplevel = wf::toplevel_cast(last_view);
if (toplevel)
{
last_view = toplevel->parent;
LOGI("Setting parent: ", last_view.get());
} else
{
//!! does not work, it is already NULL when this is called
auto surface = last_view->get_wlr_surface();
auto popup = surface ? wlr_xdg_popup_try_from_wlr_surface (surface) : nullptr;
if (popup && popup->parent)
{
last_view = wf::wl_surface_to_wayfire_view (popup->parent->resource);
LOGI("Setting parent (from xdg-popup): ", last_view.get());
} else
{
//!! TODO: XWayland popups?
last_view = nullptr;
LOGI("No parent");
}
}

if (last_view)
{
last_view->connect(&on_view_unmapped);
// last_view->connect(&on_view_deactivated);
}
};

/*
wf::signal::connection_t<wf::view_activated_state_signal> on_view_deactivated = [this] (auto)
{
if (last_view->activated)
{
// could be a spurious event, e.g. activating the parent
// view after closing a dialog
return;
}

last_view->disconnect(&on_view_unmapped);
last_view->disconnect(&on_view_deactivated);
last_view = nullptr;
};
*/

wf::signal::connection_t<wf::view_mapped_signal> on_view_mapped = [this] (auto signal)
{
signal->view->disconnect(&on_view_mapped);

bool should_focus = true;

// re-check focus stealing prevention
if (prevent_focus_stealing)
{
if (!last_view || last_view != wf::get_core().seat->get_active_view())
{
LOGI("Denying focus request, requesting view has been deactivated");
should_focus = false;
}
}
if (last_view)
{
last_view->disconnect(&on_view_unmapped);
// last_view->disconnect(&on_view_deactivated);
last_view = nullptr;
}

if (should_focus)
{
LOGD("Activating view");
wf::get_core().default_wm->focus_request(signal->view);
}
};

wf::signal::connection_t<wf::command_run_signal> on_run_command = [this] (auto signal)
{
if (wf::get_core().default_wm->focus_on_map)
{
// no need to do anything if views are focused anyway
return;
}

if (last_self_token)
{
// TODO: invalidate our last token !
last_self_token = nullptr;
}

auto active_view = wf::get_core().seat->get_active_view();
if (active_view && (active_view->role == wf::VIEW_ROLE_DESKTOP_ENVIRONMENT))
{
active_view = nullptr;
}

// auto active_toplevel = active_view ? wf::toplevel_cast(active_view) : nullptr;

if (!active_view)
{
// if there is no active view, we don't need a token
return;
}

if (last_view)
{
// TODO: we need a separate last_view actually !
last_view->disconnect(&on_view_unmapped);
// last_view->disconnect(&on_view_deactivated);
}

last_view = active_view;
last_view->connect(&on_view_unmapped);
// last_view->connect(&on_view_deactivated);
last_self_token = wlr_xdg_activation_token_v1_create(xdg_activation);
xdg_activation_token_self_destroy.disconnect();
xdg_activation_token_self_destroy.connect(&last_self_token->events.destroy);
const char *token_id = wlr_xdg_activation_token_v1_get_name(last_self_token);
signal->env.emplace_back("XDG_ACTIVATION_TOKEN", token_id);
signal->env.emplace_back("DESKTOP_STARTUP_ID", token_id);
};

struct wlr_xdg_activation_v1 *xdg_activation;
wf::wl_listener_wrapper xdg_activation_request_activate;
wf::wl_listener_wrapper xdg_activation_new_token;
wf::wl_listener_wrapper xdg_activation_token_destroy;
wf::wl_listener_wrapper xdg_activation_token_self_destroy;
struct wlr_xdg_activation_token_v1 *last_token = nullptr;
struct wlr_xdg_activation_token_v1 *last_self_token = nullptr;
wayfire_view last_view = nullptr; // view that created the token

wf::option_wrapper_t<bool> check_surface{"xdg-activation/check_surface"};
wf::option_wrapper_t<bool> only_last_token{"xdg-activation/only_last_request"};
wf::option_wrapper_t<bool> prevent_focus_stealing{"xdg-activation/focus_stealing_prevention"};
wf::option_wrapper_t<int> timeout{"xdg-activation/timeout"};
};

Expand Down
15 changes: 15 additions & 0 deletions src/api/wayfire/signal-definitions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,21 @@ struct idle_inhibit_changed_signal
bool inhibit;
};

/**
* on: core
* when: before running a command in compositor_core_t::run().
* Plugins can listen to this signal and add environment variables to
* set in env.
*/
struct command_run_signal
{
const std::string& command;
std::vector<std::pair<std::string, std::string>> env;

command_run_signal(const std::string& cmd) : command(cmd)
{}
};

/**
* on: output, core(output-)
* when: Immediately after the output becomes focused.
Expand Down
11 changes: 11 additions & 0 deletions src/api/wayfire/toplevel-view.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,17 @@ class toplevel_view_interface_t : public virtual wf::view_interface_t
* This function is useful for view implementations only.
*/
void set_toplevel(std::shared_ptr<wf::toplevel_t> toplevel);

/**
* Potentially focus this view on first map. Newly mapped views are
* focused if either:
* - the "core/focus_on_map" option is set
* - no toplevel view is currently focused (i.e. no view is focused
* or the currently focused view has role VIEW_ROLE_DESKTOP_ENVIRONMENT)
* - the newly mapped view is a dialog (i.e. has a non-null parent)
* and its parent is focused
*/
void focus_toplevel_on_map();
};

inline wayfire_toplevel_view toplevel_cast(wayfire_view view)
Expand Down
5 changes: 5 additions & 0 deletions src/api/wayfire/window-manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ class window_manager_t
public:
virtual ~window_manager_t() = default;

/**
* Option that sets whether newly mapped views get focus by default.
*/
wf::option_wrapper_t<bool> focus_on_map{"core/focus_on_map"};

/**
* Update the remembered last windowed geometry.
*
Expand Down
8 changes: 8 additions & 0 deletions src/core/core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,9 @@ pid_t wf::compositor_core_impl_t::run(std::string command)
return 0;
}

wf::command_run_signal signal(command);
wf::get_core().emit(&signal);

/* The following is a "hack" for disowning the child processes,
* otherwise they will simply stay as zombie processes */
pid_t pid = fork();
Expand All @@ -545,6 +548,11 @@ pid_t wf::compositor_core_impl_t::run(std::string command)
}

#endif
for (const auto& var : signal.env)
{
setenv(var.first.c_str(), var.second.c_str(), 1);
}

if (discard_command_output)
{
int dev_null = open("/dev/null", O_WRONLY);
Expand Down
Loading
Loading