mirror of
https://github.com/miracle-wm-org/miracle-wm.git
synced 2024-11-22 03:02:17 +03:00
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:
parent
2a727eab46
commit
690da3a8f4
@ -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}
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
21
session/LICENSE.sway-systemd
Normal file
21
session/LICENSE.sway-systemd
Normal 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
43
session/README.md
Normal 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"
|
||||
```
|
157
session/usr/bin/libexec/miracle-wm-session-setup
Executable file
157
session/usr/bin/libexec/miracle-wm-session-setup
Executable 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
|
@ -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
|
6
session/usr/lib/systemd/user/miracle-wm-session.target
Normal file
6
session/usr/lib/systemd/user/miracle-wm-session.target
Normal 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
|
@ -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
|
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
22
src/ipc.cpp
22
src/ipc.cpp
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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
2
src/miracle-wm-session.none.in
Executable file
@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
@CMAKE_INSTALL_FULL_BINDIR@/miracle-wm
|
2
src/miracle-wm-session.systemd.in
Executable file
2
src/miracle-wm-session.systemd.in
Executable 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
|
@ -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;
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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 = ""
|
||||
|
36
tests/ipc/test_shutdown.py
Normal file
36
tests/ipc/test_shutdown.py
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user