feature: miracle can now integrate with systemd in a meaningful way (#228)

Miracle now supports running through systemd, including logging to the
journal and invoking processes as transient units through systemd-run.

This is controlled as a build-time feature in CMake.
This commit is contained in:
Matthew Kosarek 2024-09-07 09:07:58 -04:00 committed by GitHub
parent 2a727eab46
commit 690da3a8f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 429 additions and 47 deletions

View File

@ -14,6 +14,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(MIR_LIBRARIES_PKGCONFIG_DIRECTORY "" CACHE STRING "Search for Mir libraries pc files in this directory")
option(SNAP_BUILD "Building as a snap?" OFF)
option(SYSTEMD_INTEGRATION "Specifies that the systemd integration script will run at startup" OFF)
set(ENV{PKG_CONFIG_PATH} "${MIR_LIBRARIES_PKGCONFIG_DIRECTORY}:/usr/local/lib/pkgconfig/")
@ -79,7 +80,6 @@ add_executable(miracle-wm
src/main.cpp
)
target_include_directories(miracle-wm-implementation PUBLIC SYSTEM
${MIRAL_INCLUDE_DIRS}
${MIROIL_INCLUDE_DIRS}
@ -108,6 +108,21 @@ target_link_libraries(miracle-wm-implementation
PkgConfig::GLESv2
-lpcre2-8 -lpcre2-16 -lpcre2-32)
if(SYSTEMD_INTEGRATION)
# Install systemd files
install(FILES session/usr/lib/systemd/user/miracle-wm-session.target
session/usr/lib/systemd/user/miracle-wm-session-shutdown.target
DESTINATION /usr/lib/systemd/user/)
# Install the setup script
install(PROGRAMS session/usr/bin/libexec/miracle-wm-session-setup
DESTINATION ${CMAKE_INSTALL_LIBEXECDIR})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/miracle-wm-session.systemd.in miracle-wm-session @ONLY)
else()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/miracle-wm-session.none.in miracle-wm-session @ONLY)
endif()
target_include_directories(miracle-wm PUBLIC SYSTEM ${MIRAL_INCLUDE_DIRS})
target_link_libraries(miracle-wm PUBLIC ${MIRAL_LDFLAGS} PRIVATE miracle-wm-implementation)
@ -125,6 +140,7 @@ add_custom_target(miracle-wm-sensible-terminal ALL
install(PROGRAMS
src/miracle-wm-sensible-terminal
${CMAKE_CURRENT_BINARY_DIR}/miracle-wm-session
DESTINATION ${CMAKE_INSTALL_BINDIR}
)

View File

@ -18,7 +18,6 @@ Portions of this code originate from swaymsg, licensed under the MIT license.
See the LICENSE.Sway file for details.
**/
#ifndef _SWAY_IPC_H
#define _SWAY_IPC_H

View File

@ -18,7 +18,6 @@ Portions of this code originate from swaymsg, licensed under the MIT license.
See the LICENSE.Sway file for details.
**/
#ifndef _SWAY_IPC_CLIENT_H
#define _SWAY_IPC_CLIENT_H

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Aleksei Bavshin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

43
session/README.md Normal file
View File

@ -0,0 +1,43 @@
# Running miracle as a systemd user session
> Most of my understanding about how Wayland compositors interact with systemd
> is taken from here https://github.com/swaywm/sway/wiki/Systemd-integration
> and here https://github.com/alebastr/sway-systemd
Miracle offers a standard way of integrating with `systemd` that is largely
derived from https://github.com/alebastr/sway-systemd.
## Installation
To install miracle with systemd support, simply provide the following option to cmake:
```
cmake -DSYSTEMD_INTEGRATION=1 ..
```
## View Logs
```sh
journalctl --user --identifier miracle-wm
```
## Manual Setup
From the root of the project:
```sh
cd session
cp usr/lib/systemd/user/*.target /usr/lib/systemd/user/ # or $XDG_CONFIG_HOME/systemd/user/ or ~/.config/systemd/user
cp usr/bin/libexec/miracle-wm-session-setup /usr/local/libexec/miracle-wm/miracle-wm-session-setup
```
Then, in `/usr/local/bin/miracle-wm-session` (or wherever you have it), you should paste the following:
```sh
#!/bin/sh
systemd-cat --identifier=miracle-wm miracle-wm --systemd-session-configure=/usr/local/libexec/miracle-wm/miracle-wm-session-setup
```
Finally, log out and log back into miracle. Once running, try to run:
```sh
echo $XDG_SESSION_DESKTOP # This should be "miracle-wm"
```

View File

@ -0,0 +1,157 @@
#!/bin/sh
#
#
# Copyright (C) 2024 Matthew Kosarek
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Portions of this code originate from https://github.com/alebastr/sway-systemd,
# licensed under the MIT license. See the LICENSE.sway-systemd file for details.
#
#
#
# Address several issues with DBus activation and systemd user sessions
#
# 1. DBus-activated and systemd services do not share the environment with user
# login session. In order to make the applications that have GUI or interact
# with the compositor work as a systemd user service, certain variables must
# be propagated to the systemd and dbus.
# Possible (but not exhaustive) list of variables:
# - DISPLAY - for X11 applications that are started as user session services
# - WAYLAND_DISPLAY - similarly, this is needed for wayland-native services
# - I3SOCK/SWAYSOCK - allow services to talk with miracle using i3 IPC protocol
#
# 2. `xdg-desktop-portal` requires XDG_CURRENT_DESKTOP to be set in order to
# select the right implementation for screenshot and screencast portals.
# With all the numerous ways to start miracle, it's not possible to rely on the
# right value of the XDG_CURRENT_DESKTOP variable within the login session,
# therefore the script will ensure that it is always set to `miracle-wm`.
#
# 3. GUI applications started as a systemd service (or via xdg-autostart-generator)
# may rely on the XDG_SESSION_TYPE variable to select the backend.
# Ensure that it is always set to `wayland`.
#
# 4. The common way to autostart a systemd service along with the desktop
# environment is to add it to a `graphical-session.target`. However, systemd
# forbids starting the graphical session target directly and encourages use
# of an environment-specific target units. Therefore, the integration
# package here provides and uses `miracle-wm-session.target` which would bind to
# the `graphical-session.target`.
#
# 5. Stop the target and unset the variables when the compositor exits.
#
# References:
# - https://github.com/swaywm/sway/wiki#gtk-applications-take-20-seconds-to-start
# - https://github.com/emersion/xdg-desktop-portal-wlr/wiki/systemd-user-services,-pam,-and-environment-variables
# - https://www.freedesktop.org/software/systemd/man/systemd.special.html#graphical-session.target
# - https://systemd.io/DESKTOP_ENVIRONMENTS/
#
export XDG_CURRENT_DESKTOP=mir:miracle-wm
export XDG_SESSION_DESKTOP="${XDG_SESSION_DESKTOP:-miracle-wm}"
export XDG_SESSION_TYPE=wayland
VARIABLES="DESKTOP_SESSION XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP XDG_SESSION_TYPE"
VARIABLES="${VARIABLES} DISPLAY I3SOCK SWAYSOCK WAYLAND_DISPLAY"
SESSION_TARGET="miracle-wm-session.target"
SESSION_SHUTDOWN_TARGET="miracle-wm-session-shutdown.target"
WITH_CLEANUP=1
print_usage() {
cat <<EOH
Usage:
--help Show this help message and exit.
--add-env NAME, -E NAME
Add a variable name to the subset of environment passed
to the user session. Can be specified multiple times.
--no-cleanup Skip cleanup code at compositor exit.
EOH
}
while [ $# -gt 0 ]; do
case "$1" in
--help)
print_usage
exit 0 ;;
# The following flag is intentionally not exposed in the usage info:
# - I don't believe that's the right or safe thing to do;
# - systemd upstream is of the same opinion and has already deprecated
# the ability to import the full environment (systemd/systemd#18137)
--all-environment)
VARIABLES="" ;;
--add-env=?*)
VARIABLES="${VARIABLES} ${1#*=}" ;;
--add-env | -E)
shift
VARIABLES="${VARIABLES} ${1}" ;;
--with-cleanup)
;; # ignore (enabled by default)
--no-cleanup)
unset WITH_CLEANUP ;;
-*)
echo "Unexpected option: $1" 1>&2
print_usage
exit 1 ;;
*)
break ;;
esac
shift
done
# Check if another Miracle session is already active.
#
# Ignores all other kinds of parallel or nested sessions
# (Miracle on Gnome/KDE/X11/etc.), as the only way to detect these is to check
# for (WAYLAND_)?DISPLAY and that is know to be broken on Arch.
if systemctl --user -q is-active "$SESSION_TARGET"; then
echo "Another session found; refusing to overwrite the variables"
exit 1
fi
# DBus activation environment is independent from systemd. While most of
# dbus-activated services are already using `SystemdService` directive, some
# still don't and thus we should set the dbus environment with a separate
# command.
if hash dbus-update-activation-environment 2>/dev/null; then
# shellcheck disable=SC2086
dbus-update-activation-environment --systemd ${VARIABLES:- --all}
fi
# reset failed state of all user units
systemctl --user reset-failed
# shellcheck disable=SC2086
systemctl --user import-environment $VARIABLES
systemctl --user start "$SESSION_TARGET"
# Optionally, wait until the compositor exits and cleanup variables and services.
if [ -z "$WITH_CLEANUP" ] ||
[ -z "$SWAYSOCK" ] ||
! hash miraclemsg 2>/dev/null
then
exit 0;
fi
# declare cleanup handler and run it on script termination via kill or Ctrl-C
session_cleanup () {
# stop the session target and unset the variables
systemctl --user start --job-mode=replace-irreversibly "$SESSION_SHUTDOWN_TARGET"
if [ -n "$VARIABLES" ]; then
# shellcheck disable=SC2086
systemctl --user unset-environment $VARIABLES
fi
}
trap session_cleanup INT TERM
# wait until the compositor exits
miraclemsg -t subscribe '["shutdown"]'
# run cleanup handler on normal exit
session_cleanup

