1 /*
2 * Classes for nwg-launchers
3 * Copyright (c) 2021 Érico Nogueira
4 * e-mail: ericonr@disroot.org
5 * Copyright (c) 2021 Piotr Miller
6 * e-mail: nwg.piotr@gmail.com
7 * Website: http://nwg.pl
8 * Project: https://github.com/nwg-piotr/nwg-launchers
9 * License: GPL3
10 * */
11
12 #pragma once
13
14 #include <array>
15 #include <string>
16 #include <vector>
17 #include <variant>
18
19 #include <gtkmm.h>
20 #include <glibmm/ustring.h>
21
22 #ifdef HAVE_GTK_LAYER_SHELL
23 #include <gtk-layer-shell.h>
24 #endif
25
26 #include "filesystem-compat.h"
27
28 template <typename ... Os>
29 struct Overloaded: Os... { using Os::operator()...; };
30 template <typename ... Os> Overloaded(Os ...) -> Overloaded<Os...>;
31
32 struct RGBA {
33 double red;
34 double green;
35 double blue;
36 double alpha;
37 };
38
39 /*
40 * Argument parser
41 * Credits for this cool class go to iain at https://stackoverflow.com/a/868894
42 * */
43 class InputParser{
44 public:
45 InputParser (int, char **);
46 /// @author iain
47 std::string_view getCmdOption(std::string_view) const;
48 /// @author iain
49 bool cmdOptionExists(std::string_view) const;
50 RGBA get_background_color(double default_opacity) const;
51 private:
52 std::vector <std::string_view> tokens;
53 };
54
55 #ifdef HAVE_GTK_LAYER_SHELL
56 struct LayerShellArgs {
57 GtkLayerShellLayer layer = GTK_LAYER_SHELL_LAYER_OVERLAY;
58 int exclusive_zone = -1;
59 bool exclusive_zone_is_auto = true;
60
61 LayerShellArgs(const InputParser& parser);
62 };
63 #endif
64
65 enum class HAlign: unsigned int { NotSpecified = 0, Left, Right };
66 enum class VAlign: unsigned int { NotSpecified = 0, Top, Bottom };
67
68 /*
69 * Stores configuration data
70 */
71 struct Config {
72 const InputParser& parser;
73 std::string wm;
74 std::string_view title;
75 std::string_view role;
76 HAlign halign{ HAlign::NotSpecified };
77 VAlign valign{ VAlign::NotSpecified };
78 fs::path css_filename{ "style.css" }; // filename relative to config dir
79
80 #ifdef HAVE_GTK_LAYER_SHELL
81 LayerShellArgs layer_shell_args;
82 #endif
83
84 Config(const InputParser&, std::string_view, std::string_view, const Glib::RefPtr<Gdk::Screen>&);
85 };
86
87 class CommonWindow : public Gtk::Window {
88 public:
89 CommonWindow(Config&);
90 virtual ~CommonWindow() = default;
91
92 void check_screen();
93 void set_background_color(RGBA color);
94
95 virtual int get_height(); // we need to override get_height for dmenu to work
96
97 std::string_view title_view();
98 protected:
99 bool on_draw(const ::Cairo::RefPtr< ::Cairo::Context>& cr) override;
100 void on_screen_changed(const Glib::RefPtr<Gdk::Screen>& previous_screen) override;
101 private:
102 std::string_view title;
103 RGBA background_color;
104 bool _SUPPORTS_ALPHA;
105 };
106
107 class AppBox : public Gtk::Button {
108 public:
109 AppBox();
110 AppBox(Glib::ustring, Glib::ustring, Glib::ustring);
111 AppBox(AppBox&&) = default;
112 AppBox(const AppBox&) = delete;
113
114 Glib::ustring name;
115 Glib::ustring exec;
116 Glib::ustring comment;
117
118 virtual ~AppBox() = default;
119 };
120
121 /*
122 * Stores x, y, width, height
123 * */
124 struct Geometry {
125 int x;
126 int y;
127 int width;
128 int height;
129 };
130
131 struct DesktopEntry {
132 std::string name;
133 std::string exec;
134 std::string icon;
135 std::string comment;
136 std::string mime_type;
137 bool terminal;
138 };
139
140 struct Instance {
141 Gtk::Application& app;
142 fs::path pid_file;
143 int pid_lock_fd;
144
145 Instance(Gtk::Application& app, std::string_view name);
146 virtual ~Instance();
147 // note: the provided implementation of on_{sigterm,sigint} handlers
148 // calls Gtk::Application::quit, which does NOT call any destructors
149 virtual void on_sigterm();
150 virtual void on_sigusr1();
151 virtual void on_sighup();
152 virtual void on_sigint();
153 };
154
155 struct IconProvider {
156 Glib::RefPtr<Gtk::IconTheme> icon_theme;
157 Glib::RefPtr<Gdk::Pixbuf> fallback;
158 int icon_size;
159
160 IconProvider(const Glib::RefPtr<Gtk::IconTheme>& theme, int icon_size);
161 // Returns Gtk::Image out of the icon name of file path
162 // the returned image is scaled to icon_size x icon_size
163 Gtk::Image load_icon(const std::string& icon) const;
164 };
165
166 enum class SwayError {
167 ConnectFailed,
168 EnvNotSet,
169 OpenFailed,
170 RecvHeaderFailed,
171 RecvBodyFailed,
172 SendHeaderFailed,
173 SendBodyFailed
174 };
175
176 struct SwaySock {
177 SwaySock();
178 SwaySock(const SwaySock&) = delete;
179 ~SwaySock();
180 // pass the command to sway via socket
181 template <typename ... Ts>
runSwaySock182 void run(Ts ... ts) {
183 auto body_size = (ts.size() + ...);
184 send_header_(body_size, Commands::Run);
185 // should we send it as one message? 1 write() is better than N
186 (send_body_(ts), ...);
187 // should we recv the response?
188 // suppress warning
189 (void)recv_response_();
190 }
191 // swaymsg -t get_outputs
192 std::string get_outputs();
193 std::string get_workspaces();
194
195 // see sway-ipc (7)
196 enum class Commands: std::uint32_t {
197 Run = 0,
198 GetWorkspaces = 1,
199 GetOutputs = 3
200 };
201 static constexpr std::array MAGIC { 'i', '3', '-', 'i', 'p', 'c' };
202 static constexpr auto MAGIC_SIZE = MAGIC.size();
203 // magic + body length (u32) + type (u32)
204 static constexpr auto HEADER_SIZE = MAGIC_SIZE + 2 * sizeof(std::uint32_t);
205
206 int sock_;
207 std::array<char, HEADER_SIZE> header;
208
209 void send_header_(std::uint32_t, Commands);
210 void send_body_(std::string_view);
211 std::string recv_response_();
212 };
213
214 /*
215 * This namespace defines types that can be passed to PlatformWindow::show
216 * The rationale behind this design is as follows:
217 * 1) Logic implementing all the positioning is collected in one place,
218 * not scattered across classes / spagetti functions
219 * 2) It is resolved at compile time, allowing compiler to optimize it better
220 * 3) It can be tweaked and expanded with ease and safety (everything is checked at compile-time)
221 */
222 namespace hint {
223 constexpr struct Fullscreen_ {} Fullscreen;
224 constexpr struct Center_ {} Center;
225 struct Horizontal{};
226 struct Vertical{};
227 template <typename S>
228 struct Side { bool side; int margin; constexpr static S side_type{}; };
229 struct Sides { Side<Horizontal> h; Side<Vertical> v; };
230 }
231
232 /*
233 * Each shell defined by which means window is positioned. They do not share common base class,
234 * but instead wrapped in a variant to avoid unecessary dynamic allocations
235 * Shell::show method receives a window reference and a templated parameter,
236 * it's job is to position window according to the parameter type
237 * GenericShell only uses common Gtk functions and is best used on X11 or as a fallback
238 * SwayShell uses IPC connection to Sway/i3
239 * LayerShell uses wlr-layer-shell (or rather gtk-layer-shell library built on top of it)
240 */
241 struct GenericShell {
242 GenericShell(Config& config);
243 Geometry geometry(CommonWindow& window);
244 template <typename S> void show(CommonWindow&, S);
245 // some window managers (openbox, notably) do not open window in fullscreen
246 // when requested
247 bool respects_fullscreen = true;
248 };
249
250 struct SwayShell: GenericShell {
251 SwayShell(CommonWindow& window, Config& config);
252 // use GenericShell::show unless called with Fullscreen
253 using GenericShell::show;
254 void show(CommonWindow& window, hint::Fullscreen_);
255
256 SwaySock sock_;
257 };
258
259 #ifdef HAVE_GTK_LAYER_SHELL
260 struct LayerShell {
261 LayerShell(CommonWindow& window, LayerShellArgs args);
262 template <typename S> void show(CommonWindow& window, S);
263
264 LayerShellArgs args;
265 };
266 #endif
267
268 struct PlatformWindow: public CommonWindow {
269 public:
270 PlatformWindow(Config& config);
271 void fullscreen();
272 template <typename S> void show(S);
273 private:
274 std::variant<
275 #ifdef HAVE_GTK_LAYER_SHELL
276 LayerShell,
277 #endif
278 SwayShell, GenericShell> shell;
279 };
280
281 template <typename Hint>
show(CommonWindow & window,Hint hint)282 void GenericShell::show(CommonWindow& window, Hint hint) {
283 window.show();
284 window.set_type_hint(Gdk::WINDOW_TYPE_HINT_SPLASHSCREEN);
285 window.set_decorated(false);
286 auto display = geometry(window);
287 auto window_coord_at_side = [](auto d_size, auto w_size, auto side, auto margin) {
288 std::array map { margin, d_size - w_size - margin };
289 return map[side];
290 };
291 Overloaded place_window {
292 [&](hint::Center_) {
293 auto x = display.x + (display.width - window.get_width()) / 2;
294 auto y = display.y + (display.height - window.get_height()) / 2;
295 window.move(x, y);
296 },
297 [&](hint::Fullscreen_) {
298 if (this->respects_fullscreen) {
299 window.fullscreen();
300 } else {
301 window.resize(display.width, display.height);
302 window.move(display.x, display.y);
303 }
304 },
305 [&](hint::Side<hint::Horizontal> hint) {
306 auto w_x = window_coord_at_side(display.width, window.get_width(), hint.side, hint.margin);
307 window.move(display.x + w_x, display.y + (display.height - window.get_height()) / 2);
308 },
309 [&](hint::Side<hint::Vertical> hint) {
310 auto w_y = window_coord_at_side(display.height, window.get_height(), hint.side, hint.margin);
311 window.move(display.x + (display.width - window.get_width()) / 2, display.y + w_y);
312 },
313 [&,display](hint::Sides hint) {
314 auto w_x = window_coord_at_side(display.width, window.get_width(), hint.h.side, hint.h.margin);
315 auto w_y = window_coord_at_side(display.height, window.get_height(), hint.v.side, hint.v.margin);
316 window.move(display.x + w_x, display.y + w_y);
317 }
318 };
319 place_window(hint);
320 window.present(); // grab focus
321 }
322
323 #ifdef HAVE_GTK_LAYER_SHELL
324 template <typename Hint>
show(CommonWindow & window,Hint hint)325 void LayerShell::show(CommonWindow& window, Hint hint) {
326 std::array<bool, 4> edges{ 0, 0, 0, 0 };
327 std::array<int, 4> margins{ 0, 0, 0, 0 };
328 constexpr Overloaded index { [](hint::Horizontal){ return 0; }, [](hint::Vertical) { return 2; } };
329 auto account_side = [&](auto side) {
330 constexpr auto i = index(side.side_type);
331 edges[i + side.side] = true;
332 margins[i + side.side] = side.margin;
333 };
334 Overloaded set_edges_margins {
335 [&](hint::Center_) { /* nothing to do */ },
336 [&](hint::Fullscreen_) { edges = { 1, 1, 1, 1 }; },
337 [&](hint::Sides hint) { account_side(hint.h); account_side(hint.v); },
338 account_side
339 };
340 set_edges_margins(hint);
341 window.show();
342 auto gtk_win = window.gobj();
343 std::array edges_ {
344 GTK_LAYER_SHELL_EDGE_LEFT,
345 GTK_LAYER_SHELL_EDGE_RIGHT,
346 GTK_LAYER_SHELL_EDGE_TOP,
347 GTK_LAYER_SHELL_EDGE_BOTTOM
348 };
349 for (size_t i = 0; i < 4; i++) {
350 gtk_layer_set_anchor(gtk_win, edges_[i], edges[i]);
351 gtk_layer_set_margin(gtk_win, edges_[i], margins[i]);
352 }
353 gtk_layer_set_layer(gtk_win, args.layer);
354 gtk_layer_set_keyboard_interactivity(gtk_win, true);
355 gtk_layer_set_namespace(gtk_win, window.title_view().data());
356 if (args.exclusive_zone_is_auto) {
357 gtk_layer_auto_exclusive_zone_enable (gtk_win);
358 } else {
359 gtk_layer_set_exclusive_zone(gtk_win, args.exclusive_zone);
360 }
361 }
362 #endif
363
364 template <typename Hint>
show(Hint h)365 void PlatformWindow::show(Hint h) {
366 std::visit([&](auto& shell){ shell.show(*this, h); }, shell);
367 }
368
369
370