1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
|
diff --git a/src/backgrounds.cpp b/src/backgrounds.cpp
new file mode 100644
index 0000000..0c3c272
--- /dev/null
+++ b/src/backgrounds.cpp
@@ -0,0 +1,293 @@
+#include "backgrounds.hpp"
+#include "ipc.hpp"
+#include <gio/gio.h>
+#include <filesystem>
+#include <set>
+
+namespace
+{
+using json = nlohmann::json;
+json empty_slot() { return {{"image", ""}, {"sizing", "fill"}, {"color", {0,0,0,0}}, {"opacity", 1.0}}; }
+json empty_layout() { return {{"mode", "transparent"}, {"image", empty_slot()}, {"monitors", json::object()}}; }
+void clear(Gtk::Box& box) { for (auto child : box.get_children()) box.remove(*child); }
+void label_style(Gtk::Label& label)
+{ label.set_halign(Gtk::ALIGN_START); label.set_line_wrap(); label.set_max_width_chars(75); label.set_selectable(true); }
+json backdrop_request(json request)
+{
+ GError *error = nullptr;
+ auto child = g_subprocess_new(GSubprocessFlags(G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE |
+ G_SUBPROCESS_FLAGS_STDERR_PIPE), &error, "wf-appearance", "backgrounds-api", nullptr);
+ if (!child)
+ {
+ std::string message = error ? error->message : "Cannot start wf-appearance";
+ g_clear_error(&error); throw std::runtime_error(message + ". Install the matching wf-appearance package.");
+ }
+ std::unique_ptr<GSubprocess, decltype(&g_object_unref)> guard(child, g_object_unref);
+ gchar *out = nullptr, *err = nullptr;
+ bool ok = g_subprocess_communicate_utf8(child, request.dump().c_str(), nullptr, &out, &err, &error);
+ std::string bytes = out ? out : "", message = err ? err : "";
+ g_free(out); g_free(err);
+ if (error) { message = error->message; g_clear_error(&error); }
+ if (!ok || !g_subprocess_get_successful(child)) throw std::runtime_error(message);
+ return json::parse(bytes);
+}
+}
+BackgroundsPage::json BackgroundsPage::request(const std::string& method, const json& data)
+{
+ wf::json_t input; wf::json_t::parse_string(data.dump(), input);
+ return json::parse(wcm_ipc_request(method, input).serialize());
+}
+BackgroundsPage::BackgroundsPage(const std::string& config) : Gtk::Box(Gtk::ORIENTATION_VERTICAL, 10), config_file(config)
+{
+ set_border_width(12); pack_start(tabs, true, true);
+ workspace.set_border_width(10); backdrop.set_border_width(10);
+ tabs.append_page(workspace, "Workspace images"); tabs.append_page(backdrop, "Fixed backdrop");
+ help.set_text("Each workspace has its own image. Transparent areas reveal the other cube faces, including while the cube is at rest. The fixed backdrop stays behind the cube. Selecting a workspace here does not switch your desktop.");
+ label_style(help); label_style(status); label_style(backdrop_help); label_style(backdrop_status);
+ workspace.pack_start(help, false, false);
+ outputs.get_accessible()->set_name("Background desktop output");
+ selection.get_accessible()->set_name("Background workspace"); mode.get_accessible()->set_name("Workspace background layout");
+ workspace.pack_start(outputs, false, false); workspace.pack_start(selection, false, false);
+ mode.append("inherit", "Use workspace default"); mode.append("transparent", "Transparent");
+ mode.append("image", "One image — span the selected output"); mode.append("monitors", "Separate physical monitor images");
+ workspace.pack_start(mode, false, false); workspace.pack_start(editor, false, false);
+ buttons.pack_start(reload, false, false); buttons.pack_start(all, false, false); buttons.pack_end(apply, false, false);
+ workspace.pack_start(buttons, false, false); workspace.pack_start(status, false, false);
+ all.set_tooltip_text("Replace every workspace override on this output with the selected layout.");
+ backdrop_help.set_text("This image remains behind the cube. Use rotation keeps the existing Wallhaven wallpaper service in control. Workspace backgrounds have a separate Apply button.");
+ backdrop.pack_start(backdrop_help, false, false);
+ backdrop_mode.append("individual", "Separate physical monitor images"); backdrop_mode.append("combined", "One image spanning VIRTUAL");
+ for (auto name : {"fill", "fit", "tile"}) backdrop_sizing.append(name, name);
+ backdrop_mode.get_accessible()->set_name("Fixed backdrop layout"); backdrop_sizing.get_accessible()->set_name("Fixed backdrop sizing");
+ backdrop.pack_start(backdrop_mode, false, false); backdrop.pack_start(backdrop_sizing, false, false);
+ backdrop.pack_start(backdrop_editor, false, false);
+ backdrop_buttons.pack_start(backdrop_reload, false, false); backdrop_buttons.pack_end(backdrop_apply, false, false);
+ backdrop.pack_start(backdrop_buttons, false, false); backdrop.pack_start(backdrop_status, false, false);
+ outputs.signal_changed().connect([this] { if (!loading) select_output(); });
+ selection.signal_changed().connect([this] { if (!loading) { selected_workspace = selection.get_active_id(); show_layout(); } });
+ mode.signal_changed().connect([this]
+ {
+ if (loading || !available) return;
+ auto id = mode.get_active_id().raw();
+ if (id == "inherit")
+ {
+ if (selected_workspace == "default") { show_layout(); return; }
+ auto profile = state["outputs"][outputs.get_active_row_number()]["profile"].get<std::string>();
+ if (draft["profiles"].contains(profile)) draft["profiles"][profile]["workspaces"].erase(selected_workspace);
+ changed();
+ } else { auto layout = current_layout(); layout["mode"] = id; store_layout(layout); }
+ show_layout();
+ });
+ reload.signal_clicked().connect([this] { refresh(); });
+ apply.signal_clicked().connect([this] { save(); });
+ all.signal_clicked().connect([this]
+ {
+ if (!available) return;
+ Gtk::MessageDialog confirm("Replace all workspace overrides on this output with this layout?", false,
+ Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL, true);
+ if (confirm.run() != Gtk::RESPONSE_OK) return;
+ auto layout = current_layout(); auto output = state["outputs"][outputs.get_active_row_number()];
+ auto profile = output["profile"].get<std::string>();
+ auto& target = draft["profiles"][profile];
+ if (target.is_null()) target = {{"default", empty_layout()}, {"workspaces", json::object()}};
+ for (int y=0; y<output["grid"][1]; ++y) for (int x=0; x<output["grid"][0]; ++x)
+ target["workspaces"][std::to_string(x)+","+std::to_string(y)] = layout;
+ changed(); show_layout();
+ });
+ backdrop_reload.signal_clicked().connect([this] { fixed_request(false); });
+ backdrop_apply.signal_clicked().connect([this] { fixed_request(true); });
+ backdrop_mode.signal_changed().connect([this]
+ { if (!loading && fixed_draft.is_object()) { fixed_draft["mode"] = backdrop_mode.get_active_id().raw(); show_backdrop(); } });
+ backdrop_sizing.signal_changed().connect([this]
+ { if (!loading && fixed_draft.is_object()) { fixed_draft["sizing"] = backdrop_sizing.get_active_id().raw(); backdrop_status.set_text("Unapplied backdrop changes"); } });
+ refresh(); fixed_request(false);
+ timer = Glib::signal_timeout().connect([this]
+ {
+ if (pending_job)
+ {
+ try
+ {
+ auto next = request("workspace-backgrounds/status");
+ if (next["completed_job"].get<uint64_t>() == pending_job)
+ {
+ pending_job = 0;
+ if (next["job_success"].get<bool>()) refresh();
+ else { changed(); status.set_text(next["error_message"].get<std::string>()); }
+ }
+ } catch (const std::exception& e) { pending_job = 0; changed(); status.set_text(e.what()); }
+ }
+ if (fixed_worker.valid() && fixed_worker.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
+ {
+ try
+ {
+ fixed_state = fixed_worker.get(); fixed_draft = fixed_state["settings"];
+ show_backdrop(); backdrop_status.set_text(fixed_state["status"].get<std::string>());
+ } catch (const std::exception& e) { backdrop_status.set_text(e.what()); }
+ backdrop_reload.set_sensitive(true); backdrop_apply.set_sensitive(fixed_draft.is_object());
+ backdrop_editor.set_sensitive(true); backdrop_mode.set_sensitive(true); backdrop_sizing.set_sensitive(true);
+ }
+ return true;
+ }, 200);
+}
+BackgroundsPage::~BackgroundsPage() { timer.disconnect(); if (fixed_worker.valid()) fixed_worker.wait(); }
+void BackgroundsPage::refresh()
+{
+ try
+ {
+ auto next = request("workspace-backgrounds/get");
+ if (next["version"] != 1) throw std::runtime_error("Unsupported workspace background API");
+ if (std::filesystem::weakly_canonical(next["config_file"].get<std::string>()) != std::filesystem::weakly_canonical(config_file))
+ throw std::runtime_error("Open WCM with the running session's configuration to edit workspace backgrounds");
+ state = next; draft = original = state["settings"]; available = true;
+ loading = true; outputs.remove_all();
+ for (auto& output : state["outputs"]) outputs.append(output["name"].get<std::string>(), output["name"].get<std::string>());
+ if (!outputs.set_active_id(selected_output) && !state["outputs"].empty()) outputs.set_active(0);
+ loading = false; select_output();
+ auto error = state["error_message"].get<std::string>();
+ status.set_text(!error.empty() ? error : state["applied"].get<bool>() ? "Saved and loaded by the workspace renderer" : "Saved; images are awaiting the workspace renderer");
+ } catch (const std::exception& e)
+ {
+ available = false; loading = false;
+ status.set_text(std::string(e.what()) + "\nWorkspace images need the workspace-backgrounds and ipc plugins with the matching Wayfire build.");
+ }
+ changed();
+}
+void BackgroundsPage::select_output()
+{
+ selected_output = outputs.get_active_id();
+ if (selected_output.empty()) { available = false; changed(); return; }
+ loading = true; selection.remove_all(); selection.append("default", "Workspace default (inherited by unset workspaces)");
+ auto output = state["outputs"][outputs.get_active_row_number()];
+ for (int y=0; y<output["grid"][1]; ++y) for (int x=0; x<output["grid"][0]; ++x)
+ selection.append(std::to_string(x)+","+std::to_string(y), "Workspace " + std::to_string(x+1) + ", " + std::to_string(y+1));
+ if (!selection.set_active_id(selected_workspace)) { selection.set_active(0); selected_workspace = "default"; }
+ loading = false; show_layout();
+}
+BackgroundsPage::json BackgroundsPage::current_layout() const
+{
+ if (!available || outputs.get_active_row_number() < 0) return empty_layout();
+ auto profile = state["outputs"][outputs.get_active_row_number()]["profile"].get<std::string>();
+ if (!draft["profiles"].contains(profile)) return empty_layout();
+ const auto& target = draft["profiles"][profile];
+ if (selected_workspace != "default" && target["workspaces"].contains(selected_workspace)) return target["workspaces"][selected_workspace];
+ return target["default"];
+}
+void BackgroundsPage::store_layout(const json& layout)
+{
+ auto profile = state["outputs"][outputs.get_active_row_number()]["profile"].get<std::string>();
+ auto& target = draft["profiles"][profile];
+ if (target.is_null()) target = {{"default", empty_layout()}, {"workspaces", json::object()}};
+ if (selected_workspace == "default") target["default"] = layout; else target["workspaces"][selected_workspace] = layout;
+ changed();
+}
+void BackgroundsPage::changed()
+{
+ outputs.set_sensitive(available && !pending_job); selection.set_sensitive(available && !pending_job);
+ mode.set_sensitive(available && !pending_job); editor.set_sensitive(available && !pending_job && mode.get_active_id() != "inherit");
+ apply.set_sensitive(available && !pending_job && draft != original); all.set_sensitive(available && !pending_job);
+ reload.set_sensitive(!pending_job);
+ if (available && draft != original && !pending_job) status.set_text("Unapplied workspace background changes");
+}
+void BackgroundsPage::show_layout()
+{
+ if (!available) return;
+ loading = true; clear(editor); auto layout = current_layout();
+ auto output = state["outputs"][outputs.get_active_row_number()]; auto profile = output["profile"].get<std::string>();
+ bool inherit = selected_workspace != "default" && (!draft["profiles"].contains(profile) || !draft["profiles"][profile]["workspaces"].contains(selected_workspace));
+ mode.set_active_id(inherit ? "inherit" : layout["mode"].get<std::string>());
+ if (layout["mode"] == "image")
+ image_row(editor, output["name"].get<std::string>() + " — full workspace", layout["image"], [this] (json slot)
+ { auto layout = current_layout(); layout["image"] = slot; store_layout(layout); });
+ if (layout["mode"] == "monitors")
+ for (auto& monitor : output["monitors"])
+ {
+ auto name = monitor["name"].get<std::string>();
+ image_row(editor, name, layout["monitors"].value(name, empty_slot()), [this, name] (json slot)
+ { auto layout = current_layout(); layout["monitors"][name] = slot; store_layout(layout); });
+ }
+ loading = false; editor.show_all(); changed();
+}
+void BackgroundsPage::image_row(Gtk::Box& parent, const std::string& label, const json& value,
+ std::function<void(json)> change, bool rotation)
+{
+ auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 6)); parent.pack_start(*box, false, false);
+ auto heading = Gtk::manage(new Gtk::Label(label)); heading->set_halign(Gtk::ALIGN_START); box->pack_start(*heading, false, false);
+ auto row = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 6)); box->pack_start(*row, false, false);
+ auto entry = Gtk::manage(new Gtk::Entry()); entry->set_text(value["image"].get<std::string>());
+ entry->set_placeholder_text(rotation ? "Wallhaven rotation" : "Transparent (or choose a color below)");
+ entry->get_accessible()->set_name(label + " image");
+ auto browse = Gtk::manage(new Gtk::Button("Choose image…")); auto reset = Gtk::manage(new Gtk::Button(rotation ? "Use rotation" : "Clear image"));
+ row->pack_start(*entry, true, true); row->pack_start(*browse, false, false); row->pack_start(*reset, false, false);
+ auto slot = std::make_shared<json>(value);
+ entry->signal_changed().connect([entry, slot, change] { (*slot)["image"] = entry->get_text().raw(); change(*slot); });
+ reset->signal_clicked().connect([entry] { entry->set_text(""); });
+ browse->signal_clicked().connect([entry]
+ {
+ Gtk::FileChooserDialog dialog("Choose background image", Gtk::FILE_CHOOSER_ACTION_OPEN);
+ dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); dialog.add_button("Choose", Gtk::RESPONSE_OK);
+ auto filter = Gtk::FileFilter::create(); filter->set_name("Images"); filter->add_pixbuf_formats(); dialog.add_filter(filter);
+ if (dialog.run() == Gtk::RESPONSE_OK) entry->set_text(dialog.get_filename());
+ });
+ if (rotation) return;
+ auto options = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 10)); box->pack_start(*options, false, false);
+ auto sizing = Gtk::manage(new Gtk::ComboBoxText());
+ sizing->append("fill", "Fill — crop to cover"); sizing->append("fit", "Fit — show the whole image"); sizing->append("tile", "Tile — repeat original size");
+ sizing->set_active_id(value["sizing"].get<std::string>()); sizing->get_accessible()->set_name(label + " image sizing");
+ auto color = Gtk::manage(new Gtk::ColorButton()); Gdk::RGBA rgba;
+ rgba.set_rgba(value["color"][0], value["color"][1], value["color"][2], value["color"][3]);
+ color->set_use_alpha(true); color->set_rgba(rgba); color->set_title("Image margins and transparent areas"); color->set_tooltip_text("Color underneath the image; alpha 0 leaves transparent areas");
+ color->get_accessible()->set_name(label + " background color");
+ auto opacity = Gtk::manage(new Gtk::SpinButton()); opacity->set_range(0,100); opacity->set_increments(1,10);
+ opacity->set_value(value["opacity"].get<double>() * 100); opacity->get_accessible()->set_name(label + " opacity percent");
+ auto caption = Gtk::manage(new Gtk::Label("Opacity %"));
+ options->pack_start(*sizing, true, true); options->pack_start(*color, false, false); options->pack_start(*caption, false, false); options->pack_start(*opacity, false, false);
+ sizing->signal_changed().connect([sizing,slot,change] { (*slot)["sizing"] = sizing->get_active_id().raw(); change(*slot); });
+ color->signal_color_set().connect([color,slot,change] { auto c=color->get_rgba(); (*slot)["color"]={c.get_red(),c.get_green(),c.get_blue(),c.get_alpha()}; change(*slot); });
+ opacity->signal_value_changed().connect([opacity,slot,change] { (*slot)["opacity"] = opacity->get_value()/100; change(*slot); });
+}
+void BackgroundsPage::save()
+{
+ try
+ {
+ auto response = request("workspace-backgrounds/apply", {{"revision",state["revision"]}, {"topology",state["topology"]}, {"settings",draft}});
+ pending_job = response["job"]; status.set_text("Preparing workspace background images…"); changed();
+ } catch (const std::exception& e) { status.set_text(e.what()); }
+}
+void BackgroundsPage::fixed_request(bool save)
+{
+ if (fixed_worker.valid()) return;
+ json input = {{"method", save ? "apply" : "get"}};
+ if (save) { input["revision"] = fixed_state["revision"]; input["settings"] = fixed_draft; }
+ fixed_worker = std::async(std::launch::async, backdrop_request, input);
+ backdrop_status.set_text(save ? "Validating and saving fixed backdrop…" : "Loading fixed backdrop…");
+ backdrop_apply.set_sensitive(false); backdrop_reload.set_sensitive(false); backdrop_editor.set_sensitive(false);
+ backdrop_mode.set_sensitive(false); backdrop_sizing.set_sensitive(false);
+}
+void BackgroundsPage::show_backdrop()
+{
+ if (!fixed_draft.is_object()) return;
+ loading = true; clear(backdrop_editor);
+ backdrop_mode.set_active_id(fixed_draft["mode"].get<std::string>()); backdrop_sizing.set_active_id(fixed_draft["sizing"].get<std::string>());
+ auto add = [this] (const std::string& name, const std::string& path)
+ {
+ auto slot = empty_slot(); slot["image"] = path;
+ image_row(backdrop_editor, name, slot, [this,name] (json slot)
+ {
+ if (name == "VIRTUAL") fixed_draft["combined_image"] = slot["image"];
+ else fixed_draft["monitor_images"][name] = slot["image"];
+ backdrop_status.set_text("Unapplied backdrop changes");
+ }, true);
+ };
+ if (fixed_draft["mode"] == "combined") add("VIRTUAL", fixed_draft["combined_image"]);
+ std::set<std::string> names, combined;
+ for (auto& [name, value] : fixed_draft["monitor_images"].items()) names.insert(name);
+ for (auto& output : fixed_state.value("outputs", json::array()))
+ {
+ if (output.contains("members") && !output["members"].empty())
+ for (auto& member : output["members"]) { names.insert(member["name"]); combined.insert(member["name"]); }
+ else names.insert(output["name"]);
+ }
+ for (auto& name : names)
+ if (fixed_draft["mode"] != "combined" || !combined.count(name)) add(name, fixed_draft["monitor_images"].value(name, ""));
+ loading = false; backdrop_editor.show_all();
+}
diff --git a/src/backgrounds.hpp b/src/backgrounds.hpp
new file mode 100644
index 0000000..0b5d906
--- /dev/null
+++ b/src/backgrounds.hpp
@@ -0,0 +1,38 @@
+#pragma once
+#include <gtkmm.h>
+#include <nlohmann/json.hpp>
+#include <future>
+
+class BackgroundsPage : public Gtk::Box
+{
+ using json = nlohmann::json;
+ Gtk::Notebook tabs;
+ Gtk::Box workspace{Gtk::ORIENTATION_VERTICAL, 10}, backdrop{Gtk::ORIENTATION_VERTICAL, 10};
+ Gtk::Box editor{Gtk::ORIENTATION_VERTICAL, 8}, backdrop_editor{Gtk::ORIENTATION_VERTICAL, 8};
+ Gtk::Box buttons{Gtk::ORIENTATION_HORIZONTAL, 8}, backdrop_buttons{Gtk::ORIENTATION_HORIZONTAL, 8};
+ Gtk::Label help, status, backdrop_help, backdrop_status;
+ Gtk::ComboBoxText outputs, selection, mode, backdrop_mode, backdrop_sizing;
+ Gtk::Button apply{"Apply workspace backgrounds"}, reload{"Revert workspace changes"}, all{"Copy to all workspaces"};
+ Gtk::Button backdrop_apply{"Apply fixed backdrop"}, backdrop_reload{"Revert backdrop changes"};
+ json state, draft, original, fixed_state, fixed_draft;
+ std::string config_file, selected_output, selected_workspace = "default";
+ bool loading = false, available = false;
+ uint64_t pending_job = 0;
+ std::future<json> fixed_worker;
+ sigc::connection timer;
+ static json request(const std::string& method, const json& data = json::object());
+ void refresh();
+ void select_output();
+ void show_layout();
+ json current_layout() const;
+ void store_layout(const json& layout);
+ void changed();
+ void save();
+ void fixed_request(bool save);
+ void show_backdrop();
+ void image_row(Gtk::Box& parent, const std::string& label, const json& value,
+ std::function<void(json)> change, bool rotation = false);
+ public:
+ explicit BackgroundsPage(const std::string& config);
+ ~BackgroundsPage() override;
+};
diff --git a/src/displays.cpp b/src/displays.cpp
index 7a40bcd..71a7829 100644
--- a/src/displays.cpp
+++ b/src/displays.cpp
@@ -1,4 +1,5 @@
#include "displays.hpp"
+#include "backgrounds.hpp"
#include "ipc.hpp"
#include <gtk-layer-shell.h>
#include <wayfire/config/types.hpp>
@@ -61,6 +62,12 @@ DisplaysPage::DisplaysPage(const std::string& config, std::function<void()> on_s
settings_tabs.append_page(left, "Desktop and window zones");
settings_tabs.append_page(right, "Physical monitor");
settings_tabs.append_page(scope_tab, "Application scope");
+ settings_tabs.append_page(*Gtk::manage(new BackgroundsPage(config)), "Backgrounds");
+ settings_tabs.signal_switch_page().connect([this] (Gtk::Widget*, guint page)
+ {
+ toolbar.set_visible(page != 3); actions.set_visible(page != 3); status.set_visible(page != 3);
+ diagram.set_visible(page != 3 && available);
+ });
scope_tab.set_border_width(12);
scope_help.set_text("Physical scope keeps maximize and fullscreen on one monitor. VIRTUAL spans the combined desktop. Dialogs follow their parent; moving a window updates its current monitor.");
scope_help.set_line_wrap(); scope_help.set_max_width_chars(65); scope_help.set_halign(Gtk::ALIGN_START);
@@ -194,7 +201,8 @@ void DisplaysPage::refresh(bool discard)
throw std::runtime_error("WCM is editing a different configuration from the running compositor. Open its session configuration: " + data["config_file"].as_string());
if (data["pending"].as_bool() && transaction.empty())
{
- body.set_sensitive(false); apply_button.set_sensitive(false); identify_button.set_sensitive(false);
+ left.set_sensitive(false); right.set_sensitive(false); scope_tab.set_sensitive(false);
+ apply_button.set_sensitive(false); identify_button.set_sensitive(false);
status.set_text("A display preview is awaiting confirmation in another window."); return;
}
bool recovering = !available;
@@ -353,9 +361,10 @@ void DisplaysPage::changed()
void DisplaysPage::update_controls()
{
bool preview = !transaction.empty();
- settings_state.set_visible_child(available ? "settings" : "unavailable");
- diagram.set_visible(available);
- body.set_sensitive(available && !preview); refresh_button.set_sensitive(!preview);
+ settings_state.set_visible_child("settings");
+ diagram.set_visible(available && settings_tabs.get_current_page() != 3);
+ left.set_sensitive(available && !preview); right.set_sensitive(available && !preview);
+ scope_tab.set_sensitive(available && !preview); refresh_button.set_sensitive(!preview);
identify_button.set_sensitive(available && !heads.empty() && !preview);
keep_button.set_visible(preview); revert_button.set_visible(preview); apply_button.set_visible(!preview); apply_button.set_sensitive(available && dirty && !preview);
bool desktop = role.get_active_id() == "desktop";
diff --git a/src/displays.hpp b/src/displays.hpp
index d3c2d36..dc5664b 100644
--- a/src/displays.hpp
+++ b/src/displays.hpp
@@ -72,4 +72,5 @@ class DisplaysPage : public Gtk::Box
DisplaysPage(const std::string& config, std::function<void()> saved,
std::function<void()> shell, std::function<void()> zones);
~DisplaysPage() override;
+ void show_backgrounds() { settings_tabs.set_current_page(3); }
};
diff --git a/src/meson.build b/src/meson.build
index 8a0d979..9eebcf2 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -7,9 +7,10 @@ libfmt = dependency('fmt', required: true)
wf_json = dependency('yyjson')
layer_shell = dependency('gtk-layer-shell-0')
-dep_list = [xml, gtkmm, wf_config, wf_protos, evdev, xkbregistry, libintl, libfmt, wf_json, layer_shell]
+dep_list = [xml, gtkmm, wf_config, wf_protos, evdev, xkbregistry, libintl, libfmt, wf_json, layer_shell,
+ dependency('nlohmann_json'), dependency('threads')]
-sources = files('main.cpp', 'metadata.cpp', 'wcm.cpp', 'utils.cpp', 'displays.cpp', 'move-snap.cpp', 'vendor/wf-json.cpp')
+sources = files('main.cpp', 'metadata.cpp', 'wcm.cpp', 'utils.cpp', 'displays.cpp', 'backgrounds.cpp', 'move-snap.cpp', 'vendor/wf-json.cpp')
executable(meson.project_name(), sources,
install : true,
diff --git a/src/wcm.cpp b/src/wcm.cpp
index 31a7f1d..f1ba719 100644
--- a/src/wcm.cpp
+++ b/src/wcm.cpp
@@ -1591,9 +1591,10 @@ void WCM::create_main_layout()
global_layout.pack_start(left_stack, false, false);
global_layout.pack_start(main_stack, true, true);
window->add(global_layout);
- if (start_plugin == "displays")
+ if (start_plugin == "displays" || start_plugin == "backgrounds")
{
- Glib::signal_idle().connect_once([this] { open_displays(); });
+ Glib::signal_idle().connect_once([this]
+ { open_displays(); if (start_plugin == "backgrounds") displays_page->show_backgrounds(); });
} else if (!start_plugin.empty())
{
Plugin *launch_plugin = find_plugin_by_name(plugins, start_plugin);
|