View File

@ -0,0 +1,10 @@
[Unit]
Description=Shutdown running miracle-wm session
DefaultDependencies=no
StopWhenUnneeded=true
Conflicts=graphical-session.target graphical-session-pre.target
After=graphical-session.target graphical-session-pre.target
Conflicts=miracle-wm-session.target
After=miracle-wm-session.target

View File

@ -0,0 +1,6 @@
[Unit]
Description=miracle-wm session
Documentation=man:systemd.special(7)
BindsTo=graphical-session.target
Wants=graphical-session-pre.target
After=graphical-session-pre.target

View File

@ -1,5 +1,6 @@
[Desktop Entry]
Name=Miracle
Comment=A wayland tiling window manager based on Mir
Exec=sh -l -c @CMAKE_INSTALL_FULL_BINDIR@/miracle-wm
Exec=@CMAKE_INSTALL_FULL_BINDIR@/miracle-wm-session
Type=Application
DesktopNames=miracle-wm;mir

View File

@ -33,10 +33,58 @@ AutoRestartingLauncher::AutoRestartingLauncher(
{ reap(); }); });
}
std::vector<std::string_view> split(std::string_view str, char delim)
{
std::vector<std::string_view> result;
auto left = str.begin();
for (auto it = left; it != str.end(); ++it)
{
if (*it == delim)
{
result.emplace_back(&*left, it - left);
left = it + 1;
}
}
if (left != str.end())
result.emplace_back(&*left, str.end() - left);
return result;
}
void AutoRestartingLauncher::launch(miracle::StartupApp const& cmd)
{
std::lock_guard lock { mutex };
auto pid = launcher.launch(cmd.command);
pid_t pid;
if (cmd.in_systemd_scope)
{
std::vector<std::string> result = { "systemd-run", "--user" };
if (cmd.restart_on_death)
{
result.push_back("--property");
result.push_back("Restart=on-failure");
}
size_t start = 0;
for (size_t i = 0; i < cmd.command.size(); i++)
{
if (cmd.command[i] == ' ')
{
if (start != i)
result.push_back(cmd.command.substr(start, i - start));
start = i + 1;
}
}
if (start != cmd.command.size())
result.push_back(cmd.command.substr(start));
pid = launcher.launch(result);
}
else
{
pid = launcher.launch(cmd.command);
}
if (pid <= 0)
{
mir::log_error("Unable to start external client: %s\n", cmd.command.c_str());

View File

@ -161,7 +161,7 @@ FilesystemConfiguration::FilesystemConfiguration(
mir::log_info("FilesystemConfiguration: File is being loaded immediately on construction. "
"It is assumed that you are running this inside of a test");
config_path = default_config_path;
_init(std::nullopt);
_init(std::nullopt, std::nullopt);
}
}
@ -186,28 +186,41 @@ void FilesystemConfiguration::load(mir::Server& server)
"dies, miracle will also die.",
"");
server.add_init_callback([this, config_file_name_option, no_config_option, exec_option, &server]
const char* systemd_session_configure_option = "systemd-session-configure";
server.add_configuration_option(
systemd_session_configure_option,
"If specified, this script will setup the systemd session before any apps are run",
"");
server.add_init_callback([this, config_file_name_option, no_config_option, exec_option, systemd_session_configure_option, &server]
{
auto const server_opts = server.get_options();
no_config = server_opts->get<bool>(no_config_option);
config_path = server_opts->get<std::string>(config_file_name_option);
std::optional<StartupApp> systemd_app = std::nullopt;
std::optional<StartupApp> exec_app = std::nullopt;
auto systemd_session_configure = server_opts->get<std::string>(systemd_session_configure_option);
if (!systemd_session_configure.empty())
systemd_app = StartupApp { .command = systemd_session_configure };
if (server_opts->is_set(exec_option))
{
auto command = server_opts->get<std::string>(exec_option);
if (!command.empty())
{
exec_app = StartupApp {
.command=command,
.should_halt_compositor_on_death=true
.command = command,
.should_halt_compositor_on_death = true
};
}
}
_init(exec_app);
_init(systemd_app, exec_app);
});
}
void FilesystemConfiguration::_init(std::optional<StartupApp> const& startup_app)
void FilesystemConfiguration::_init(std::optional<StartupApp> const& systemd_app, std::optional<StartupApp> const& exec_app)
{
if (no_config)
{
@ -240,17 +253,20 @@ void FilesystemConfiguration::_init(std::optional<StartupApp> const& startup_app
_reload();
// If the user specified an --exec <APP_NAME>, let's add that to the list
if (startup_app)
// If the user specified an --systemd-session-configure <APP_NAME>, let's add that to the list
if (systemd_app)
{
mir::log_info("Miracle will die when the application specified with --exec dies");
options.startup_apps.push_back(startup_app.value());
options.startup_apps.insert(options.startup_apps.begin(), systemd_app.value());
}
for (auto const& listener : config_ready_listeners)
listener();
// If the user specified an --exec <APP_NAME>, let's add that to the list
if (exec_app)
{
mir::log_info("Miracle will die when the application specified with --exec dies");
options.startup_apps.push_back(exec_app.value());
}
config_ready_listeners.clear();
is_loaded_ = true;
_watch(runner);
}
@ -653,11 +669,15 @@ void FilesystemConfiguration::_reload()
auto command = wrap_command(node["command"].as<std::string>());
bool restart_on_death = false;
if (node["restart_on_death"])
{
restart_on_death = node["restart_on_death"].as<bool>();
}
options.startup_apps.push_back({ std::move(command), restart_on_death });
bool in_systemd_scope = false;
if (node["in_systemd_scope"])
in_systemd_scope = node["in_systemd_scope"].as<bool>();
options.startup_apps.push_back({ .command = std::move(command),
.restart_on_death = restart_on_death,
.in_systemd_scope = in_systemd_scope });
}
catch (YAML::BadConversion const& e)
{
@ -932,11 +952,6 @@ uint FilesystemConfiguration::parse_modifier(std::string const& stringified_acti
return mir_input_event_modifier_none;
}
void FilesystemConfiguration::on_config_ready(std::function<void()> const& listener)
{
config_ready_listeners.push_back(listener);
}
std::string const& FilesystemConfiguration::get_filename() const
{
return config_path;

View File

@ -118,6 +118,7 @@ struct StartupApp
bool restart_on_death = false;
bool no_startup_id = false;
bool should_halt_compositor_on_death = false;
bool in_systemd_scope = false;
};
struct EnvironmentVariable
@ -153,7 +154,6 @@ class MiracleConfig
public:
virtual ~MiracleConfig() = default;
virtual void load(mir::Server& server) = 0;
virtual void on_config_ready(std::function<void()> const&) = 0;
[[nodiscard]] virtual std::string const& get_filename() const = 0;
[[nodiscard]] virtual MirInputEventModifier get_input_event_modifier() const = 0;
[[nodiscard]] virtual CustomKeyCommand const* matches_custom_key_command(MirKeyboardAction action, int scan_code, unsigned int modifiers) const = 0;
@ -190,7 +190,6 @@ public:
auto operator=(FilesystemConfiguration const&) -> FilesystemConfiguration& = default;
void load(mir::Server& server) override;
void on_config_ready(std::function<void()> const&) override;
[[nodiscard]] std::string const& get_filename() const override;
[[nodiscard]] MirInputEventModifier get_input_event_modifier() const override;
[[nodiscard]] CustomKeyCommand const* matches_custom_key_command(MirKeyboardAction action, int scan_code, unsigned int modifiers) const override;
@ -222,7 +221,7 @@ private:
};
static uint parse_modifier(std::string const& stringified_action_key);
void _init(std::optional<StartupApp> const&);
void _init(std::optional<StartupApp> const& systemd_app, std::optional<StartupApp> const& exec_app);
void _reload();
void _watch(miral::MirRunner& runner);
void read_animation_definitions(YAML::Node const&);
@ -237,8 +236,8 @@ private:
std::unique_ptr<miral::FdHandle> watch_handle;
int file_watch = 0;
std::mutex mutex;
std::vector<std::function<void()>> config_ready_listeners;
std::atomic<bool> has_changes = false;
bool is_loaded_ = false;
static const uint miracle_input_event_modifier_default = 1 << 18;
struct ConfigDetails

View File

@ -351,6 +351,10 @@ Ipc::Ipc(miral::MirRunner& runner,
});
}
Ipc::~Ipc()
{
}
void Ipc::on_created(std::shared_ptr<Output> const& info, int key)
{
json j = {
@ -432,6 +436,22 @@ void Ipc::on_changed(WindowManagerMode mode)
}
}
void Ipc::on_shutdown()
{
auto response = to_string(json({
{ "change", "exit" }
}));
for (auto& client : clients)
{
if ((client.subscribed_events & event_mask(IPC_EVENT_SHUTDOWN)) == 0)
{
continue;
}
send_reply(client, IPC_EVENT_SHUTDOWN, response);
}
}
Ipc::IpcClient& Ipc::get_client(int fd)
{
for (auto& client : clients)
@ -549,6 +569,8 @@ void Ipc::handle_command(miracle::Ipc::IpcClient& client, uint32_t payload_lengt
client.subscribed_events |= event_mask(IPC_EVENT_TICK);
send_event_tick = true;
}
else if (event_type == "shutdown")
client.subscribed_events |= event_mask(IPC_EVENT_SHUTDOWN);
else
{
mir::log_error("Cannot process IPC subscription event for event_type: %s", event_type.c_str());

View File

@ -87,11 +87,13 @@ public:
std::shared_ptr<mir::ServerActionQueue> const&,
I3CommandExecutor&,
std::shared_ptr<MiracleConfig> const&);
~Ipc();
void on_created(std::shared_ptr<Output> const& info, int key) override;
void on_removed(std::shared_ptr<Output> const& info, int key) override;
void on_focused(std::shared_ptr<Output> const& previous, int, std::shared_ptr<Output> const& current, int) override;
void on_changed(WindowManagerMode mode) override;
void on_shutdown();
private:
struct IpcClient

View File

@ -97,14 +97,6 @@ int main(int argc, char const* argv[])
Keymap config_keymap;
config->on_config_ready([&]()
{
for (auto const& app : config->get_startup_apps())
{
auto_restarting_launcher.launch(app);
}
});
notify_init("miracle-wm");
WaylandExtensions wayland_extensions = WaylandExtensions {}

2
src/miracle-wm-session.none.in Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
@CMAKE_INSTALL_FULL_BINDIR@/miracle-wm

View File

@ -0,0 +1,2 @@
#!/bin/sh
systemd-cat --identifier=miracle-wm @CMAKE_INSTALL_FULL_BINDIR@/miracle-wm --systemd-session-configure=@CMAKE_INSTALL_FULL_LIBEXECDIR@/miracle-wm-session-setup

View File

@ -81,7 +81,7 @@ std::shared_ptr<Container> Output::intersect(const MirPointerEvent* event)
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);
if (auto const window = tools.window_at({x, y}))
if (auto const window = tools.window_at({ x, y }))
return window_controller.get_container(window);
return nullptr;

