Skip to content

Commit afadd99

Browse files
committed
feat: commit transaction system
1 parent 6bab0e4 commit afadd99

9 files changed

Lines changed: 491 additions & 7 deletions

File tree

include/Server.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "PointerConstraint.h"
1515
#include "Seat.h"
1616
#include "Toplevel.h"
17+
#include "Transaction.h"
1718
#include "Workspace.h"
1819
#include "WorkspaceManager.h"
1920
#include "wlr.h"
@@ -52,6 +53,7 @@ struct Server {
5253

5354
OutputManager *output_manager;
5455
WorkspaceManager *workspace_manager;
56+
TransactionManager *transaction_manager;
5557

5658
struct {
5759
wlr_scene_tree *background;

include/Toplevel.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ struct Toplevel {
8080
wlr_box geometry{};
8181
wlr_box saved_geometry{};
8282

83+
bool in_transaction{false};
84+
wlr_box pending_transaction_geometry{};
85+
8386
std::string tag{};
8487

8588
Toplevel(Server *server, wlr_xdg_toplevel *wlr_xdg_toplevel);

include/Transaction.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#pragma once
2+
3+
#include "wlr.h"
4+
#include <vector>
5+
#include <unordered_set>
6+
7+
struct PendingGeometry {
8+
struct Toplevel *toplevel;
9+
wlr_box geometry;
10+
uint32_t serial;
11+
bool committed{false};
12+
};
13+
14+
struct Transaction {
15+
struct Server *server;
16+
std::vector<PendingGeometry> pending_changes;
17+
std::unordered_set<struct Toplevel *> waiting_for_commit;
18+
wl_event_source *timeout_timer{nullptr};
19+
bool committed{false};
20+
21+
wl_listener surface_commit;
22+
23+
explicit Transaction(struct Server *server);
24+
~Transaction();
25+
26+
// Add a geometry change to this transaction
27+
void add_change(Toplevel *toplevel, const wlr_box &geometry);
28+
void add_change(Toplevel *toplevel, int x, int y, int width, int height);
29+
30+
// Commit the transaction (send configure events and wait for responses)
31+
void commit();
32+
33+
// Apply all changes immediately (called after all surfaces commit or timeout)
34+
void apply();
35+
36+
// Check if a toplevel is part of this transaction
37+
bool contains(Toplevel *toplevel) const;
38+
39+
// Handle surface commit from a toplevel
40+
void handle_commit(Toplevel *toplevel);
41+
42+
// Remove a toplevel from this transaction (e.g., when it unmaps)
43+
void remove_toplevel(Toplevel *toplevel);
44+
45+
private:
46+
void setup_timeout();
47+
void cleanup();
48+
static int on_timeout(void *data);
49+
};
50+
51+
// Transaction manager handles the active transaction
52+
struct TransactionManager {
53+
Server *server;
54+
Transaction *active_transaction{nullptr};
55+
56+
explicit TransactionManager(Server *server);
57+
~TransactionManager();
58+
59+
// Start a new transaction (commits any existing one first)
60+
Transaction *begin();
61+
62+
// Get the current active transaction, or nullptr if none
63+
Transaction *current() const { return active_transaction; }
64+
65+
// Check if a transaction is currently active
66+
bool is_active() const { return active_transaction != nullptr; }
67+
68+
// Commit and apply the current transaction
69+
void commit();
70+
71+
// Remove a toplevel from any active transaction
72+
void remove_toplevel(Toplevel *toplevel);
73+
};

meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ executable(
136136
'src' / 'InputMethodPopup.cpp',
137137
'src' / 'TearingController.cpp',
138138
'src' / 'ActivationToken.cpp',
139+
'src' / 'Transaction.cpp',
139140
protocol_sources,
140141
],
141142
include_directories: include,

src/BSPTree.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include "BSPTree.h"
2+
#include "Server.h"
23
#include "Toplevel.h"
4+
#include "Transaction.h"
35
#include "Workspace.h"
46
#include <algorithm>
57
#include <functional>
@@ -202,8 +204,14 @@ void BSPTree::apply_layout(const wlr_box &bounds) {
202204
if (!root)
203205
return;
204206

207+
// start transaction for atomic updates
208+
workspace->output->server->transaction_manager->begin();
209+
205210
calculate_layout(root.get(), bounds);
206211
apply_geometries(root.get());
212+
213+
// Commit the transaction to apply all changes atomically
214+
workspace->output->server->transaction_manager->commit();
207215
}
208216

209217
BSPNode *BSPTree::find_node(Toplevel *toplevel) {

src/Server.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,9 @@ Server::Server(Config *config) : config(config) {
592592
// workspace manager
593593
workspace_manager = new WorkspaceManager(this);
594594

595+
// transaction manager
596+
transaction_manager = new TransactionManager(this);
597+
595598
// scene
596599
scene = wlr_scene_create();
597600
scene_layout =
@@ -1389,6 +1392,7 @@ Server::~Server() {
13891392

13901393
delete output_manager;
13911394
delete workspace_manager;
1395+
delete transaction_manager;
13921396
delete seat;
13931397
delete cursor;
13941398

src/Toplevel.cpp

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,10 @@ void Toplevel::map_notify(wl_listener *listener, [[maybe_unused]] void *data) {
8282
} else {
8383
wlr_box output_box = output->layout_geometry;
8484

85-
int32_t x = output_box.x + usable_area.x + (usable_area.width - width) / 2;
86-
int32_t y = output_box.y + usable_area.y + (usable_area.height - height) / 2;
85+
int32_t x =
86+
output_box.x + usable_area.x + (usable_area.width - width) / 2;
87+
int32_t y = output_box.y + usable_area.y +
88+
(usable_area.height - height) / 2;
8789

8890
// ensure position falls in bounds
8991
x = std::max(x, output_box.x + usable_area.x);
@@ -114,12 +116,21 @@ void Toplevel::map_notify(wl_listener *listener, [[maybe_unused]] void *data) {
114116
if (!xwayland_surface->surface)
115117
return;
116118

119+
// commit if part of transaction
120+
if (toplevel->in_transaction) {
121+
Transaction *txn =
122+
toplevel->server->transaction_manager->current();
123+
if (txn && txn->contains(toplevel))
124+
txn->handle_commit(toplevel);
125+
}
126+
117127
wlr_surface_state *state = &xwayland_surface->surface->current;
118128
wlr_box *current = &toplevel->geometry;
119129

120130
// update size if state differs
121-
if (current->width != state->width ||
122-
current->height != state->height) {
131+
if (!toplevel->in_transaction &&
132+
(current->width != state->width ||
133+
current->height != state->height)) {
123134
current->width = state->width;
124135
current->height = state->height;
125136
toplevel->set_position_size(*current);
@@ -159,9 +170,11 @@ void Toplevel::map_notify(wl_listener *listener, [[maybe_unused]] void *data) {
159170

160171
// center the surface to the focused output if zero
161172
if (!x)
162-
x = output->layout_geometry.x + area.x + (area.width - width) / 2;
173+
x = output->layout_geometry.x + area.x +
174+
(area.width - width) / 2;
163175
if (!y)
164-
y = output->layout_geometry.y + area.y + (area.height - height) / 2;
176+
y = output->layout_geometry.y + area.y +
177+
(area.height - height) / 2;
165178

166179
// send a configure event
167180
wlr_xwayland_surface_configure(xwayland_surface, x, y, width,
@@ -223,6 +236,12 @@ void Toplevel::unmap_notify(wl_listener *listener,
223236
Toplevel *toplevel = wl_container_of(listener, toplevel, unmap);
224237
Server *server = toplevel->server;
225238

239+
// remove from any active transaction
240+
if (toplevel->in_transaction && server->transaction_manager) {
241+
server->transaction_manager->remove_toplevel(toplevel);
242+
toplevel->in_transaction = false;
243+
}
244+
226245
// deactivate
227246
if (toplevel == server->seat->grabbed_toplevel)
228247
server->cursor->reset_mode();
@@ -297,6 +316,13 @@ Toplevel::Toplevel(Server *server, wlr_xdg_toplevel *xdg_toplevel)
297316
WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE |
298317
WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE);
299318
}
319+
320+
// handle commit if part of transaction
321+
if (toplevel->in_transaction) {
322+
Transaction *txn = toplevel->server->transaction_manager->current();
323+
if (txn && txn->contains(toplevel))
324+
txn->handle_commit(toplevel);
325+
}
300326
};
301327
wl_signal_add(&xdg_toplevel->base->surface->events.commit, &commit);
302328

@@ -313,6 +339,10 @@ Toplevel::Toplevel(Server *server, wlr_xdg_toplevel *xdg_toplevel)
313339
// xdg_toplevel_destroy
314340
destroy.notify = [](wl_listener *listener, [[maybe_unused]] void *data) {
315341
Toplevel *toplevel = wl_container_of(listener, toplevel, destroy);
342+
if (toplevel->in_transaction && toplevel->server->transaction_manager) {
343+
toplevel->server->transaction_manager->remove_toplevel(toplevel);
344+
toplevel->in_transaction = false;
345+
}
316346
delete toplevel;
317347
};
318348
wl_signal_add(&xdg_toplevel->events.destroy, &destroy);
@@ -479,6 +509,10 @@ Toplevel::Toplevel(Server *server, wlr_xwayland_surface *xwayland_surface)
479509
// destroy
480510
destroy.notify = [](wl_listener *listener, [[maybe_unused]] void *data) {
481511
Toplevel *toplevel = wl_container_of(listener, toplevel, destroy);
512+
if (toplevel->in_transaction && toplevel->server->transaction_manager) {
513+
toplevel->server->transaction_manager->remove_toplevel(toplevel);
514+
toplevel->in_transaction = false;
515+
}
482516
delete toplevel;
483517
};
484518
wl_signal_add(&xwayland_surface->events.destroy, &destroy);
@@ -812,6 +846,12 @@ void Toplevel::begin_interactive(const CursorMode mode, const uint32_t edges) {
812846

813847
// set the position and size of a toplevel, send a configure
814848
void Toplevel::set_position_size(double x, double y, int width, int height) {
849+
if (Transaction *txn = server->transaction_manager->current()) {
850+
txn->add_change(this, static_cast<int>(x), static_cast<int>(y), width,
851+
height);
852+
return;
853+
}
854+
815855
// xwayland surfaces can call fullscreen and maximize when unmapped so this
816856
// check is necessary
817857
#ifdef XWAYLAND

0 commit comments

Comments
 (0)