mirror of
https://github.com/miracle-wm-org/miracle-wm.git
synced 2024-11-22 19:58:51 +03:00
feature: supporting I3 IPC enough to make waybar's workspaces function (#35)
* Switching workspaces now emits a signal to i3 IPC clients which makes it so that waybar works * Refactored a lot of workspace code for readability and usability
This commit is contained in:
parent
2a11993588
commit
b6d1ae752e
2
.github/workflows/cmake.yml
vendored
2
.github/workflows/cmake.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
run: sudo apt-add-repository ppa:mir-team/release
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get install libmiral-dev libgtest-dev libyaml-cpp-dev libglib2.0-dev libevdev-dev
|
||||
run: sudo apt-get install libmiral-dev libgtest-dev libyaml-cpp-dev libglib2.0-dev libevdev-dev nlohmann-json3-dev
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
|
@ -19,6 +19,7 @@ pkg_check_modules(MIRAL miral REQUIRED)
|
||||
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||
pkg_check_modules(YAML REQUIRED IMPORTED_TARGET yaml-cpp)
|
||||
pkg_check_modules(LIBEVDEV REQUIRED IMPORTED_TARGET libevdev)
|
||||
find_package(nlohmann_json 3.2.0 REQUIRED)
|
||||
|
||||
add_executable(miracle-wm
|
||||
src/main.cpp
|
||||
@ -31,11 +32,13 @@ add_executable(miracle-wm
|
||||
src/miracle_config.h
|
||||
src/screen.cpp
|
||||
src/workspace_manager.cpp
|
||||
src/ipc.cpp
|
||||
src/auto_restarting_launcher.cpp
|
||||
src/workspace_observer.cpp
|
||||
)
|
||||
|
||||
target_include_directories(miracle-wm PUBLIC SYSTEM ${MIRAL_INCLUDE_DIRS})
|
||||
target_link_libraries( miracle-wm ${MIRAL_LDFLAGS} PkgConfig::YAML PkgConfig::GLIB PkgConfig::LIBEVDEV)
|
||||
target_link_libraries( miracle-wm ${MIRAL_LDFLAGS} PkgConfig::YAML PkgConfig::GLIB PkgConfig::LIBEVDEV nlohmann_json::nlohmann_json)
|
||||
|
||||
install(PROGRAMS ${CMAKE_BINARY_DIR}/bin/miracle-wm
|
||||
DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
|
||||
|
@ -60,6 +60,7 @@ parts:
|
||||
- libglib2.0-0
|
||||
- libevdev-dev
|
||||
- libevdev2
|
||||
- nlohmann-json3-dev
|
||||
stage-packages:
|
||||
- libmiral6
|
||||
- mir-graphics-drivers-desktop
|
||||
|
406
src/ipc.cpp
Normal file
406
src/ipc.cpp
Normal file
@ -0,0 +1,406 @@
|
||||
#define MIR_LOG_COMPONENT "miracle_ipc"
|
||||
|
||||
#include "ipc.h"
|
||||
#include "screen.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#include <mir/log.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using namespace miracle;
|
||||
|
||||
static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'};
|
||||
|
||||
#define IPC_HEADER_SIZE (sizeof(ipc_magic) + 8)
|
||||
#define event_mask(ev) (1 << (ev & 0x7F))
|
||||
|
||||
namespace
|
||||
{
|
||||
struct sockaddr_un *ipc_user_sockaddr() {
|
||||
auto ipc_sockaddr = (sockaddr_un*)malloc(sizeof(struct sockaddr_un));
|
||||
if (ipc_sockaddr == nullptr)
|
||||
{
|
||||
mir::log_error("Can't allocate ipc_sockaddr");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ipc_sockaddr->sun_family = AF_UNIX;
|
||||
int path_size = sizeof(ipc_sockaddr->sun_path);
|
||||
|
||||
// Env var typically set by logind, e.g. "/run/user/<user-id>"
|
||||
const char *dir = getenv("XDG_RUNTIME_DIR");
|
||||
if (!dir)
|
||||
dir = "/tmp";
|
||||
|
||||
if (path_size <= snprintf(ipc_sockaddr->sun_path, path_size,
|
||||
"%s/miracle-wm-ipc.%u.%i.sock", dir, getuid(), getpid()))
|
||||
{
|
||||
mir::log_error("Socket path won't fit into ipc_sockaddr->sun_path");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return ipc_sockaddr;
|
||||
}
|
||||
|
||||
json workspace_to_json(std::shared_ptr<Screen> const& screen, int key)
|
||||
{
|
||||
bool is_focused = screen->get_active_workspace() == key;
|
||||
auto area = screen->get_area();
|
||||
|
||||
return {
|
||||
{"num", key},
|
||||
{"id", key},
|
||||
{"type", "workspace"},
|
||||
{"name", std::to_string(key)},
|
||||
{"visible", screen->is_active() && is_focused},
|
||||
{"focused", screen->is_active() && is_focused},
|
||||
{"urgent", false},
|
||||
{"output", screen->get_output().name()},
|
||||
{"rect", {
|
||||
{"x", area.top_left.x.as_int()},
|
||||
{"y", area.top_left.y.as_int()},
|
||||
{"width", area.size.width.as_int()},
|
||||
{"height", area.size.height.as_int()},
|
||||
}}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Ipc::Ipc(miral::MirRunner& runner, miracle::WorkspaceManager& workspace_manager)
|
||||
: workspace_manager{workspace_manager}
|
||||
{
|
||||
auto ipc_socket_raw = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (ipc_socket_raw == -1)
|
||||
{
|
||||
mir::log_error("Unable to create ipc socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (fcntl(ipc_socket_raw, F_SETFD, FD_CLOEXEC) == -1) {
|
||||
mir::log_error("Unable to set CLOEXEC on IPC socket");
|
||||
exit(1);
|
||||
}
|
||||
if (fcntl(ipc_socket_raw, F_SETFL, O_NONBLOCK) == -1) {
|
||||
mir::log_error("Unable to set NONBLOCK on IPC socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ipc_sockaddr = ipc_user_sockaddr();
|
||||
if (getenv("SWAYSOCK") != nullptr && access(getenv("SWAYSOCK"), F_OK) == -1)
|
||||
{
|
||||
strncpy(ipc_sockaddr->sun_path, getenv("SWAYSOCK"), sizeof(ipc_sockaddr->sun_path) - 1);
|
||||
ipc_sockaddr->sun_path[sizeof(ipc_sockaddr->sun_path) - 1] = 0;
|
||||
}
|
||||
|
||||
unlink(ipc_sockaddr->sun_path);
|
||||
if (bind(ipc_socket_raw, (struct sockaddr *)ipc_sockaddr, sizeof(*ipc_sockaddr)) == -1)
|
||||
{
|
||||
mir::log_error("Unable to bind IPC socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (listen(ipc_socket_raw, 3) == -1) {
|
||||
mir::log_error("Unable to listen on IPC socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Set i3 IPC socket path so that i3-msg works out of the box
|
||||
setenv("I3SOCK", ipc_sockaddr->sun_path, 1);
|
||||
setenv("SWAYSOCK", ipc_sockaddr->sun_path, 1);
|
||||
|
||||
ipc_socket = mir::Fd{ipc_socket_raw};
|
||||
socket_handle = runner.register_fd_handler(ipc_socket, [&](int fd)
|
||||
{
|
||||
int client_fd = accept(ipc_socket, NULL, NULL);
|
||||
if (client_fd == -1) {
|
||||
mir::log_error("Unable to accept IPC client connection");
|
||||
return;
|
||||
}
|
||||
|
||||
int flags;
|
||||
if ((flags = fcntl(client_fd, F_GETFD)) == -1
|
||||
|| fcntl(client_fd, F_SETFD, flags|FD_CLOEXEC) == -1) {
|
||||
mir::log_error("Unable to set CLOEXEC on IPC client socket");
|
||||
close(client_fd);
|
||||
return;
|
||||
}
|
||||
if ((flags = fcntl(client_fd, F_GETFL)) == -1
|
||||
|| fcntl(client_fd, F_SETFL, flags|O_NONBLOCK) == -1) {
|
||||
mir::log_error("Unable to set NONBLOCK on IPC client socket");
|
||||
close(client_fd);
|
||||
return;
|
||||
}
|
||||
|
||||
auto mir_fd = mir::Fd{client_fd};
|
||||
clients.push_back({
|
||||
mir_fd,
|
||||
runner.register_fd_handler(mir_fd, [this](int fd)
|
||||
{
|
||||
auto& client = get_client(fd);
|
||||
|
||||
int read_available;
|
||||
if (ioctl(client.client_fd, FIONREAD, &read_available) == -1) {
|
||||
mir::log_error("Unable to read IPC socket buffer size");
|
||||
disconnect(client);
|
||||
return;
|
||||
}
|
||||
|
||||
if (client.pending_read_length > 0) {
|
||||
if ((uint32_t)read_available >= client.pending_read_length) {
|
||||
// Reset pending values.
|
||||
uint32_t pending_length = client.pending_read_length;
|
||||
IpcCommandType pending_type = client.pending_type;
|
||||
client.pending_read_length = 0;
|
||||
handle_command(client, pending_length, pending_type);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (read_available < (int) IPC_HEADER_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t buf[IPC_HEADER_SIZE];
|
||||
// Should be fully available, because read_available >= IPC_HEADER_SIZE
|
||||
ssize_t received = recv(client.client_fd, buf, IPC_HEADER_SIZE, 0);
|
||||
if (received == -1)
|
||||
{
|
||||
mir::log_error("Unable to receive header from IPC client");
|
||||
disconnect(client);
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp(buf, ipc_magic, sizeof(ipc_magic)) != 0) {
|
||||
mir::log_error("IPC header check failed");
|
||||
disconnect(client);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(&client.pending_read_length, buf + sizeof(ipc_magic), sizeof(uint32_t));
|
||||
memcpy(&client.pending_type, buf + sizeof(ipc_magic) + sizeof(uint32_t), sizeof(uint32_t));
|
||||
|
||||
if (read_available - received >= (long)client.pending_read_length)
|
||||
{
|
||||
// Reset pending values.
|
||||
uint32_t pending_length = client.pending_read_length;
|
||||
IpcCommandType pending_type = client.pending_type;
|
||||
client.pending_read_length = 0;
|
||||
handle_command(client, pending_length, pending_type);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
void Ipc::on_created(std::shared_ptr<Screen> const& info, int key)
|
||||
{
|
||||
json j = {
|
||||
{"change", "init"},
|
||||
{"old", nullptr},
|
||||
{"current", workspace_to_json(info, key)}
|
||||
};
|
||||
|
||||
auto serialized_value = to_string(j);
|
||||
for (auto& client : clients)
|
||||
{
|
||||
if ((client.subscribed_events & event_mask(IPC_EVENT_WORKSPACE)) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
send_reply(client, IPC_EVENT_WORKSPACE, serialized_value);
|
||||
}
|
||||
}
|
||||
|
||||
void Ipc::on_removed(std::shared_ptr<Screen> const& screen, int key)
|
||||
{
|
||||
json j = {
|
||||
{"change", "empty"},
|
||||
{"current", workspace_to_json(screen, key)}
|
||||
};
|
||||
|
||||
auto serialized_value = to_string(j);
|
||||
for (auto& client : clients)
|
||||
{
|
||||
if ((client.subscribed_events & event_mask(IPC_EVENT_WORKSPACE)) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
send_reply(client, IPC_EVENT_WORKSPACE, serialized_value);
|
||||
}
|
||||
}
|
||||
|
||||
void Ipc::on_focused(
|
||||
std::shared_ptr<Screen> const& previous,
|
||||
int previous_key,
|
||||
std::shared_ptr<Screen> const& current,
|
||||
int current_key)
|
||||
{
|
||||
json j = {
|
||||
{"change", "focus"},
|
||||
{"current", workspace_to_json(current, current_key)}
|
||||
};
|
||||
|
||||
if (previous)
|
||||
j["old"] = workspace_to_json(previous, previous_key);
|
||||
else
|
||||
j["old"] = nullptr;
|
||||
|
||||
auto serialized_value = to_string(j);
|
||||
for (auto& client : clients)
|
||||
{
|
||||
if ((client.subscribed_events & event_mask(IPC_EVENT_WORKSPACE)) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
send_reply(client, IPC_EVENT_WORKSPACE, serialized_value);
|
||||
}
|
||||
}
|
||||
|
||||
Ipc::IpcClient &Ipc::get_client(int fd)
|
||||
{
|
||||
for (auto& client : clients)
|
||||
{
|
||||
if (client.client_fd == fd)
|
||||
return client;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Could not find IPC client");
|
||||
}
|
||||
|
||||
void Ipc::disconnect(Ipc::IpcClient& client)
|
||||
{
|
||||
auto it = std::find_if(clients.begin(), clients.end(), [&](IpcClient const& other)
|
||||
{
|
||||
return other.client_fd.operator int() == client.client_fd.operator int();
|
||||
});
|
||||
if (it != clients.end())
|
||||
{
|
||||
shutdown(client.client_fd, SHUT_RDWR);
|
||||
mir::log_info("Disconnected client: %d", (int)client.client_fd);
|
||||
clients.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
mir::log_error("Unable to disconnect client");
|
||||
}
|
||||
}
|
||||
|
||||
void Ipc::handle_command(miracle::Ipc::IpcClient &client, uint32_t payload_length, miracle::IpcCommandType payload_type)
|
||||
{
|
||||
char *buf = (char*)malloc(payload_length + 1);
|
||||
if (!buf)
|
||||
{
|
||||
mir::log_error("Unable to allocate IPC payload");
|
||||
disconnect(client);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload_length > 0) {
|
||||
// Payload should be fully available
|
||||
ssize_t received = recv(client.client_fd, buf, payload_length, 0);
|
||||
if (received == -1)
|
||||
{
|
||||
mir::log_error("Unable to receive payload from IPC client");
|
||||
disconnect(client);
|
||||
free(buf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
buf[payload_length] = '\0';
|
||||
|
||||
switch (payload_type)
|
||||
{
|
||||
case IPC_GET_WORKSPACES:
|
||||
{
|
||||
json j = json::array();
|
||||
for (int i = 0; i < WorkspaceManager::NUM_WORKSPACES; i++)
|
||||
{
|
||||
auto workspace = workspace_manager.get_workspaces()[i];
|
||||
if (workspace)
|
||||
j.push_back(workspace_to_json(workspace, i));
|
||||
}
|
||||
auto json_string = to_string(j);
|
||||
send_reply(client, payload_type, json_string);
|
||||
break;
|
||||
}
|
||||
case IPC_SUBSCRIBE:
|
||||
{
|
||||
json j = json::parse(buf);
|
||||
for (auto const& i : j)
|
||||
{
|
||||
std::string event_type = i.template get<std::string>();
|
||||
if (event_type == "workspace")
|
||||
{
|
||||
client.subscribed_events |= event_mask(IPC_EVENT_WORKSPACE);
|
||||
const std::string msg = "{\"success\": true}";
|
||||
send_reply(client, payload_type, msg);
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
mir::log_warning("Unknown payload type: %d", payload_type);
|
||||
disconnect(client);
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void Ipc::send_reply(miracle::Ipc::IpcClient &client, miracle::IpcCommandType command_type, const std::string &payload)
|
||||
{
|
||||
const uint32_t payload_length = payload.size();
|
||||
char data[IPC_HEADER_SIZE];
|
||||
|
||||
memcpy(data, ipc_magic, sizeof(ipc_magic));
|
||||
memcpy(data + sizeof(ipc_magic), &payload_length, sizeof(payload_length));
|
||||
memcpy(data + sizeof(ipc_magic) + sizeof(payload_length), &command_type, sizeof(command_type));
|
||||
|
||||
auto new_buffer_size = client.buffer.size();
|
||||
while (client.write_buffer_len + IPC_HEADER_SIZE + payload_length >= new_buffer_size) {
|
||||
if (new_buffer_size == 0) new_buffer_size = 1;
|
||||
new_buffer_size *= 2;
|
||||
}
|
||||
|
||||
if (new_buffer_size > 4e6) { // 4 MB
|
||||
mir::log_error("Client write buffer too big (%zu), disconnecting client", client.buffer.size());
|
||||
disconnect(client);
|
||||
return;
|
||||
}
|
||||
|
||||
client.buffer.resize(new_buffer_size);
|
||||
|
||||
memcpy(client.buffer.data() + client.write_buffer_len, data, IPC_HEADER_SIZE);
|
||||
client.write_buffer_len += IPC_HEADER_SIZE;
|
||||
memcpy(client.buffer.data() + client.write_buffer_len, payload.c_str(), payload_length);
|
||||
client.write_buffer_len += payload_length;
|
||||
handle_writeable(client);
|
||||
}
|
||||
|
||||
|
||||
void Ipc::handle_writeable(miracle::Ipc::IpcClient &client)
|
||||
{
|
||||
while (client.write_buffer_len > 0)
|
||||
{
|
||||
ssize_t written = write(client.client_fd, client.buffer.data(), client.write_buffer_len);
|
||||
if (written == -1 && errno == EAGAIN) {
|
||||
return;
|
||||
} else if (written == -1) {
|
||||
mir::log_error("Unable to send data from queue to IPC client");
|
||||
disconnect(client);
|
||||
return;
|
||||
}
|
||||
|
||||
memmove(client.buffer.data(), client.buffer.data() + written, client.write_buffer_len - written);
|
||||
client.write_buffer_len -= written;
|
||||
}
|
||||
|
||||
client.write_buffer_len = 0;
|
||||
}
|
90
src/ipc.h
Normal file
90
src/ipc.h
Normal file
@ -0,0 +1,90 @@
|
||||
#ifndef MIRACLEWM_IPC_H
|
||||
#define MIRACLEWM_IPC_H
|
||||
|
||||
#include "workspace_manager.h"
|
||||
#include "workspace_observer.h"
|
||||
#include <miral/runner.h>
|
||||
#include <mir/fd.h>
|
||||
#include <vector>
|
||||
|
||||
struct sockaddr_un;
|
||||
|
||||
namespace miracle
|
||||
{
|
||||
|
||||
/// This it taken directly from SWAY
|
||||
enum IpcCommandType {
|
||||
// i3 command types - see i3's I3_REPLY_TYPE constants
|
||||
IPC_COMMAND = 0,
|
||||
IPC_GET_WORKSPACES = 1,
|
||||
IPC_SUBSCRIBE = 2,
|
||||
IPC_GET_OUTPUTS = 3,
|
||||
IPC_GET_TREE = 4,
|
||||
IPC_GET_MARKS = 5,
|
||||
IPC_GET_BAR_CONFIG = 6,
|
||||
IPC_GET_VERSION = 7,
|
||||
IPC_GET_BINDING_MODES = 8,
|
||||
IPC_GET_CONFIG = 9,
|
||||
IPC_SEND_TICK = 10,
|
||||
IPC_SYNC = 11,
|
||||
IPC_GET_BINDING_STATE = 12,
|
||||
|
||||
// sway-specific command types
|
||||
IPC_GET_INPUTS = 100,
|
||||
IPC_GET_SEATS = 101,
|
||||
|
||||
// Events sent from sway to clients. Events have the highest bits set.
|
||||
IPC_EVENT_WORKSPACE = ((1<<31) | 0),
|
||||
IPC_EVENT_OUTPUT = ((1<<31) | 1),
|
||||
IPC_EVENT_MODE = ((1<<31) | 2),
|
||||
IPC_EVENT_WINDOW = ((1<<31) | 3),
|
||||
IPC_EVENT_BARCONFIG_UPDATE = ((1<<31) | 4),
|
||||
IPC_EVENT_BINDING = ((1<<31) | 5),
|
||||
IPC_EVENT_SHUTDOWN = ((1<<31) | 6),
|
||||
IPC_EVENT_TICK = ((1<<31) | 7),
|
||||
|
||||
// sway-specific event types
|
||||
IPC_EVENT_BAR_STATE_UPDATE = ((1<<31) | 20),
|
||||
IPC_EVENT_INPUT = ((1<<31) | 21),
|
||||
};
|
||||
|
||||
/// Inter process communication for compositor clients (e.g. waybar).
|
||||
/// This class will implement I3's interface: https://i3wm.org/docs/ipc.html
|
||||
/// plus some of the sway-specific items.
|
||||
/// It may be extended in the future.
|
||||
class Ipc : public WorkspaceObserver
|
||||
{
|
||||
public:
|
||||
Ipc(miral::MirRunner& runner, WorkspaceManager&);
|
||||
|
||||
void on_created(std::shared_ptr<Screen> const& info, int key) override;
|
||||
void on_removed(std::shared_ptr<Screen> const& info, int key) override;
|
||||
void on_focused(std::shared_ptr<Screen> const& previous, int, std::shared_ptr<Screen> const& current, int) override;
|
||||
private:
|
||||
struct IpcClient
|
||||
{
|
||||
mir::Fd client_fd;
|
||||
std::unique_ptr<miral::FdHandle> handle;
|
||||
uint32_t pending_read_length = 0;
|
||||
IpcCommandType pending_type;
|
||||
std::vector<char> buffer;
|
||||
int write_buffer_len = 0;
|
||||
int subscribed_events = 0;
|
||||
};
|
||||
|
||||
WorkspaceManager& workspace_manager;
|
||||
mir::Fd ipc_socket;
|
||||
std::unique_ptr<miral::FdHandle> socket_handle;
|
||||
sockaddr_un* ipc_sockaddr = nullptr;
|
||||
std::vector<IpcClient> clients;
|
||||
|
||||
void disconnect(IpcClient& client);
|
||||
IpcClient& get_client(int fd);
|
||||
void handle_command(IpcClient& client, uint32_t payload_length, IpcCommandType payload_type);
|
||||
void send_reply(IpcClient& client, IpcCommandType command_type, std::string const& payload);
|
||||
void handle_writeable(IpcClient& client);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif //MIRACLEWM_IPC_H
|
@ -4,7 +4,6 @@
|
||||
#include "miracle_config.h"
|
||||
#include "auto_restarting_launcher.h"
|
||||
|
||||
#include <miral/set_window_management_policy.h>
|
||||
#include <miral/external_client.h>
|
||||
#include <miral/runner.h>
|
||||
#include <miral/window_management_options.h>
|
||||
@ -14,7 +13,6 @@
|
||||
#include <miral/wayland_extensions.h>
|
||||
#include <miral/display_configuration_option.h>
|
||||
#include <miral/add_init_callback.h>
|
||||
#include <mir/log.h>
|
||||
|
||||
using namespace miral;
|
||||
|
||||
|
@ -71,8 +71,18 @@ MiracleWindowManagementPolicy::MiracleWindowManagementPolicy(
|
||||
internal_client_launcher{internal_client_launcher},
|
||||
runner{runner},
|
||||
config{config},
|
||||
workspace_manager{WorkspaceManager(tools)}
|
||||
workspace_manager{WorkspaceManager(
|
||||
tools,
|
||||
workspace_observer_registrar,
|
||||
[&]() { return get_active_output(); })},
|
||||
ipc{std::make_shared<Ipc>(runner, workspace_manager)}
|
||||
{
|
||||
workspace_observer_registrar.register_interest(ipc);
|
||||
}
|
||||
|
||||
MiracleWindowManagementPolicy::~MiracleWindowManagementPolicy()
|
||||
{
|
||||
workspace_observer_registrar.unregister_interest(*ipc);
|
||||
}
|
||||
|
||||
bool MiracleWindowManagementPolicy::handle_keyboard_event(MirKeyboardEvent const* event)
|
||||
@ -102,118 +112,118 @@ bool MiracleWindowManagementPolicy::handle_keyboard_event(MirKeyboardEvent const
|
||||
}
|
||||
return true;
|
||||
case RequestVertical:
|
||||
if(active_output) active_output->screen->get_active_tree().request_vertical();
|
||||
if(active_output) active_output->get_active_tree().request_vertical();
|
||||
return true;
|
||||
case RequestHorizontal:
|
||||
if(active_output) active_output->screen->get_active_tree().request_horizontal();
|
||||
if(active_output) active_output->get_active_tree().request_horizontal();
|
||||
return true;
|
||||
case ToggleResize:
|
||||
if(active_output) active_output->screen->get_active_tree().toggle_resize_mode();
|
||||
if(active_output) active_output->get_active_tree().toggle_resize_mode();
|
||||
return true;
|
||||
case MoveUp:
|
||||
if (active_output && active_output->screen->get_active_tree().try_move_active_window(Direction::up))
|
||||
if (active_output && active_output->get_active_tree().try_move_active_window(Direction::up))
|
||||
return true;
|
||||
return false;
|
||||
case MoveDown:
|
||||
if (active_output && active_output->screen->get_active_tree().try_move_active_window(Direction::down))
|
||||
if (active_output && active_output->get_active_tree().try_move_active_window(Direction::down))
|
||||
return true;
|
||||
return false;
|
||||
case MoveLeft:
|
||||
if (active_output && active_output->screen->get_active_tree().try_move_active_window(Direction::left))
|
||||
if (active_output && active_output->get_active_tree().try_move_active_window(Direction::left))
|
||||
return true;
|
||||
return false;
|
||||
case MoveRight:
|
||||
if (active_output && active_output->screen->get_active_tree().try_move_active_window(Direction::right))
|
||||
if (active_output && active_output->get_active_tree().try_move_active_window(Direction::right))
|
||||
return true;
|
||||
return false;
|
||||
case SelectUp:
|
||||
if (active_output && (active_output->screen->get_active_tree().try_resize_active_window(Direction::up)
|
||||
|| active_output->screen->get_active_tree().try_select_next(Direction::up)))
|
||||
if (active_output && (active_output->get_active_tree().try_resize_active_window(Direction::up)
|
||||
|| active_output->get_active_tree().try_select_next(Direction::up)))
|
||||
return true;
|
||||
return false;
|
||||
case SelectDown:
|
||||
if (active_output && (active_output->screen->get_active_tree().try_resize_active_window(Direction::down)
|
||||
|| active_output->screen->get_active_tree().try_select_next(Direction::down)))
|
||||
if (active_output && (active_output->get_active_tree().try_resize_active_window(Direction::down)
|
||||
|| active_output->get_active_tree().try_select_next(Direction::down)))
|
||||
return true;
|
||||
return false;
|
||||
case SelectLeft:
|
||||
if (active_output && (active_output->screen->get_active_tree().try_resize_active_window(Direction::left)
|
||||
|| active_output->screen->get_active_tree().try_select_next(Direction::left)))
|
||||
if (active_output && (active_output->get_active_tree().try_resize_active_window(Direction::left)
|
||||
|| active_output->get_active_tree().try_select_next(Direction::left)))
|
||||
return true;
|
||||
return false;
|
||||
case SelectRight:
|
||||
if (active_output && (active_output->screen->get_active_tree().try_resize_active_window(Direction::right)
|
||||
|| active_output->screen->get_active_tree().try_select_next(Direction::right)))
|
||||
if (active_output && (active_output->get_active_tree().try_resize_active_window(Direction::right)
|
||||
|| active_output->get_active_tree().try_select_next(Direction::right)))
|
||||
return true;
|
||||
return false;
|
||||
case QuitActiveWindow:
|
||||
if (active_output) active_output->screen->get_active_tree().close_active_window();
|
||||
if (active_output) active_output->get_active_tree().close_active_window();
|
||||
return true;
|
||||
case QuitCompositor:
|
||||
runner.stop();
|
||||
return true;
|
||||
case Fullscreen:
|
||||
if (active_output) active_output->screen->get_active_tree().try_toggle_active_fullscreen();
|
||||
if (active_output) active_output->get_active_tree().try_toggle_active_fullscreen();
|
||||
return true;
|
||||
case SelectWorkspace1:
|
||||
if (active_output) workspace_manager.request_workspace(active_output->screen, '1');
|
||||
if (active_output) workspace_manager.request_workspace(active_output, 1);
|
||||
break;
|
||||
case SelectWorkspace2:
|
||||
if (active_output) workspace_manager.request_workspace(active_output->screen, '2');
|
||||
if (active_output) workspace_manager.request_workspace(active_output, 2);
|
||||
break;
|
||||
case SelectWorkspace3:
|
||||
if (active_output) workspace_manager.request_workspace(active_output->screen, '3');
|
||||
if (active_output) workspace_manager.request_workspace(active_output, 3);
|
||||
break;
|
||||
case SelectWorkspace4:
|
||||
if (active_output) workspace_manager.request_workspace(active_output->screen, '4');
|
||||
if (active_output) workspace_manager.request_workspace(active_output, 4);
|
||||
break;
|
||||
case SelectWorkspace5:
|
||||
if (active_output) workspace_manager.request_workspace(active_output->screen, '5');
|
||||
if (active_output) workspace_manager.request_workspace(active_output, 5);
|
||||
break;
|
||||
case SelectWorkspace6:
|
||||
if (active_output) workspace_manager.request_workspace(active_output->screen, '6');
|
||||
if (active_output) workspace_manager.request_workspace(active_output, 6);
|
||||
break;
|
||||
case SelectWorkspace7:
|
||||
if (active_output) workspace_manager.request_workspace(active_output->screen, '7');
|
||||
if (active_output) workspace_manager.request_workspace(active_output, 7);
|
||||
break;
|
||||
case SelectWorkspace8:
|
||||
if (active_output) workspace_manager.request_workspace(active_output->screen, '8');
|
||||
if (active_output) workspace_manager.request_workspace(active_output, 8);
|
||||
break;
|
||||
case SelectWorkspace9:
|
||||
if (active_output) workspace_manager.request_workspace(active_output->screen, '9');
|
||||
if (active_output) workspace_manager.request_workspace(active_output, 9);
|
||||
break;
|
||||
case SelectWorkspace0:
|
||||
if (active_output) workspace_manager.request_workspace(active_output->screen, '0');
|
||||
if (active_output) workspace_manager.request_workspace(active_output, 0);
|
||||
break;
|
||||
case MoveToWorkspace1:
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output->screen, '1');
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output, 1);
|
||||
break;
|
||||
case MoveToWorkspace2:
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output->screen, '2');
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output, 2);
|
||||
break;
|
||||
case MoveToWorkspace3:
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output->screen, '3');
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output, 3);
|
||||
break;
|
||||
case MoveToWorkspace4:
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output->screen, '4');
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output, 4);
|
||||
break;
|
||||
case MoveToWorkspace5:
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output->screen, '5');
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output, 5);
|
||||
break;
|
||||
case MoveToWorkspace6:
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output->screen, '6');
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output, 6);
|
||||
break;
|
||||
case MoveToWorkspace7:
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output->screen, '7');
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output, 7);
|
||||
break;
|
||||
case MoveToWorkspace8:
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output->screen, '8');
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output, 8);
|
||||
break;
|
||||
case MoveToWorkspace9:
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output->screen, '9');
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output, 9);
|
||||
break;
|
||||
case MoveToWorkspace0:
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output->screen, '0');
|
||||
if (active_output) workspace_manager.move_active_to_workspace(active_output, 0);
|
||||
break;
|
||||
default:
|
||||
std::cerr << "Unknown key_command: " << key_command << std::endl;
|
||||
@ -227,15 +237,22 @@ bool MiracleWindowManagementPolicy::handle_pointer_event(MirPointerEvent const*
|
||||
auto x = miral::toolkit::mir_pointer_event_axis_value(event, MirPointerAxis::mir_pointer_axis_x);
|
||||
auto y = miral::toolkit::mir_pointer_event_axis_value(event, MirPointerAxis::mir_pointer_axis_y);
|
||||
|
||||
for (auto const& pair : output_list)
|
||||
for (auto const& output : output_list)
|
||||
{
|
||||
if (pair->screen->point_is_in_output(static_cast<int>(x), static_cast<int>(y)))
|
||||
if (output->point_is_in_output(static_cast<int>(x), static_cast<int>(y)))
|
||||
{
|
||||
if (active_output != pair)
|
||||
if (active_output != output)
|
||||
{
|
||||
active_output = pair;
|
||||
if (active_output) active_output->set_is_active(false);
|
||||
active_output = output;
|
||||
active_output->set_is_active(true);
|
||||
workspace_manager.request_focus(output->get_active_workspace());
|
||||
}
|
||||
|
||||
if (output->get_active_workspace() >= 0)
|
||||
{
|
||||
active_output->get_active_tree().select_window_from_point(static_cast<int>(x), static_cast<int>(y));
|
||||
}
|
||||
active_output->screen->get_active_tree().select_window_from_point(static_cast<int>(x), static_cast<int>(y));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -254,15 +271,15 @@ auto MiracleWindowManagementPolicy::place_new_window(
|
||||
}
|
||||
|
||||
pending_output = active_output;
|
||||
return active_output->screen->get_active_tree().allocate_position(requested_specification);
|
||||
return active_output->get_active_tree().allocate_position(requested_specification);
|
||||
}
|
||||
|
||||
void MiracleWindowManagementPolicy::_add_to_output_immediately(miral::Window& window, std::shared_ptr<OutputInfo>& output)
|
||||
void MiracleWindowManagementPolicy::_add_to_output_immediately(miral::Window& window, std::shared_ptr<Screen>& output)
|
||||
{
|
||||
miral::WindowSpecification spec;
|
||||
spec = output->screen->get_active_tree().allocate_position(spec);
|
||||
spec = output->get_active_tree().allocate_position(spec);
|
||||
window_manager_tools.modify_window(window, spec);
|
||||
output->screen->get_active_tree().advise_new_window(window_manager_tools.info_for(window));
|
||||
output->get_active_tree().advise_new_window(window_manager_tools.info_for(window));
|
||||
}
|
||||
|
||||
void MiracleWindowManagementPolicy::advise_new_window(miral::WindowInfo const& window_info)
|
||||
@ -287,7 +304,7 @@ void MiracleWindowManagementPolicy::advise_new_window(miral::WindowInfo const& w
|
||||
return;
|
||||
}
|
||||
|
||||
shared_output->screen->get_active_tree().advise_new_window(window_info);
|
||||
shared_output->get_active_tree().advise_new_window(window_info);
|
||||
pending_output.reset();
|
||||
}
|
||||
|
||||
@ -295,7 +312,7 @@ void MiracleWindowManagementPolicy::handle_window_ready(miral::WindowInfo &windo
|
||||
{
|
||||
for (auto const& output : output_list)
|
||||
{
|
||||
if (output->screen->get_active_tree().handle_window_ready(window_info))
|
||||
if (output->get_active_tree().handle_window_ready(window_info))
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -303,14 +320,14 @@ void MiracleWindowManagementPolicy::handle_window_ready(miral::WindowInfo &windo
|
||||
void MiracleWindowManagementPolicy::advise_focus_gained(const miral::WindowInfo &window_info)
|
||||
{
|
||||
for (auto const& output : output_list)
|
||||
output->screen->get_active_tree().advise_focus_gained(window_info.window());
|
||||
output->get_active_tree().advise_focus_gained(window_info.window());
|
||||
window_manager_tools.raise_tree(window_info.window());
|
||||
}
|
||||
|
||||
void MiracleWindowManagementPolicy::advise_focus_lost(const miral::WindowInfo &window_info)
|
||||
{
|
||||
for (auto const& output : output_list)
|
||||
output->screen->get_active_tree().advise_focus_lost(window_info.window());
|
||||
output->get_active_tree().advise_focus_lost(window_info.window());
|
||||
}
|
||||
|
||||
void MiracleWindowManagementPolicy::advise_delete_window(const miral::WindowInfo &window_info)
|
||||
@ -324,7 +341,7 @@ void MiracleWindowManagementPolicy::advise_delete_window(const miral::WindowInfo
|
||||
}
|
||||
|
||||
for (auto const& output : output_list)
|
||||
output->screen->get_active_tree().advise_delete_window(window_info.window());
|
||||
output->get_active_tree().advise_delete_window(window_info.window());
|
||||
}
|
||||
|
||||
void MiracleWindowManagementPolicy::advise_move_to(miral::WindowInfo const& window_info, geom::Point top_left)
|
||||
@ -334,10 +351,9 @@ void MiracleWindowManagementPolicy::advise_move_to(miral::WindowInfo const& wind
|
||||
void MiracleWindowManagementPolicy::advise_output_create(miral::Output const& output)
|
||||
{
|
||||
WindowTreeOptions options = { config.get_gap_size_x(), config.get_gap_size_y() };
|
||||
auto new_tree = std::make_shared<OutputInfo>(
|
||||
output,
|
||||
std::make_shared<Screen>(workspace_manager, output.extents(), window_manager_tools, options));
|
||||
workspace_manager.request_first_available_workspace(new_tree->screen);
|
||||
auto new_tree = std::make_shared<Screen>(
|
||||
output, workspace_manager, output.extents(), window_manager_tools, options);
|
||||
workspace_manager.request_first_available_workspace(new_tree);
|
||||
output_list.push_back(new_tree);
|
||||
if (active_output == nullptr)
|
||||
active_output = new_tree;
|
||||
@ -357,9 +373,9 @@ void MiracleWindowManagementPolicy::advise_output_update(miral::Output const& up
|
||||
{
|
||||
for (auto& output : output_list)
|
||||
{
|
||||
if (output->output.is_same_output(original))
|
||||
if (output->get_output().is_same_output(original))
|
||||
{
|
||||
for (auto& workspace : output->screen->get_workspaces())
|
||||
for (auto& workspace : output->get_workspaces())
|
||||
{
|
||||
workspace.tree.set_output_area(updated.extents());
|
||||
}
|
||||
@ -373,7 +389,7 @@ void MiracleWindowManagementPolicy::advise_output_delete(miral::Output const& ou
|
||||
for (auto it = output_list.begin(); it != output_list.end();)
|
||||
{
|
||||
auto other_output = *it;
|
||||
if (other_output->output.is_same_output(output))
|
||||
if (other_output->get_output().is_same_output(output))
|
||||
{
|
||||
it = output_list.erase(it);
|
||||
if (other_output == active_output)
|
||||
@ -381,7 +397,7 @@ void MiracleWindowManagementPolicy::advise_output_delete(miral::Output const& ou
|
||||
if (output_list.empty())
|
||||
{
|
||||
// All nodes should become orphaned
|
||||
for (auto& workspace : other_output->screen->get_workspaces())
|
||||
for (auto& workspace : other_output->get_workspaces())
|
||||
{
|
||||
workspace.tree.foreach_node([&](auto node)
|
||||
{
|
||||
@ -397,9 +413,9 @@ void MiracleWindowManagementPolicy::advise_output_delete(miral::Output const& ou
|
||||
else
|
||||
{
|
||||
active_output = output_list[0];
|
||||
for (auto& workspace : other_output->screen->get_workspaces())
|
||||
for (auto& workspace : other_output->get_workspaces())
|
||||
{
|
||||
active_output->screen->get_active_tree().add_tree(workspace.tree);
|
||||
active_output->get_active_tree().add_tree(workspace.tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -412,7 +428,7 @@ void MiracleWindowManagementPolicy::advise_state_change(miral::WindowInfo const&
|
||||
{
|
||||
for (auto const& output : output_list)
|
||||
{
|
||||
if (output->screen->get_active_tree().advise_state_change(window_info, state))
|
||||
if (output->get_active_tree().advise_state_change(window_info, state))
|
||||
{
|
||||
break;
|
||||
}
|
||||
@ -430,7 +446,7 @@ void MiracleWindowManagementPolicy::handle_modify_window(
|
||||
for (auto const& output : output_list)
|
||||
{
|
||||
bool found = false;
|
||||
for (auto& workspace : output->screen->get_workspaces())
|
||||
for (auto& workspace : output->get_workspaces())
|
||||
{
|
||||
if (workspace.tree.advise_fullscreen_window(window_info))
|
||||
{
|
||||
@ -447,7 +463,7 @@ void MiracleWindowManagementPolicy::handle_modify_window(
|
||||
for (auto const& output : output_list)
|
||||
{
|
||||
bool found = false;
|
||||
for (auto& workspace : output->screen->get_workspaces())
|
||||
for (auto& workspace : output->get_workspaces())
|
||||
{
|
||||
if (workspace.tree.advise_restored_window(window_info))
|
||||
{
|
||||
@ -464,7 +480,7 @@ void MiracleWindowManagementPolicy::handle_modify_window(
|
||||
for (auto const& output :output_list)
|
||||
{
|
||||
bool found = false;
|
||||
for (auto& workspace : output->screen->get_workspaces())
|
||||
for (auto& workspace : output->get_workspaces())
|
||||
{
|
||||
if (workspace.tree.constrain(window_info))
|
||||
{
|
||||
@ -493,7 +509,7 @@ MiracleWindowManagementPolicy::confirm_placement_on_display(
|
||||
for (auto const& output : output_list)\
|
||||
{
|
||||
bool found = false;
|
||||
for (auto& workspace : output->screen->get_workspaces())
|
||||
for (auto& workspace : output->get_workspaces())
|
||||
{
|
||||
if (workspace.tree.confirm_placement_on_display(window_info, new_state, modified_placement))
|
||||
{
|
||||
@ -536,7 +552,7 @@ void MiracleWindowManagementPolicy::advise_application_zone_create(miral::Zone c
|
||||
{
|
||||
for (auto const& output : output_list)
|
||||
{
|
||||
output->screen->advise_application_zone_create(application_zone);
|
||||
output->advise_application_zone_create(application_zone);
|
||||
}
|
||||
}
|
||||
|
||||
@ -544,7 +560,7 @@ void MiracleWindowManagementPolicy::advise_application_zone_update(miral::Zone c
|
||||
{
|
||||
for (auto const& output : output_list)
|
||||
{
|
||||
output->screen->advise_application_zone_update(updated, original);
|
||||
output->advise_application_zone_update(updated, original);
|
||||
}
|
||||
}
|
||||
|
||||
@ -552,6 +568,6 @@ void MiracleWindowManagementPolicy::advise_application_zone_delete(miral::Zone c
|
||||
{
|
||||
for (auto const& output : output_list)
|
||||
{
|
||||
output->screen->advise_application_zone_delete(application_zone);
|
||||
output->advise_application_zone_delete(application_zone);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "screen.h"
|
||||
#include "miracle_config.h"
|
||||
#include "workspace_manager.h"
|
||||
#include "ipc.h"
|
||||
|
||||
#include <miral/window_manager_tools.h>
|
||||
#include <miral/window_management_policy.h>
|
||||
@ -21,12 +22,6 @@ class MirRunner;
|
||||
namespace miracle
|
||||
{
|
||||
|
||||
struct OutputInfo
|
||||
{
|
||||
miral::Output output;
|
||||
std::shared_ptr<Screen> screen;
|
||||
};
|
||||
|
||||
class MiracleWindowManagementPolicy : public miral::WindowManagementPolicy
|
||||
{
|
||||
public:
|
||||
@ -36,7 +31,7 @@ public:
|
||||
miral::InternalClientLauncher const&,
|
||||
miral::MirRunner&,
|
||||
MiracleConfig const&);
|
||||
~MiracleWindowManagementPolicy() override = default;
|
||||
~MiracleWindowManagementPolicy() override;
|
||||
|
||||
bool handle_keyboard_event(MirKeyboardEvent const* event) override;
|
||||
bool handle_pointer_event(MirPointerEvent const* event) override;
|
||||
@ -80,19 +75,23 @@ public:
|
||||
void advise_application_zone_update(miral::Zone const& updated, miral::Zone const& original) override;
|
||||
void advise_application_zone_delete(miral::Zone const& application_zone) override;
|
||||
|
||||
std::shared_ptr<Screen> const& get_active_output() { return active_output; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<OutputInfo> active_output;
|
||||
std::vector<std::shared_ptr<OutputInfo>> output_list;
|
||||
std::weak_ptr<OutputInfo> pending_output;
|
||||
std::shared_ptr<Screen> active_output;
|
||||
std::vector<std::shared_ptr<Screen>> output_list;
|
||||
std::weak_ptr<Screen> pending_output;
|
||||
std::vector<Window> orphaned_window_list;
|
||||
miral::WindowManagerTools window_manager_tools;
|
||||
miral::ExternalClientLauncher const external_client_launcher;
|
||||
miral::InternalClientLauncher const internal_client_launcher;
|
||||
miral::MirRunner& runner;
|
||||
MiracleConfig const& config;
|
||||
WorkspaceObserverRegistrar workspace_observer_registrar;
|
||||
WorkspaceManager workspace_manager;
|
||||
std::shared_ptr<Ipc> ipc;
|
||||
|
||||
void _add_to_output_immediately(Window&, std::shared_ptr<OutputInfo>&);
|
||||
void _add_to_output_immediately(Window&, std::shared_ptr<Screen>&);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -5,11 +5,13 @@
|
||||
using namespace miracle;
|
||||
|
||||
Screen::Screen(
|
||||
miral::Output const& output,
|
||||
WorkspaceManager& workspace_manager,
|
||||
geom::Rectangle const& area,
|
||||
miral::WindowManagerTools const& tools,
|
||||
WindowTreeOptions const& options)
|
||||
: workspace_manager{workspace_manager},
|
||||
: output{output},
|
||||
workspace_manager{workspace_manager},
|
||||
area{area},
|
||||
tools{tools},
|
||||
options{options}
|
||||
@ -27,16 +29,15 @@ WindowTree &Screen::get_active_tree()
|
||||
throw std::runtime_error("Unable to find the active tree. We shouldn't be here");
|
||||
}
|
||||
|
||||
void Screen::advise_new_workspace(char workspace)
|
||||
void Screen::advise_new_workspace(int workspace)
|
||||
{
|
||||
workspaces.push_back({
|
||||
workspace,
|
||||
WindowTree(this, tools, options)
|
||||
});
|
||||
make_workspace_active(workspace);
|
||||
}
|
||||
|
||||
void Screen::advise_workspace_deleted(char workspace)
|
||||
void Screen::advise_workspace_deleted(int workspace)
|
||||
{
|
||||
for (auto it = workspaces.begin(); it != workspaces.end(); it++)
|
||||
{
|
||||
@ -48,7 +49,7 @@ void Screen::advise_workspace_deleted(char workspace)
|
||||
}
|
||||
}
|
||||
|
||||
bool Screen::make_workspace_active(char key)
|
||||
bool Screen::advise_workspace_active(int key)
|
||||
{
|
||||
for (auto& workspace : workspaces)
|
||||
{
|
||||
@ -93,6 +94,17 @@ void Screen::show(ScreenWorkspaceInfo& info)
|
||||
info.tree.show();
|
||||
}
|
||||
|
||||
const ScreenWorkspaceInfo &Screen::get_workspace(int key)
|
||||
{
|
||||
for (auto const& workspace : workspaces)
|
||||
{
|
||||
if (workspace.workspace == key)
|
||||
return workspace;
|
||||
}
|
||||
|
||||
mir::fatal_error("Cannot find workspace with key: %c", key);
|
||||
}
|
||||
|
||||
void Screen::advise_application_zone_create(miral::Zone const& application_zone)
|
||||
{
|
||||
if (application_zone.extents().contains(area))
|
||||
|
19
src/screen.h
19
src/screen.h
@ -3,6 +3,7 @@
|
||||
|
||||
#include "window_tree.h"
|
||||
#include <memory>
|
||||
#include <miral/output.h>
|
||||
|
||||
namespace miracle
|
||||
{
|
||||
@ -17,7 +18,7 @@ struct NodeResurrection
|
||||
|
||||
struct ScreenWorkspaceInfo
|
||||
{
|
||||
char workspace;
|
||||
int workspace;
|
||||
WindowTree tree;
|
||||
std::vector<NodeResurrection> nodes_to_resurrect;
|
||||
};
|
||||
@ -29,6 +30,7 @@ class Screen
|
||||
{
|
||||
public:
|
||||
Screen(
|
||||
miral::Output const& output,
|
||||
WorkspaceManager& workspace_manager,
|
||||
geom::Rectangle const& area,
|
||||
miral::WindowManagerTools const& tools,
|
||||
@ -36,10 +38,12 @@ public:
|
||||
~Screen() = default;
|
||||
|
||||
WindowTree& get_active_tree();
|
||||
void advise_new_workspace(char workspace);
|
||||
void advise_workspace_deleted(char workspace);
|
||||
bool make_workspace_active(char workspace);
|
||||
int get_active_workspace() const { return active_workspace; }
|
||||
void advise_new_workspace(int workspace);
|
||||
void advise_workspace_deleted(int workspace);
|
||||
bool advise_workspace_active(int workspace);
|
||||
std::vector<ScreenWorkspaceInfo>& get_workspaces() { return workspaces; }
|
||||
ScreenWorkspaceInfo const& get_workspace(int key);
|
||||
void advise_application_zone_create(miral::Zone const& application_zone);
|
||||
void advise_application_zone_update(miral::Zone const& updated, miral::Zone const& original);
|
||||
void advise_application_zone_delete(miral::Zone const& application_zone);
|
||||
@ -47,15 +51,20 @@ public:
|
||||
|
||||
geom::Rectangle const& get_area() { return area; }
|
||||
std::vector<miral::Zone> const& get_app_zones() { return application_zone_list; }
|
||||
miral::Output const& get_output() { return output; }
|
||||
bool is_active() const { return is_active_; }
|
||||
void set_is_active(bool new_is_active) { is_active_ = new_is_active; }
|
||||
|
||||
private:
|
||||
miral::Output output;
|
||||
WorkspaceManager& workspace_manager;
|
||||
miral::WindowManagerTools tools;
|
||||
geom::Rectangle area;
|
||||
WindowTreeOptions options;
|
||||
char active_workspace = '\0';
|
||||
int active_workspace = -1;
|
||||
std::vector<ScreenWorkspaceInfo> workspaces;
|
||||
std::vector<miral::Zone> application_zone_list;
|
||||
bool is_active_ = false;
|
||||
|
||||
void hide(ScreenWorkspaceInfo&);
|
||||
void show(ScreenWorkspaceInfo&);
|
||||
|
@ -1,63 +1,67 @@
|
||||
#define MIR_LOG_COMPONENT "workspace_manager"
|
||||
#include "workspace_manager.h"
|
||||
#include "screen.h"
|
||||
#include <mir/log.h>
|
||||
|
||||
using namespace mir::geometry;
|
||||
using namespace miral;
|
||||
using namespace miracle;
|
||||
|
||||
namespace
|
||||
{
|
||||
char DEFAULT_WORKSPACES[] = {'1','2','3','4','5','6','7','8','9', '0'};
|
||||
}
|
||||
|
||||
WorkspaceManager::WorkspaceManager(WindowManagerTools const& tools) :
|
||||
tools_{tools}
|
||||
WorkspaceManager::WorkspaceManager(
|
||||
WindowManagerTools const& tools,
|
||||
WorkspaceObserverRegistrar& registry,
|
||||
std::function<std::shared_ptr<Screen> const()> const& get_active_screen) :
|
||||
tools_{tools},
|
||||
registry{registry},
|
||||
get_active_screen{get_active_screen}
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<Screen> WorkspaceManager::request_workspace(std::shared_ptr<Screen> screen, char key)
|
||||
std::shared_ptr<Screen> WorkspaceManager::request_workspace(std::shared_ptr<Screen> screen, int key)
|
||||
{
|
||||
for (auto workspace : workspaces)
|
||||
if (workspaces[key] != nullptr)
|
||||
{
|
||||
if (workspace.key == key)
|
||||
auto workspace = workspaces[key];
|
||||
auto active_workspace = workspace->get_active_workspace();
|
||||
if (active_workspace == key)
|
||||
{
|
||||
workspace.screen->make_workspace_active(key);
|
||||
return workspace.screen;
|
||||
mir::log_warning("Same workspace selected twice in a row");
|
||||
return workspace;
|
||||
}
|
||||
|
||||
request_focus(key);
|
||||
return workspace;
|
||||
}
|
||||
|
||||
workspaces.push_back({
|
||||
key,
|
||||
screen
|
||||
});
|
||||
workspaces[key] = screen;
|
||||
screen->advise_new_workspace(key);
|
||||
|
||||
request_focus(key);
|
||||
registry.advise_created(workspaces[key], key);
|
||||
return screen;
|
||||
}
|
||||
|
||||
bool WorkspaceManager::request_first_available_workspace(std::shared_ptr<Screen> screen)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
for (int i = 1; i < NUM_WORKSPACES; i++)
|
||||
{
|
||||
bool can_use = true;
|
||||
for (auto workspace : workspaces)
|
||||
if (workspaces[i] == nullptr)
|
||||
{
|
||||
if (workspace.key == DEFAULT_WORKSPACES[i])
|
||||
{
|
||||
can_use = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (can_use)
|
||||
{
|
||||
request_workspace(screen, DEFAULT_WORKSPACES[i]);
|
||||
request_workspace(screen, i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (workspaces[0] == nullptr)
|
||||
{
|
||||
request_workspace(screen, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WorkspaceManager::move_active_to_workspace(std::shared_ptr<Screen> screen, char workspace)
|
||||
bool WorkspaceManager::move_active_to_workspace(std::shared_ptr<Screen> screen, int workspace)
|
||||
{
|
||||
auto window = tools_.active_window();
|
||||
if (!window)
|
||||
@ -81,17 +85,32 @@ bool WorkspaceManager::move_active_to_workspace(std::shared_ptr<Screen> screen,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WorkspaceManager::delete_workspace(char key)
|
||||
bool WorkspaceManager::delete_workspace(int key)
|
||||
{
|
||||
for (auto it = workspaces.begin(); it != workspaces.end(); it++)
|
||||
if (workspaces[key])
|
||||
{
|
||||
if (it->key == key)
|
||||
{
|
||||
it->screen->advise_workspace_deleted(key);
|
||||
workspaces.erase(it);
|
||||
return true;
|
||||
}
|
||||
workspaces[key]->advise_workspace_deleted(key);
|
||||
registry.advise_removed(workspaces[key], key);
|
||||
workspaces[key] = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void WorkspaceManager::request_focus(int key)
|
||||
{
|
||||
if (!workspaces[key])
|
||||
return;
|
||||
|
||||
auto active_screen = get_active_screen();
|
||||
workspaces[key]->advise_workspace_active(key);
|
||||
|
||||
if (active_screen != nullptr)
|
||||
{
|
||||
auto active_workspace = active_screen->get_active_workspace();
|
||||
registry.advise_focused(active_screen, active_workspace, workspaces[key], key);
|
||||
}
|
||||
else
|
||||
registry.advise_focused(nullptr, -1, workspaces[key], key);
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
#ifndef WORKSPACE_MANAGER_H
|
||||
#define WORKSPACE_MANAGER_H
|
||||
|
||||
#include "workspace_observer.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <miral/window_manager_tools.h>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
namespace miracle
|
||||
{
|
||||
@ -18,45 +21,36 @@ using miral::Workspace;
|
||||
|
||||
class Screen;
|
||||
|
||||
// TODO:
|
||||
// As it stands, this isn't exactly what I want. What I do want
|
||||
// is a workspace manager that is output-aware. The data structure
|
||||
// should be a global WorkspaceManager who holds a mapping of Screens -> Workspaces.
|
||||
// Workspaces should have a name and be organized according to that name. This
|
||||
// goes to mean that we should ignore the idea of a vector of workspaces, and settle
|
||||
// for char-encoded workspaces instead.
|
||||
|
||||
struct WorkspaceInfo
|
||||
{
|
||||
char key;
|
||||
std::shared_ptr<Screen> screen;
|
||||
};
|
||||
|
||||
|
||||
class WorkspaceManager
|
||||
{
|
||||
public:
|
||||
explicit WorkspaceManager(WindowManagerTools const& tools);
|
||||
explicit WorkspaceManager(
|
||||
WindowManagerTools const& tools,
|
||||
WorkspaceObserverRegistrar& registry,
|
||||
std::function<std::shared_ptr<Screen> const()> const& get_active_screen);
|
||||
virtual ~WorkspaceManager() = default;
|
||||
|
||||
/// Request the workspace. If it does not yet exist, then one
|
||||
/// is created on the current Screen. If it does exist, we navigate
|
||||
/// to the screen containing that workspace and show it if it
|
||||
/// isn't already shown.
|
||||
std::shared_ptr<Screen> request_workspace(std::shared_ptr<Screen> screen, char workspace);
|
||||
std::shared_ptr<Screen> request_workspace(std::shared_ptr<Screen> screen, int workspace);
|
||||
|
||||
bool request_first_available_workspace(std::shared_ptr<Screen> screen);
|
||||
|
||||
bool move_active_to_workspace(std::shared_ptr<Screen> screen, char workspace);
|
||||
bool move_active_to_workspace(std::shared_ptr<Screen> screen, int workspace);
|
||||
|
||||
bool delete_workspace(char workspace);
|
||||
bool delete_workspace(int workspace);
|
||||
|
||||
void request_focus(int workspace);
|
||||
|
||||
static int constexpr NUM_WORKSPACES = 10;
|
||||
std::array<std::shared_ptr<Screen>, NUM_WORKSPACES> const& get_workspaces() { return workspaces; }
|
||||
private:
|
||||
WindowManagerTools tools_;
|
||||
|
||||
std::vector<WorkspaceInfo> workspaces;
|
||||
|
||||
void erase_if_empty(std::vector<std::shared_ptr<Workspace>>::iterator const& old_workspace);
|
||||
WorkspaceObserverRegistrar& registry;
|
||||
std::function<std::shared_ptr<Screen> const()> get_active_screen;
|
||||
std::array<std::shared_ptr<Screen>, NUM_WORKSPACES> workspaces;
|
||||
};
|
||||
}
|
||||
|
||||
|
64
src/workspace_observer.cpp
Normal file
64
src/workspace_observer.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
#include "workspace_observer.h"
|
||||
|
||||
using namespace miracle;
|
||||
|
||||
namespace
|
||||
{
|
||||
static int NEXT_ID = 0;
|
||||
}
|
||||
|
||||
WorkspaceObserver::WorkspaceObserver()
|
||||
: id{NEXT_ID}
|
||||
{}
|
||||
|
||||
int WorkspaceObserver::get_id() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
void WorkspaceObserverRegistrar::register_interest(std::weak_ptr<WorkspaceObserver> observer)
|
||||
{
|
||||
observers.push_back(observer);
|
||||
}
|
||||
|
||||
void WorkspaceObserverRegistrar::unregister_interest(miracle::WorkspaceObserver& observer)
|
||||
{
|
||||
observers.erase(std::remove_if(observers.begin(), observers.end(), [&observer](std::weak_ptr<WorkspaceObserver> const& other)
|
||||
{
|
||||
if (other.expired())
|
||||
return true;
|
||||
|
||||
return other.lock()->get_id() == observer.get_id();
|
||||
}));
|
||||
}
|
||||
|
||||
void WorkspaceObserverRegistrar::advise_created(std::shared_ptr<Screen> const& info, int key)
|
||||
{
|
||||
for (auto& observer : observers)
|
||||
{
|
||||
if (!observer.expired())
|
||||
observer.lock()->on_created(info, key);
|
||||
}
|
||||
}
|
||||
|
||||
void WorkspaceObserverRegistrar::advise_removed(std::shared_ptr<Screen> const& info, int key)
|
||||
{
|
||||
for (auto& observer : observers)
|
||||
{
|
||||
if (!observer.expired())
|
||||
observer.lock()->on_removed(info, key);
|
||||
}
|
||||
}
|
||||
|
||||
void WorkspaceObserverRegistrar::advise_focused(
|
||||
std::shared_ptr<Screen> const& previous,
|
||||
int previous_key,
|
||||
std::shared_ptr<Screen> const& current,
|
||||
int current_key)
|
||||
{
|
||||
for (auto& observer : observers)
|
||||
{
|
||||
if (!observer.expired())
|
||||
observer.lock()->on_focused(previous, previous_key, current, current_key);
|
||||
}
|
||||
}
|
45
src/workspace_observer.h
Normal file
45
src/workspace_observer.h
Normal file
@ -0,0 +1,45 @@
|
||||
#ifndef MIRACLEWM_WORKSPACE_OBSERVER_H
|
||||
#define MIRACLEWM_WORKSPACE_OBSERVER_H
|
||||
|
||||
#include "screen.h"
|
||||
#include <mir/executor.h>
|
||||
#include <memory>
|
||||
|
||||
namespace miracle
|
||||
{
|
||||
|
||||
class Screen;
|
||||
|
||||
class WorkspaceObserver
|
||||
{
|
||||
public:
|
||||
virtual ~WorkspaceObserver() = default;
|
||||
virtual void on_created(std::shared_ptr<Screen> const&, int) = 0;
|
||||
virtual void on_removed(std::shared_ptr<Screen> const&, int) = 0;
|
||||
virtual void on_focused(std::shared_ptr<Screen> const& previous, int, std::shared_ptr<Screen> const& current, int) = 0;
|
||||
|
||||
int get_id() const;
|
||||
protected:
|
||||
WorkspaceObserver();
|
||||
|
||||
private:
|
||||
int id;
|
||||
};
|
||||
|
||||
class WorkspaceObserverRegistrar
|
||||
{
|
||||
public:
|
||||
WorkspaceObserverRegistrar() = default;
|
||||
void register_interest(std::weak_ptr<WorkspaceObserver>);
|
||||
void unregister_interest(WorkspaceObserver&);
|
||||
void advise_created(std::shared_ptr<Screen> const&, int);
|
||||
void advise_removed(std::shared_ptr<Screen> const&, int);
|
||||
void advise_focused(std::shared_ptr<Screen> const& previous, int, std::shared_ptr<Screen> const& current, int);
|
||||
|
||||
private:
|
||||
std::vector<std::weak_ptr<WorkspaceObserver>> observers;
|
||||
};
|
||||
|
||||
} // miracle
|
||||
|
||||
#endif //MIRACLEWM_WORKSPACE_OBSERVER_H
|
Loading…
Reference in New Issue
Block a user