View File

@ -17,12 +17,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MIR_LOG_COMPONENT "parent_container"
#include "parent_container.h"
#include "compositor_state.h"
#include "config.h"
#include "container.h"
#include "leaf_container.h"
#include "tiling_window_tree.h"
#include "workspace.h"
#include "compositor_state.h"
#include <cmath>
#include <mir/log.h>

View File

@ -619,6 +619,18 @@ void Policy::advise_application_zone_delete(miral::Zone const& application_zone)
}
}
void Policy::advise_end()
{
if (is_starting_)
{
is_starting_ = false;
for (auto const& app : config->get_startup_apps())
{
external_client_launcher.launch(app);
}
}
}
void Policy::try_toggle_resize_mode()
{
if (!state.active)
@ -747,6 +759,7 @@ bool Policy::try_close_window()
bool Policy::quit()
{
ipc->on_shutdown();
runner.stop();
return true;
}

View File

@ -97,6 +97,7 @@ public:
void advise_application_zone_create(miral::Zone const& application_zone) override;
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;
void advise_end() override;
// Requests
@ -127,6 +128,7 @@ public:
[[nodiscard]] CompositorState const& get_state() const { return state; }
private:
bool is_starting_ = true;
CompositorState& state;
std::vector<std::shared_ptr<Output>> output_list;
std::weak_ptr<Output> pending_output;

View File

@ -264,7 +264,7 @@ void TilingWindowTree::toggle_layout(Container& container)
if (parent->get_direction() == LayoutScheme::horizontal)
handle_layout_scheme(LayoutScheme::vertical, container);
else if (parent ->get_direction() == LayoutScheme::vertical)
else if (parent->get_direction() == LayoutScheme::vertical)
handle_layout_scheme(LayoutScheme::horizontal, container);
else
mir::log_error("Parent with stack layout scheme cannot be toggled");
@ -408,9 +408,7 @@ std::shared_ptr<LeafContainer> TilingWindowTree::handle_select(
auto grandparent_direction = parent->get_direction();
int index = parent->get_index_of_node(current_node);
if (is_vertical && grandparent_direction == LayoutScheme::vertical
|| !is_vertical && (
grandparent_direction == LayoutScheme::horizontal
|| grandparent_direction == LayoutScheme::tabbing))
|| !is_vertical && (grandparent_direction == LayoutScheme::horizontal || grandparent_direction == LayoutScheme::tabbing))
{
if (is_negative)
{

View File

@ -156,7 +156,7 @@ private:
bool is_hidden = false;
int config_handle = 0;
void handle_layout_scheme(LayoutScheme direction, Container &container);
void handle_layout_scheme(LayoutScheme direction, Container& container);
void handle_resize(Container& node, Direction direction, int amount);
/// Constrains the container to its tile in the tree

View File

@ -14,7 +14,7 @@ def server():
env = os.environ.copy()
env['WAYLAND_DISPLAY'] = 'wayland-98'
process = Popen([command, '--platform-display-libs', 'mir:virtual', '--virtual-output', '800x600'],
process = Popen([command, '--platform-display-libs', 'mir:virtual', '--virtual-output', '800x600', '--no-config', '1'],
env=env, stdout=PIPE, stderr=STDOUT)
socket = ""

View File

@ -0,0 +1,36 @@
from i3ipc import Connection, Event, TickEvent
import time
import threading
class TestSendTick:
def test_send_tick(self, server):
class Reply:
def __init__(self) -> None:
self.is_shutdown = False
print(f"Using server: {server}")
reply = Reply()
def wait_on_shutdown():
def on_shutdown(i3, e):
reply.is_shutdown = True
conn1 = Connection(server)
conn1.on(Event.SHUTDOWN, on_shutdown)
try:
conn1.main()
except Exception as e:
print(f"Encountered error: {e}")
t1 = threading.Thread(target=wait_on_shutdown)
t1.start()
time.sleep(1) # A small wait time to ensure that the subscribe goes through first
try:
conn2 = Connection(server)
conn2.command("exit")
t1.join()
except Exception as e:
print(f"Encountered error: {e}")
assert reply.is_shutdown == True

View File

@ -27,7 +27,6 @@ namespace test
class StubConfiguration : public miracle::MiracleConfig
{
void load(mir::Server& server) override { }
void on_config_ready(std::function<void()> const&) override { }
[[nodiscard]] std::string const& get_filename() const override { return ""; }
[[nodiscard]] MirInputEventModifier get_input_event_modifier() const override { return mir_input_event_modifier_none; }
[[nodiscard]] CustomKeyCommand const* matches_custom_key_command(MirKeyboardAction action, int scan_code, unsigned int modifiers) const override