1 #include <glibmm/main.h>
2 #include <gtkmm/window.h>
3 #include <gtkmm/headerbar.h>
4 #include <gtkmm/hvbox.h>
5 #include <gtkmm/application.h>
6 #include <gdk/gdkwayland.h>
7 #include <gtk-layer-shell.h>
8
9 #include <stdio.h>
10 #include <iostream>
11 #include <sstream>
12
13 #include <map>
14
15 #include "panel.hpp"
16 #include "../util/gtk-utils.hpp"
17
18 #include "widgets/battery.hpp"
19 #include "widgets/menu.hpp"
20 #include "widgets/clock.hpp"
21 #include "widgets/launchers.hpp"
22 #include "widgets/network.hpp"
23 #include "widgets/spacing.hpp"
24 #ifdef HAVE_PULSE
25 #include "widgets/volume.hpp"
26 #endif
27 #include "widgets/window-list/window-list.hpp"
28
29 #include "wf-autohide-window.hpp"
30
31 struct WayfirePanelZwfOutputCallbacks
32 {
33 std::function<void()> enter_fullscreen;
34 std::function<void()> leave_fullscreen;
35 };
36
handle_zwf_output_enter_fullscreen(void * dd,zwf_output_v2 * zwf_output_v2)37 static void handle_zwf_output_enter_fullscreen(void* dd,
38 zwf_output_v2 *zwf_output_v2)
39 {
40 auto data = (WayfirePanelZwfOutputCallbacks*)
41 zwf_output_v2_get_user_data(zwf_output_v2);
42
43 if (data)
44 data->enter_fullscreen();
45 }
46
handle_zwf_output_leave_fullscreen(void *,zwf_output_v2 * zwf_output_v2)47 static void handle_zwf_output_leave_fullscreen(void *,
48 zwf_output_v2 *zwf_output_v2)
49 {
50 auto data = (WayfirePanelZwfOutputCallbacks*)
51 zwf_output_v2_get_user_data(zwf_output_v2);
52
53 if (data)
54 data->leave_fullscreen();
55 }
56
57 static struct zwf_output_v2_listener output_impl = {
58 .enter_fullscreen = handle_zwf_output_enter_fullscreen,
59 .leave_fullscreen = handle_zwf_output_leave_fullscreen,
60 };
61
62 class WayfirePanel::impl
63 {
64 std::unique_ptr<WayfireAutohidingWindow> window;
65
66 Gtk::HBox content_box;
67 Gtk::HBox left_box, center_box, right_box;
68
69 using Widget = std::unique_ptr<WayfireWidget>;
70 using WidgetContainer = std::vector<Widget>;
71 WidgetContainer left_widgets, center_widgets, right_widgets;
72
73 WayfireOutput *output;
74
75 int last_autohide_value = -1;
76 WfOption<bool> autohide_opt{"panel/autohide"};
77 std::function<void()> autohide_opt_updated = [=] ()
__anond7d839cc0102() 78 {
79 if (autohide_opt == last_autohide_value)
80 return;
81
82 if (autohide_opt) {
83 this->window->increase_autohide();
84 } else if (last_autohide_value == 1) {
85 this->window->decrease_autohide();
86 }
87
88 last_autohide_value = autohide_opt;
89 window->set_auto_exclusive_zone(!autohide_opt);
90 };
91
92 WfOption<std::string> bg_color{"panel/background_color"};
93 std::function<void()> on_window_color_updated = [=] ()
__anond7d839cc0202() 94 {
95 if ((std::string)bg_color == "gtk_default")
96 return window->unset_background_color();
97
98 Gdk::RGBA rgba;
99 if ((std::string)bg_color == "gtk_headerbar")
100 {
101 Gtk::HeaderBar headerbar;
102 rgba = headerbar.get_style_context()->get_background_color();
103 } else {
104 auto color = wf::option_type::from_string<wf::color_t> (
105 ((wf::option_sptr_t<std::string>)bg_color)->get_value_str());
106 if (!color) {
107 std::cerr << "Invalid panel background color in"
108 " config file" << std::endl;
109 return;
110 }
111
112 rgba.set_red(color.value().r);
113 rgba.set_green(color.value().g);
114 rgba.set_blue(color.value().b);
115 rgba.set_alpha(color.value().a);
116 }
117
118 window->override_background_color(rgba);
119 };
120
121 WfOption<std::string> panel_layer{"panel/layer"};
122 std::function<void()> set_panel_layer = [=] ()
__anond7d839cc0302() 123 {
124 if ((std::string)panel_layer == "overlay")
125 gtk_layer_set_layer(window->gobj(), GTK_LAYER_SHELL_LAYER_OVERLAY);
126 if ((std::string)panel_layer == "top")
127 gtk_layer_set_layer(window->gobj(), GTK_LAYER_SHELL_LAYER_TOP);
128 if ((std::string)panel_layer == "bottom")
129 gtk_layer_set_layer(window->gobj(), GTK_LAYER_SHELL_LAYER_BOTTOM);
130 if ((std::string)panel_layer == "background")
131 gtk_layer_set_layer(window->gobj(), GTK_LAYER_SHELL_LAYER_BACKGROUND);
132 };
133
134 WfOption<int> minimal_panel_height{"panel/minimal_height"};
135 WfOption<std::string> css_path{"panel/css_path"};
136
create_window()137 void create_window()
138 {
139 window = std::make_unique<WayfireAutohidingWindow> (output, "panel");
140 window->set_size_request(1, minimal_panel_height);
141 panel_layer.set_callback(set_panel_layer);
142 set_panel_layer(); // initial setting
143
144 gtk_layer_set_anchor(window->gobj(), GTK_LAYER_SHELL_EDGE_LEFT, true);
145 gtk_layer_set_anchor(window->gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, true);
146
147 bg_color.set_callback(on_window_color_updated);
148 on_window_color_updated(); // set initial color
149
150 autohide_opt.set_callback(autohide_opt_updated);
151 autohide_opt_updated(); // set initial autohide status
152
153 if ((std::string)css_path != "")
154 {
155 auto css = load_css_from_path(css_path);
156 if (css)
157 {
158 auto screen = Gdk::Screen::get_default();
159 auto style_context = Gtk::StyleContext::create();
160 style_context->add_provider_for_screen(
161 screen, css, GTK_STYLE_PROVIDER_PRIORITY_USER);
162 }
163 }
164
165 window->show_all();
166 init_widgets();
167 init_layout();
168
169 window->signal_delete_event().connect(
170 sigc::mem_fun(this, &WayfirePanel::impl::on_delete));
171 }
172
on_delete(GdkEventAny * ev)173 bool on_delete(GdkEventAny *ev)
174 {
175 /* We ignore close events, because the panel's lifetime is bound to
176 * the lifetime of the output */
177 return true;
178 }
179
init_layout()180 void init_layout()
181 {
182 content_box.pack_start(left_box, false, false);
183 std::vector<Gtk::Widget*> center_children = center_box.get_children();
184 if (center_children.size() > 0)
185 content_box.set_center_widget(center_box);
186 content_box.pack_end(right_box, false, false);
187 window->add(content_box);
188 window->show_all();
189 }
190
widget_from_name(std::string name)191 Widget widget_from_name(std::string name)
192 {
193 if (name == "menu")
194 return Widget(new WayfireMenu());
195 if (name == "launchers")
196 return Widget(new WayfireLaunchers());
197 if (name == "clock")
198 return Widget(new WayfireClock());
199 if (name == "network")
200 return Widget(new WayfireNetworkInfo());
201 if (name == "battery")
202 return Widget(new WayfireBatteryInfo());
203 if (name == "volume") {
204 #ifdef HAVE_PULSE
205 return Widget(new WayfireVolume());
206 #else
207 #warning "Pulse not found, volume widget will not be available."
208 std::cerr << "Built without pulse support, volume widget "
209 " is not available." << std::endl;
210 #endif
211 }
212 if (name == "window-list")
213 return Widget(new WayfireWindowList(output));
214
215 std::string spacing = "spacing";
216 if (name.find(spacing) == 0)
217 {
218 auto pixel_str = name.substr(spacing.size());
219 int pixel = std::atoi(pixel_str.c_str());
220
221 if (pixel <= 0)
222 {
223 std::cerr << "Invalid spacing, " << pixel << std::endl;
224 return nullptr;
225 }
226
227 return Widget(new WayfireSpacing(pixel));
228 }
229
230 if (name != "none")
231 std::cerr << "Invalid widget: " << name << std::endl;
232 return nullptr;
233 }
234
tokenize(std::string list)235 static std::vector<std::string> tokenize(std::string list)
236 {
237 std::string token;
238 std::istringstream stream(list);
239 std::vector<std::string> result;
240
241 while(stream >> token)
242 {
243 if (token.size())
244 result.push_back(token);
245 }
246
247 return result;
248 }
249
reload_widgets(std::string list,WidgetContainer & container,Gtk::HBox & box)250 void reload_widgets(std::string list, WidgetContainer& container,
251 Gtk::HBox& box)
252 {
253 container.clear();
254 auto widgets = tokenize(list);
255 for (auto widget_name : widgets)
256 {
257 auto widget = widget_from_name(widget_name);
258 if (!widget)
259 continue;
260
261 widget->widget_name = widget_name;
262 widget->init(&box);
263 container.push_back(std::move(widget));
264 }
265 }
266
267 WfOption<std::string> left_widgets_opt{"panel/widgets_left"};
268 WfOption<std::string> right_widgets_opt{"panel/widgets_right"};
269 WfOption<std::string> center_widgets_opt{"panel/widgets_center"};
init_widgets()270 void init_widgets()
271 {
272 left_widgets_opt.set_callback([=] () {
273 reload_widgets((std::string)left_widgets_opt, left_widgets, left_box);
274 });
275 right_widgets_opt.set_callback([=] () {
276 reload_widgets((std::string)right_widgets_opt, right_widgets, right_box);
277 });
278 center_widgets_opt.set_callback([=] () {
279 reload_widgets((std::string)center_widgets_opt, center_widgets, center_box);
280 });
281
282 reload_widgets((std::string)left_widgets_opt, left_widgets, left_box);
283 reload_widgets((std::string)right_widgets_opt, right_widgets, right_box);
284 reload_widgets((std::string)center_widgets_opt, center_widgets, center_box);
285 }
286
287 WayfirePanelZwfOutputCallbacks callbacks;
288
289 public:
impl(WayfireOutput * output)290 impl(WayfireOutput *output)
291 {
292 this->output = output;
293 create_window();
294
295 if (output->output)
296 {
297 callbacks.enter_fullscreen = [=]() { window->increase_autohide(); };
298 callbacks.leave_fullscreen = [=]() { window->decrease_autohide(); };
299 zwf_output_v2_add_listener(output->output, &output_impl, NULL);
300 zwf_output_v2_set_user_data(output->output, &callbacks);
301 }
302 }
303
~impl()304 ~impl()
305 {
306 if (output->output)
307 zwf_output_v2_set_user_data(output->output, NULL);
308 }
309
get_wl_surface()310 wl_surface *get_wl_surface()
311 {
312 return window->get_wl_surface();
313 }
314
get_window()315 Gtk::Window& get_window()
316 {
317 return *window;
318 }
319
handle_config_reload()320 void handle_config_reload()
321 {
322 for (auto& w : left_widgets)
323 w->handle_config_reload();
324 for (auto& w : right_widgets)
325 w->handle_config_reload();
326 for (auto& w : center_widgets)
327 w->handle_config_reload();
328 }
329 };
330
WayfirePanel(WayfireOutput * output)331 WayfirePanel::WayfirePanel(WayfireOutput *output) : pimpl(new impl(output)) { }
get_wl_surface()332 wl_surface *WayfirePanel::get_wl_surface() { return pimpl->get_wl_surface(); }
get_window()333 Gtk::Window& WayfirePanel::get_window() { return pimpl->get_window(); }
handle_config_reload()334 void WayfirePanel::handle_config_reload() { return pimpl->handle_config_reload(); }
335
336 class WayfirePanelApp::impl
337 {
338 public:
339 std::map<WayfireOutput*, std::unique_ptr<WayfirePanel> > panels;
340 };
341
on_config_reload()342 void WayfirePanelApp::on_config_reload()
343 {
344 for (auto& p : priv->panels)
345 p.second->handle_config_reload();
346 }
347
handle_new_output(WayfireOutput * output)348 void WayfirePanelApp::handle_new_output(WayfireOutput *output)
349 {
350 priv->panels[output] = std::unique_ptr<WayfirePanel> (
351 new WayfirePanel(output));
352 }
353
panel_for_wl_output(wl_output * output)354 WayfirePanel* WayfirePanelApp::panel_for_wl_output(wl_output *output)
355 {
356 for (auto& p : priv->panels)
357 {
358 if (p.first->wo == output)
359 return p.second.get();
360 }
361
362 return nullptr;
363 }
364
handle_output_removed(WayfireOutput * output)365 void WayfirePanelApp::handle_output_removed(WayfireOutput *output)
366 {
367 priv->panels.erase(output);
368 }
369
get()370 WayfirePanelApp& WayfirePanelApp::get()
371 {
372 if (!instance)
373 throw std::logic_error("Calling WayfirePanelApp::get() before starting app!");
374 return dynamic_cast<WayfirePanelApp&>(*instance.get());
375 }
376
create(int argc,char ** argv)377 void WayfirePanelApp::create(int argc, char **argv)
378 {
379 if (instance)
380 throw std::logic_error("Running WayfirePanelApp twice!");
381
382 instance = std::unique_ptr<WayfireShellApp>(new WayfirePanelApp{argc, argv});
383 instance->run();
384 }
385
386 WayfirePanelApp::~WayfirePanelApp() = default;
WayfirePanelApp(int argc,char ** argv)387 WayfirePanelApp::WayfirePanelApp(int argc, char **argv)
388 : WayfireShellApp(argc, argv), priv(new impl()) { }
389
main(int argc,char ** argv)390 int main(int argc, char **argv)
391 {
392 WayfirePanelApp::create(argc, argv);
393 return 0;
394 }
395