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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/user/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ reload does not update environments already captured by running processes.
Restart Umbriel after changing it, then fully quit and relaunch long-running
applications such as Steam if they survived the session restart.

## Events

```toml
[events]
lid_close = "notify-send 'The laptop lid is closed!'"
lid_open = "notify-send 'The laptop lid is open!'"
```

Defines commands that are executed when the laptop lid is closed or opened.

## Idle inhibition

Umbriel supports application idle inhibitors and idle notifications. An
Expand Down
5 changes: 5 additions & 0 deletions examples/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ honor_restored_maximize = false # let apps restore their saved maximized state
# GTK_THEME = "Adwaita:dark"
# QT_QPA_PLATFORMTHEME = "qt5ct"

# Defines commands that are executed when the laptop lid is closed or opened.
# [events]
# lid_close = "notify-send 'The laptop lid is closed!'"
# lid_open = "notify-send 'The laptop lid is open!'"

[workspaces]
back_and_forth = false
empty_above = false
Expand Down
3 changes: 3 additions & 0 deletions src/config/change.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ namespace umbriel {
.workspaces = true,
.general = true,
.environment = true,
.events = true,
.input = true,
.keybinds = true,
.outputs = true,
Expand All @@ -168,6 +169,7 @@ namespace umbriel {
.workspaces = before.workspaces != after.workspaces,
.general = before.general != after.general,
.environment = before.environment != after.environment,
.events = before.events != after.events,
.input = before.input != after.input,
.keybinds = before.keybinds != after.keybinds,
.outputs = before.outputs != after.outputs,
Expand Down Expand Up @@ -197,6 +199,7 @@ namespace umbriel {
add(workspaces, "workspaces");
add(general, "general");
add(environment, "environment");
add(events, "events");
add(input, "input");
add(keybinds, "keybinds");
add(outputs, "outputs");
Expand Down
2 changes: 2 additions & 0 deletions src/config/change.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace umbriel {
bool workspaces = false;
bool general = false;
bool environment = false;
bool events = false;
bool input = false;
bool keybinds = false;
bool outputs = false;
Expand All @@ -35,6 +36,7 @@ namespace umbriel {
|| workspaces
|| general
|| environment
|| events
|| input
|| keybinds
|| outputs
Expand Down
7 changes: 7 additions & 0 deletions src/config/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,12 @@ namespace umbriel {
});
}

void readEvents(Section& root, Config& loaded) {
root.sub("events", [&](Section& s) {
s.text("lid_close", loaded.events.lidClose).text("lid_open", loaded.events.lidOpen);
});
}

bool validateKeyboardInput(
const Config::Input::Keyboard& keyboard, const toml::source_region& source, std::string_view context
) {
Expand Down Expand Up @@ -1803,6 +1809,7 @@ namespace umbriel {
readLayout(root, loaded);
readGeneral(root, loaded);
readEnvironment(root, loaded);
readEvents(root, loaded);
readWorkspaceSettings(root, loaded);
readInput(root, loaded);
readOutputs(root, loaded);
Expand Down
6 changes: 6 additions & 0 deletions src/config/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,12 @@ namespace umbriel {
bool operator==(const Environment&) const = default;
} environment;

struct Events {
std::string lidClose;
std::string lidOpen;
bool operator==(const Events&) const = default;
} events;

struct Input {
// Advertise and accept the primary-selection clipboard used for
// middle-click paste.
Expand Down
10 changes: 10 additions & 0 deletions src/server/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,8 @@ namespace umbriel {
static void onTabletPadRing(wl_listener* listener, void* data);
static void onTabletPadStrip(wl_listener* listener, void* data);
static void onPadKeyboardFocusChange(wl_listener* listener, void* data);
static void onSwitchDestroy(wl_listener* listener, void* data);
static void onSwitchToggle(wl_listener* listener, void* data);
static void onOutputManagerApply(wl_listener* listener, void* data);
static void onOutputManagerTest(wl_listener* listener, void* data);
static void onOutputLayoutChange(wl_listener* listener, void* data);
Expand Down Expand Up @@ -409,6 +411,13 @@ namespace umbriel {
};
void addTablet(wlr_input_device* device);
void addTabletPad(wlr_input_device* device);
struct SwitchDevice {
Server* server = nullptr;
wlr_input_device* device = nullptr;
wl_listener destroy{};
wl_listener toggle{};
};
void addSwitch(wlr_input_device* device);
void applyTabletConfig(TabletDevice& tablet);
void applyTabletPadConfig(TabletPadDevice& pad);
void pairTabletPads();
Expand Down Expand Up @@ -616,6 +625,7 @@ namespace umbriel {
std::vector<std::unique_ptr<TouchDevice>> m_touchDevices;
std::vector<std::unique_ptr<TabletDevice>> m_tabletDevices;
std::vector<std::unique_ptr<TabletPadDevice>> m_tabletPads;
std::vector<std::unique_ptr<SwitchDevice>> m_switchDevices;
std::vector<std::unique_ptr<VirtualPointerDevice>> m_virtualPointers;
Dirty m_dirty = Dirty::None;
ViewRegistry m_registry;
Expand Down
47 changes: 47 additions & 0 deletions src/server/server_events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,9 @@ namespace umbriel {
case WLR_INPUT_DEVICE_TABLET_PAD:
self->addTabletPad(device);
break;
case WLR_INPUT_DEVICE_SWITCH:
self->addSwitch(device);
break;
default:
break;
}
Expand Down Expand Up @@ -1221,6 +1224,50 @@ namespace umbriel {
server->updateSeatCapabilities();
}

void Server::addSwitch(wlr_input_device* device) {
auto entry = std::make_unique<SwitchDevice>();
entry->server = this;
entry->device = device;
entry->destroy.notify = onSwitchDestroy;
wl_signal_add(&device->events.destroy, &entry->destroy);
entry->toggle.notify = onSwitchToggle;
wl_signal_add(&wlr_switch_from_input_device(device)->events.toggle, &entry->toggle);
m_switchDevices.push_back(std::move(entry));
kLog.info("input: added switch device '{}'", deviceName(device));
}

void Server::onSwitchDestroy(wl_listener* listener, void* /*data*/) {
SwitchDevice* watch;
watch = wl_container_of(listener, watch, destroy);
Server* server = watch->server;
wl_list_remove(&watch->destroy.link);
wl_list_remove(&watch->toggle.link);
std::erase_if(server->m_switchDevices, [watch](const std::unique_ptr<SwitchDevice>& entry) {
return entry.get() == watch;
});
}

void Server::onSwitchToggle(wl_listener* listener, void* data) {
SwitchDevice* watch;
watch = wl_container_of(listener, watch, toggle);
const auto* event = static_cast<wlr_switch_toggle_event*>(data);
if (event->switch_type != WLR_SWITCH_TYPE_LID) {
return;
}
Server* server = watch->server;
if (event->switch_state == WLR_SWITCH_STATE_ON) {
kLog.info("lid closed");
if (!config().events.lidClose.empty()) {
server->spawn(config().events.lidClose.c_str(), "events.lid_close");
}
} else {
kLog.info("lid opened");
if (!config().events.lidOpen.empty()) {
server->spawn(config().events.lidOpen.c_str(), "events.lid_open");
}
}
}

void Server::addTablet(wlr_input_device* device) {
auto tablet = std::make_unique<TabletDevice>();
tablet->server = this;
Expand Down
1 change: 1 addition & 0 deletions src/wlr.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ extern "C" {
#include <wlr/types/wlr_server_decoration.h>
#include <wlr/types/wlr_session_lock_v1.h>
#include <wlr/types/wlr_subcompositor.h>
#include <wlr/types/wlr_switch.h>
#include <wlr/types/wlr_tablet_pad.h>
#include <wlr/types/wlr_tablet_tool.h>
#include <wlr/types/wlr_tablet_v2.h>
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/config_change.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ UMBRIEL_TEST(aFirstLoadReportsEverything) {
CHECK(change.appearance);
CHECK(change.animation);
CHECK(change.colors);
CHECK(change.events);
CHECK(change.input);
CHECK(change.outputs);
}
Expand Down Expand Up @@ -113,6 +114,15 @@ UMBRIEL_TEST(eachSectionIsReportedOnItsOwn) {
CHECK(change.general);
CHECK(!change.input);
}
{
Config after;
after.events.lidClose = "systemctl suspend";
const ConfigChange change = ConfigChange::between(before, after);
CHECK(change.events);
CHECK(!change.general);
CHECK(!change.input);
CHECK_EQ(change.summary(), std::string("events"));
}
{
Config after;
after.overview.zoom += 0.1;
Expand Down
18 changes: 18 additions & 0 deletions tests/unit/config_load.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1344,6 +1344,24 @@ WAYLAND_DISPLAY = "wrong"
CHECK(!containsDiagnostic(store, "unknown key environment._PRIVATE"));
}

UMBRIEL_TEST(eventsLoadCanonicalLidCommands) {
const TempConfig file;
file.write(R"(
[events]
lid_close = "systemctl suspend"
lid_open = "notify-send awake"
)");

ConfigStore& store = umbriel::configStore();
store.setRootPath(file.path(), true);
const umbriel::ConfigReloadResult result = store.reload();

CHECK(result.success);
CHECK_EQ(store.config().events.lidClose, std::string{"systemctl suspend"});
CHECK_EQ(store.config().events.lidOpen, std::string{"notify-send awake"});
CHECK(store.diagnostics().empty());
}

UMBRIEL_TEST(packagedAnimationDefaultsMatchCompiledDefaults) {
std::filesystem::path root = std::filesystem::current_path();
while (!std::filesystem::exists(root / "examples/config.toml")) {
Expand Down