diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b6cc470b..c5359871 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,7 +8,9 @@ jobs: container: alpine:latest steps: - run: echo 'http://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories - - run: apk --no-cache add git g++ binutils pkgconf meson ninja musl-dev gtk+3.0-dev gtkmm3-dev wayland-protocols wayfire-dev gtk-layer-shell-dev pulseaudio-dev libdbusmenu-gtk3-dev alsa-lib-dev + - run: apk --no-cache add git g++ binutils pkgconf meson ninja musl-dev gtk4.0-dev gtkmm4-dev vala gobject-introspection gobject-introspection-dev wayland-protocols wayfire-dev pulseaudio-dev libdbusmenu-glib-dev alsa-lib-dev + - run: echo 'http://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories + - run: apk --no-cache add gtk4-layer-shell-dev - name: wf-shell uses: actions/checkout@v2 with: diff --git a/.gitmodules b/.gitmodules index 03ef7c62..0e49778e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,9 @@ -[submodule "subprojects/gtk-layer-shell"] - path = subprojects/gtk-layer-shell - url = https://github.com/wmww/gtk-layer-shell [submodule "subprojects/gvc"] path = subprojects/gvc url = https://github.com/GNOME/libgnome-volume-control [submodule "subprojects/wayland-logout"] path = subprojects/wayland-logout url = https://github.com/soreau/wayland-logout +[submodule "subprojects/gtk4-layer-shell"] + path = subprojects/gtk4-layer-shell + url = https://github.com/wmww/gtk4-layer-shell.git diff --git a/README.md b/README.md index fb500c59..e8b5d5fe 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Currently it has only a GTK-based panel and background client. # Dependencies -wf-shell needs the core wayland libraries and protocols (`wayland-devel` and `wayland-protocols-devel` for Fedora), gtkmm-3.0 and [wf-config](https://github.com/WayfireWM/wf-config) +wf-shell needs the core wayland libraries and protocols (`wayland-devel` and `wayland-protocols-devel` for Fedora), gtkmm-4.0 and [wf-config](https://github.com/WayfireWM/wf-config) # Build diff --git a/data/css/default.css b/data/css/default.css index fb09b080..c07530e8 100644 --- a/data/css/default.css +++ b/data/css/default.css @@ -59,4 +59,100 @@ .wf-panel .command-output.icon-bottom label { padding-bottom: 5px; +} + +.excellent { + color: #00ff00; +} + +.good { + color: #88ff00; +} + +.ok { + color: #ffff00; +} + +.weak { + color: #ff8800; +} + +.none { + color: #ff0000; +} + +.wf-dock, +.wf-dock .out-box { + background: transparent; +} + +.wf-dock .box { + padding-left: 1em; + padding-right: 1em; + border-radius: 1em; +} + +.wf-dock image { + transition: 200ms; + -gtk-icon-transform: scale(1.0); + padding-left: 1rem; + padding-right: 1rem; +} + +.wf-dock image:hover { + transition: 200ms; + -gtk-icon-transform: scale(1.3); + padding-left: 2rem; + padding-right: 2rem; +} + +.wf-dock .minimized image { + -gtk-icon-filter: grayscale(1); +} + +.wf-dock image { + animation-name: embiggen; + animation-duration: 1000ms; + animation-timing-function: linear; + animation-iteration-count: 1; +} + +.wf-dock .closing image { + animation-name: kromulent; + animation-duration: 1000ms; + animation-timing-function: linear; + animation-iteration-count: 1; + animation-fill-mode: forwards; +} + +@keyframes embiggen { + to { + -gtk-icon-size: 64px; + -gtk-icon-transform: translateY(0px); + padding-left: 1rem; + padding-right: 1rem; + } + + from { + -gtk-icon-size: 0px; + -gtk-icon-transform: translateY(64px); + padding-left: 0rem; + padding-right: 0rem; + } +} + +@keyframes kromulent { + from { + -gtk-icon-size: 64px; + -gtk-icon-transform: translateY(0px); + padding-left: 1rem; + padding-right: 1rem; + } + + to { + -gtk-icon-size: 0px; + -gtk-icon-transform: translateY(64px); + padding-left: 0rem; + padding-right: 0rem; + } } \ No newline at end of file diff --git a/meson.build b/meson.build index 147c4e35..d026d85d 100644 --- a/meson.build +++ b/meson.build @@ -1,51 +1,55 @@ project( 'wf-shell', - 'c', + 'c', 'cpp', version: '0.10.0', license: 'MIT', meson_version: '>=0.51.0', default_options: [ 'cpp_std=c++17', - 'c_std=c11', + 'c_std=c11', 'warning_level=2', 'werror=false', ], ) -wayfire = dependency('wayfire') +wayfire = dependency('wayfire') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols') -gtkmm = dependency('gtkmm-3.0', version: '>=3.24') -wfconfig = dependency('wf-config', version: '>=0.7.0') #TODO fallback submodule -gtklayershell = dependency('gtk-layer-shell-0', version: '>= 0.6', fallback: ['gtk-layer-shell', 'gtk_layer_shell']) -libpulse = dependency('libpulse', required : get_option('pulse')) -dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4') -libgvc = subproject('gvc', default_options: ['static=true'], required : get_option('pulse')) +gtkmm = dependency('gtkmm-4.0', version: '>=4.12') +wfconfig = dependency('wf-config', version: '>=0.7.0') #TODO fallback submodule +epoxy = dependency('epoxy') +gtklayershell = dependency('gtk4-layer-shell') +libpulse = dependency('libpulse', required: get_option('pulse')) +dbusmenu_gtk = dependency('dbusmenu-glib-0.4') +libgvc = subproject('gvc', default_options: ['static=true'], required: get_option('pulse')) if get_option('wayland-logout') == true - wayland_logout = subproject('wayland-logout') + wayland_logout = subproject('wayland-logout') endif if libpulse.found() - libgvc = libgvc.get_variable('libgvc_dep') - add_project_arguments('-DHAVE_PULSE=1', language : 'cpp') + libgvc = libgvc.get_variable('libgvc_dep') + add_project_arguments('-DHAVE_PULSE=1', language: 'cpp') endif needs_libinotify = ['freebsd', 'dragonfly'].contains(host_machine.system()) -libinotify = dependency('libinotify', required: needs_libinotify) +libinotify = dependency('libinotify', required: needs_libinotify) -add_project_arguments(['-Wno-pedantic', '-Wno-unused-parameter', '-Wno-parentheses'], language: 'cpp') +add_project_arguments( + ['-Wno-pedantic', '-Wno-unused-parameter', '-Wno-parentheses'], + language: 'cpp', +) resource_dir = join_paths(get_option('prefix'), 'share', 'wayfire') metadata_dir = join_paths(resource_dir, 'metadata', 'wf-shell') sysconf_dir = join_paths(get_option('prefix'), get_option('sysconfdir')) icon_dir = join_paths(get_option('prefix'), 'share', 'wayfire', 'icons') -add_project_arguments('-DICONDIR="' + icon_dir + '"', language : 'cpp') -add_project_arguments('-DRESOURCEDIR="' + resource_dir + '"', language : 'cpp') -add_project_arguments('-DMETADATA_DIR="' + metadata_dir + '"', language : 'cpp') -add_project_arguments('-DSYSCONF_DIR="' + sysconf_dir + '"', language : 'cpp') +add_project_arguments('-DICONDIR="' + icon_dir + '"', language: 'cpp') +add_project_arguments('-DRESOURCEDIR="' + resource_dir + '"', language: 'cpp') +add_project_arguments('-DMETADATA_DIR="' + metadata_dir + '"', language: 'cpp') +add_project_arguments('-DSYSCONF_DIR="' + sysconf_dir + '"', language: 'cpp') subdir('metadata') subdir('proto') diff --git a/meson_options.txt b/meson_options.txt index 22ccddad..aca93f47 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,2 +1,12 @@ -option('pulse', type: 'feature', value: 'auto', description: 'Build pulseaudio volume widget') -option('wayland-logout', type: 'boolean', value: 'true', description: 'Install wayland-logout') +option( + 'pulse', + type: 'feature', + value: 'auto', + description: 'Build pulseaudio volume widget', +) +option( + 'wayland-logout', + type: 'boolean', + value: true, + description: 'Install wayland-logout', +) \ No newline at end of file diff --git a/metadata/dock.xml b/metadata/dock.xml index 1c74e047..3451c111 100644 --- a/metadata/dock.xml +++ b/metadata/dock.xml @@ -40,5 +40,10 @@ <_long>The distance from the cursor to the edge of screen to show the panel when it's hidden. 20 + diff --git a/metadata/panel.xml b/metadata/panel.xml index 2fbb3598..bfe391f0 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -351,6 +351,11 @@ Set to -1 to only run it by clicking the button. <_short>Middle Click Closes Windows false + diff --git a/src/background/background.cpp b/src/background/background.cpp index 81ebec83..759a54d7 100644 --- a/src/background/background.cpp +++ b/src/background/background.cpp @@ -1,11 +1,9 @@ #include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -14,154 +12,297 @@ #include #include -#include +#include #include #include "background.hpp" -void BackgroundDrawingArea::show_image(Glib::RefPtr image, - double offset_x, double offset_y, double image_scale) + +static const char *vertex_shader = + R"( +attribute vec2 in_position; + +//uniform mat4 matrix; +attribute highp vec2 uvpos; +varying vec2 uv; + +void main() { + uv = uvpos; + gl_Position = vec4(in_position, 0.0, 1.0); +} +)"; + +static const char *fragment_shader = + R"( +precision highp float; +uniform sampler2D bg_texture_from; +uniform sampler2D bg_texture_to; +uniform float progress; +uniform vec4 from_adj; +uniform vec4 to_adj; + +varying vec2 uv; + +void main() { + vec2 from_uv = vec2((uv.x * from_adj.z) - from_adj.x, (uv.y * from_adj.w) - from_adj.y); + vec2 to_uv = vec2((uv.x * to_adj.z) - to_adj.x, (uv.y * to_adj.w) - to_adj.y); + vec4 from = texture2D(bg_texture_from, from_uv); + vec4 to = texture2D(bg_texture_to, to_uv); + vec3 color = mix(from.rgb, to.rgb, progress); + gl_FragColor = vec4(color, 1.0); +} +)"; + +/* Create and compile a shader */ +static GLuint create_shader(int type, const char *src) { - if (!image) + GLuint shader; + int status; + + shader = glCreateShader(type); + glShaderSource(shader, 1, &src, NULL); + glCompileShader(shader); + + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (status == GL_FALSE) { - to_image.source.clear(); - from_image.source.clear(); - return; - } + int log_len; + char *buffer; - from_image = to_image; - to_image.source = Gdk::Cairo::create_surface_from_pixbuf(image, - this->get_scale_factor()); + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len); - to_image.x = offset_x / this->get_scale_factor(); - to_image.y = offset_y / this->get_scale_factor(); - to_image.scale = image_scale; + buffer = (char*)g_malloc(log_len + 1); + glGetShaderInfoLog(shader, log_len, NULL, buffer); - fade = { - fade_duration, - wf::animation::smoothing::linear - }; + printf("Compile failure in %s shader:\n%s", + type == GL_VERTEX_SHADER ? "vertex" : "fragment", + buffer); - fade.animate(from_image.source ? 0.0 : 1.0, 1.0); + g_free(buffer); - Glib::signal_idle().connect_once([=] () - { - this->queue_draw(); - }); + glDeleteShader(shader); + + return 0; + } + + return shader; } -bool BackgroundDrawingArea::on_draw(const Cairo::RefPtr& cr) +/* Initialize the shaders and link them into a program */ +static GLuint init_shaders() { - if (!to_image.source) + GLuint vertex, fragment; + GLuint program = 0; + int status; + + vertex = create_shader(GL_VERTEX_SHADER, vertex_shader); + + if (vertex == 0) { - return false; + return 0; } - if (fade.running()) - { - queue_draw(); - } else + fragment = create_shader(GL_FRAGMENT_SHADER, fragment_shader); + + if (fragment == 0) { - from_image.source.clear(); + glDeleteShader(vertex); + return 0; } - cr->save(); - cr->scale(to_image.scale, to_image.scale); - cr->set_source(to_image.source, to_image.x, to_image.y); - cr->paint_with_alpha(fade); - cr->restore(); - if (!from_image.source) + program = glCreateProgram(); + glAttachShader(program, vertex); + glAttachShader(program, fragment); + + glLinkProgram(program); + + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (status == GL_FALSE) { - return false; + int log_len; + char *buffer; + + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len); + + buffer = (char*)g_malloc(log_len + 1); + glGetProgramInfoLog(program, log_len, NULL, buffer); + + g_warning("Linking failure:\n%s", buffer); + + g_free(buffer); + + glDeleteProgram(program); + program = 0; + + goto out; } - cr->save(); - cr->scale(from_image.scale, from_image.scale); - cr->set_source(from_image.source, from_image.x, from_image.y); - cr->paint_with_alpha(1.0 - fade); - cr->restore(); - return false; -} + glDetachShader(program, vertex); + glDetachShader(program, fragment); -BackgroundDrawingArea::BackgroundDrawingArea() -{ - fade.animate(0, 0); +out: + glDeleteShader(vertex); + glDeleteShader(fragment); + + return program; } -Glib::RefPtr WayfireBackground::create_from_file_safe(std::string path) +void BackgroundImage::generate_adjustments(int width, int height) { - Glib::RefPtr pbuf; - int width = window.get_allocated_width() * scale; - int height = window.get_allocated_height() * scale; + // Sanity checks + if ((width == 0) || + (height == 0) || + (source == nullptr) || + (source->get_width() == 0) || + (source->get_height() == 0)) + { + return; + } + + double screen_width = (double)width; + double screen_height = (double)height; + double source_width = (double)source->get_width(); + double source_height = (double)source->get_height(); + adjustments = Glib::RefPtr(new BackgroundImageAdjustments()); std::string fill_and_crop_string = "fill_and_crop"; std::string stretch_string = "stretch"; - - if (!stretch_string.compare(background_fill_mode)) + if (!stretch_string.compare(fill_type)) + { + adjustments->x = 0.0; + adjustments->y = 0.0; + adjustments->scale_x = 1.0; + adjustments->scale_y = 1.0; + } else if (!fill_and_crop_string.compare(fill_type)) { - try { - pbuf = - Gdk::Pixbuf::create_from_file(path, width, height, - false); - } catch (...) + auto width_difference = screen_width - source_width; + auto height_difference = screen_height - source_height; + if (width_difference > height_difference) { - return Glib::RefPtr(); + adjustments->scale_x = 1.0; + adjustments->scale_y = (screen_height / screen_width) * (source_width / source_height); + adjustments->x = 0.0; + adjustments->y = (screen_height - source_height * (screen_width / source_width)) * + adjustments->scale_y * 0.5 * (1.0 / screen_height); + } else + { + adjustments->scale_x = (screen_width / screen_height) * (source_height / source_width); + adjustments->scale_y = 1.0; + adjustments->x = (screen_width - source_width * (screen_height / source_height)) * + adjustments->scale_x * 0.5 * (1.0 / screen_width); + adjustments->y = 0.0; + } + } else /* "preserve_aspect" */ + { + auto width_difference = screen_width / source_width; + auto height_difference = screen_height / source_height; + if (width_difference > height_difference) + { + adjustments->scale_x = (screen_width / screen_height) * (source_height / source_width); + adjustments->scale_y = 1.0; + adjustments->x = (screen_width - source_width * (screen_height / source_height)) * + adjustments->scale_x * 0.5 * (1.0 / screen_width); + adjustments->y = 0.0; + } else + { + adjustments->scale_x = 1.0; + adjustments->scale_y = (screen_height / screen_width) * (source_width / source_height); + adjustments->x = 0.0; + adjustments->y = (screen_height - source_height * (screen_width / source_width)) * + adjustments->scale_y * 0.5 * (1.0 / screen_height); } + } +} - offset_x = offset_y = 0.0; - image_scale = 1.0; - return pbuf; +void BackgroundGLArea::show_image(Glib::RefPtr next_image) +{ + if (!next_image || !next_image->source || + (background->window_width <= 0) || (background->window_height <= 0)) + { + to_image = nullptr; + from_image = nullptr; + return; } + from_image = to_image; + to_image = next_image; + + int width, height; + + to_image->generate_adjustments(background->window_width, background->window_height); + width = to_image->source->get_width(); + height = to_image->source->get_height(); + glBindTexture(GL_TEXTURE_2D, to_image->tex_id); + auto format = to_image->source->get_has_alpha() ? GL_RGBA : GL_RGB; + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, + to_image->source->get_pixels()); + glBindTexture(GL_TEXTURE_2D, 0); + + fade = { + fade_duration, + wf::animation::smoothing::sigmoid + }; + fade.animate(0.0, 1.0); + + this->queue_draw(); +} + +static GLuint create_texture() +{ + GLuint tex; + + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + + return tex; +} + +BackgroundImage::BackgroundImage() +{ + tex_id = create_texture(); +} + +BackgroundImage::~BackgroundImage() +{ + glDeleteTextures(1, &tex_id); +} + +Glib::RefPtr WayfireBackground::create_from_file_safe(std::string path) +{ + Glib::RefPtr image = Glib::RefPtr(new BackgroundImage()); + image->fill_type = (std::string)background_fill_mode; + try { - pbuf = - Gdk::Pixbuf::create_from_file(path, width, height, - true); + image->source = Gdk::Pixbuf::create_from_file(path); } catch (...) { - return Glib::RefPtr(); + return nullptr; } - if (!fill_and_crop_string.compare(background_fill_mode)) - { - float screen_aspect_ratio = (float)width / height; - float image_aspect_ratio = (float)pbuf->get_width() / pbuf->get_height(); - bool should_fill_width = (screen_aspect_ratio > image_aspect_ratio); - if (should_fill_width) - { - image_scale = (double)width / pbuf->get_width(); - offset_y = ((height / image_scale) - pbuf->get_height()) * 0.5; - offset_x = 0; - } else - { - image_scale = (double)height / pbuf->get_height(); - offset_x = ((width / image_scale) - pbuf->get_width()) * 0.5; - offset_y = 0; - } - } else + if (image->source == nullptr) { - bool eq_width = (width == pbuf->get_width()); - image_scale = 1.0; - offset_x = eq_width ? 0 : (width - pbuf->get_width()) * 0.5; - offset_y = eq_width ? (height - pbuf->get_height()) * 0.5 : 0; + return nullptr; } - return pbuf; + return image; } bool WayfireBackground::change_background() { - Glib::RefPtr pbuf; - std::string path; - - if (!load_next_background(pbuf, path)) + auto next_image = load_next_background(); + if (!next_image) { return false; } - std::cout << "Loaded " << path << std::endl; - drawing_area.show_image(pbuf, offset_x, offset_y, image_scale); + gl_area->show_image(next_image); + return true; } @@ -226,31 +367,46 @@ bool WayfireBackground::load_images_from_dir(std::string path) return true; } -bool WayfireBackground::load_next_background(Glib::RefPtr & pbuf, - std::string & path) +Glib::RefPtr WayfireBackground::load_next_background() { - while (!pbuf) + Glib::RefPtr image; + while (!image) { if (!images.size()) { std::cerr << "Failed to load background images from " << (std::string)background_image << std::endl; - window.remove(); - return false; + // window.remove(); + return nullptr; } current_background = (current_background + 1) % images.size(); - path = images[current_background]; - pbuf = create_from_file_safe(path); + auto path = images[current_background]; + image = create_from_file_safe(path); - if (!pbuf) + if (!image) { images.erase(images.begin() + current_background); + } else + { + std::cout << "Picked background " << path << std::endl; } } - return true; + return image; +} + +void WayfireBackground::update_background() +{ + Glib::RefPtr image = Glib::RefPtr(new BackgroundImage()); + auto current = gl_area->get_current_image(); + if (current != nullptr) + { + image->source = current->source; + image->fill_type = background_fill_mode; + gl_area->show_image(image); + } } void WayfireBackground::reset_background() @@ -258,32 +414,32 @@ void WayfireBackground::reset_background() images.clear(); current_background = 0; change_bg_conn.disconnect(); - scale = window.get_scale_factor(); } void WayfireBackground::set_background() { - Glib::RefPtr pbuf; - reset_background(); std::string path = background_image; try { if (load_images_from_dir(path) && images.size()) { - if (!load_next_background(pbuf, path)) + auto image = load_next_background(); + if (!image) { throw std::exception(); } - std::cout << "Loaded " << path << std::endl; + gl_area->show_image(image); } else { - pbuf = create_from_file_safe(path); - if (!pbuf) + auto image = create_from_file_safe(path); + if (!image) { throw std::exception(); } + + gl_area->show_image(image); } } catch (...) { @@ -291,7 +447,6 @@ void WayfireBackground::set_background() } reset_cycle_timeout(); - drawing_area.show_image(pbuf, offset_x, offset_y, image_scale); if (inhibited && output->output) { @@ -307,37 +462,144 @@ void WayfireBackground::reset_cycle_timeout() if (images.size()) { change_bg_conn = Glib::signal_timeout().connect(sigc::mem_fun( - this, &WayfireBackground::change_background), cycle_timeout); + *this, &WayfireBackground::change_background), cycle_timeout); } } -void WayfireBackground::setup_window() +void BackgroundGLArea::realize() +{ + this->make_current(); + program = init_shaders(); +} + +bool BackgroundGLArea::render(const Glib::RefPtr& context) { - window.set_decorated(false); + static const float vertices[] = { + 1.0f, 1.0f, + -1.0f, 1.0f, + -1.0f, -1.0f, + 1.0f, -1.0f, + }; + static const float uv_coords[] = { + 1.0f, 0.0f, + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + }; + static float from_adj[4] = {0.0, 0.0, 1.0, 1.0}; + if (from_image) + { + from_adj[0] = from_image->adjustments->x; + from_adj[1] = from_image->adjustments->y; + from_adj[2] = from_image->adjustments->scale_x; + from_adj[3] = from_image->adjustments->scale_y; + } - gtk_layer_init_for_window(window.gobj()); - gtk_layer_set_layer(window.gobj(), GTK_LAYER_SHELL_LAYER_BACKGROUND); - gtk_layer_set_monitor(window.gobj(), this->output->monitor->gobj()); + static float to_adj[4] = {0.0, 0.0, 1.0, 1.0}; + if (to_image) + { + to_adj[0] = to_image->adjustments->x; + to_adj[1] = to_image->adjustments->y; + to_adj[2] = to_image->adjustments->scale_x; + to_adj[3] = to_image->adjustments->scale_y; + } + + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + glUseProgram(program); + if (from_image) + { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, from_image->tex_id); + GLuint from_tex_uniform = glGetUniformLocation(program, "bg_texture_from"); + glUniform1i(from_tex_uniform, 0); + } - gtk_layer_set_anchor(window.gobj(), GTK_LAYER_SHELL_EDGE_TOP, true); - gtk_layer_set_anchor(window.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, true); - gtk_layer_set_anchor(window.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, true); - gtk_layer_set_anchor(window.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, true); - gtk_layer_set_keyboard_mode(window.gobj(), GTK_LAYER_SHELL_KEYBOARD_MODE_ON_DEMAND); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, to_image->tex_id); + GLuint to_tex_uniform = glGetUniformLocation(program, "bg_texture_to"); + glUniform1i(to_tex_uniform, 1); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, uv_coords); + GLuint progress = glGetUniformLocation(program, "progress"); + glUniform1f(progress, fade); + GLuint from_adj_loc = glGetUniformLocation(program, "from_adj"); + glUniform4fv(from_adj_loc, 1, from_adj); + GLuint to_adj_loc = glGetUniformLocation(program, "to_adj"); + glUniform4fv(to_adj_loc, 1, to_adj); + + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, 0); + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glUseProgram(0); - gtk_layer_set_exclusive_zone(window.gobj(), -1); - window.add(drawing_area); - window.show_all(); + if (fade.running()) + { + Glib::signal_idle().connect([=] () + { + this->queue_draw(); + return false; + }); + } + + return true; +} - auto reset_background = [=] () { set_background(); }; +BackgroundGLArea::BackgroundGLArea(WayfireBackground *background) +{ + this->background = background; +} + +BackgroundWindow::BackgroundWindow(WayfireBackground *background) +{ + this->background = background; +} + +void BackgroundWindow::size_allocate_vfunc(int width, int height, int baseline) +{ + Gtk::Widget::size_allocate_vfunc(width, height, baseline); + background->window_width = width; + background->window_height = height; + + background->set_background(); +} + +void WayfireBackground::setup_window() +{ + gl_area = Glib::RefPtr(new BackgroundGLArea(this)); + window = Glib::RefPtr(new BackgroundWindow(this)); + window->set_decorated(false); + + gtk_layer_init_for_window(window->gobj()); + gtk_layer_set_layer(window->gobj(), GTK_LAYER_SHELL_LAYER_BACKGROUND); + gtk_layer_set_monitor(window->gobj(), this->output->monitor->gobj()); + + gtk_layer_set_anchor(window->gobj(), GTK_LAYER_SHELL_EDGE_TOP, true); + gtk_layer_set_anchor(window->gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, true); + gtk_layer_set_anchor(window->gobj(), GTK_LAYER_SHELL_EDGE_LEFT, true); + gtk_layer_set_anchor(window->gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, true); + gtk_layer_set_keyboard_mode(window->gobj(), GTK_LAYER_SHELL_KEYBOARD_MODE_ON_DEMAND); + + gtk_layer_set_exclusive_zone(window->gobj(), -1); + gl_area->signal_realize().connect(sigc::mem_fun(*gl_area, &BackgroundGLArea::realize)); + gl_area->signal_render().connect(sigc::mem_fun(*gl_area, &BackgroundGLArea::render), false); + window->set_child(*gl_area); + + auto update_background = [=] () { this->update_background(); }; + auto set_background = [=] () { this->set_background(); }; auto reset_cycle = [=] () { reset_cycle_timeout(); }; - background_image.set_callback(reset_background); - background_randomize.set_callback(reset_background); - background_fill_mode.set_callback(reset_background); + background_image.set_callback(set_background); + background_fill_mode.set_callback(update_background); background_cycle_timeout.set_callback(reset_cycle); - window.property_scale_factor().signal_changed().connect( - sigc::mem_fun(this, &WayfireBackground::set_background)); + window->present(); } WayfireBackground::WayfireBackground(WayfireShellApp *app, WayfireOutput *output) @@ -352,17 +614,6 @@ WayfireBackground::WayfireBackground(WayfireShellApp *app, WayfireOutput *output } setup_window(); - - this->window.signal_size_allocate().connect_notify( - [this, width = 0, height = 0] (Gtk::Allocation& alloc) mutable - { - if ((alloc.get_width() != width) || (alloc.get_height() != height)) - { - this->set_background(); - width = alloc.get_width(); - height = alloc.get_height(); - } - }); } WayfireBackground::~WayfireBackground() @@ -379,9 +630,9 @@ class WayfireBackgroundApp : public WayfireShellApp static void create(int argc, char **argv) { WayfireShellApp::instance = - std::make_unique(argc, argv); + std::make_unique(); g_unix_signal_add(SIGUSR1, sigusr1_handler, (void*)instance.get()); - instance->run(); + instance->run(argc, argv); } void handle_new_output(WayfireOutput *output) override diff --git a/src/background/background.hpp b/src/background/background.hpp index f34db143..1affa40c 100644 --- a/src/background/background.hpp +++ b/src/background/background.hpp @@ -7,18 +7,34 @@ #include #include +#include + class WayfireBackground; +class BackgroundImageAdjustments +{ + public: + GLfloat scale_x = -1, scale_y = -1; + GLfloat x, y; +}; + class BackgroundImage { public: - double scale; - double x, y; - Cairo::RefPtr source; + BackgroundImage(); + ~BackgroundImage(); + Glib::RefPtr source; + std::string fill_type; + Glib::RefPtr adjustments; + + void generate_adjustments(int width, int height); + GLuint tex_id = 0; }; -class BackgroundDrawingArea : public Gtk::DrawingArea +class BackgroundGLArea : public Gtk::GLArea { + WayfireBackground *background; + GLuint program = 0; wf::animation::simple_animation_t fade; WfOption fade_duration{"background/fade_duration"}; @@ -28,15 +44,28 @@ class BackgroundDrawingArea : public Gtk::DrawingArea * pbuf is the current image to which we are fading and * pbuf2 is the image from which we are fading. x and y * are used as offsets when preserve aspect is set. */ - BackgroundImage to_image, from_image; + Glib::RefPtr to_image, from_image; public: - BackgroundDrawingArea(); - void show_image(Glib::RefPtr image, - double offset_x, double offset_y, double image_scale); + BackgroundGLArea(WayfireBackground *background); + void realize(); + bool render(const Glib::RefPtr& context); + void show_image(Glib::RefPtr image); + Glib::RefPtr get_current_image() + { + return to_image; + } +}; + +class BackgroundWindow : public Gtk::Window +{ + WayfireBackground *background; + + public: + BackgroundWindow(WayfireBackground *background); protected: - bool on_draw(const Cairo::RefPtr& cr) override; + void size_allocate_vfunc(int width, int height, int baseline) override; }; class WayfireBackground @@ -44,14 +73,11 @@ class WayfireBackground WayfireShellApp *app; WayfireOutput *output; - BackgroundDrawingArea drawing_area; + Glib::RefPtr gl_area; std::vector images; - Gtk::Window window; + Glib::RefPtr window; - int scale; - double offset_x, offset_y; - double image_scale = 1.0; - bool inhibited = false; + bool inhibited = false; uint current_background; sigc::connection change_bg_conn; @@ -60,18 +86,21 @@ class WayfireBackground WfOption background_randomize{"background/randomize"}; WfOption background_fill_mode{"background/fill_mode"}; - Glib::RefPtr create_from_file_safe(std::string path); + Glib::RefPtr create_from_file_safe(std::string path); bool background_transition_frame(int timer); bool load_images_from_dir(std::string path); - bool load_next_background(Glib::RefPtr & pbuf, std::string & path); + Glib::RefPtr load_next_background(); void reset_background(); - void set_background(); + void update_background(); void reset_cycle_timeout(); void setup_window(); public: + guint window_width = 0; + guint window_height = 0; WayfireBackground(WayfireShellApp *app, WayfireOutput *output); + void set_background(); bool change_background(); ~WayfireBackground(); }; diff --git a/src/background/meson.build b/src/background/meson.build index 8d5fad00..9d22bbb1 100644 --- a/src/background/meson.build +++ b/src/background/meson.build @@ -1,3 +1,3 @@ executable('wf-background', ['background.cpp'], - dependencies: [gtkmm, wayland_client, libutil, wf_protos, wfconfig, gtklayershell], + dependencies: [gtkmm, wayland_client, libutil, wf_protos, wfconfig, gtklayershell, epoxy], install: true) diff --git a/src/dock/dock-app.cpp b/src/dock/dock-app.cpp index 9f755b53..dc6e6d9b 100644 --- a/src/dock/dock-app.cpp +++ b/src/dock/dock-app.cpp @@ -2,7 +2,8 @@ #include "toplevel.hpp" #include "toplevel-icon.hpp" #include -#include +#include +#include namespace @@ -46,6 +47,7 @@ class WfDockApp::impl void WfDockApp::on_activate() { WayfireShellApp::on_activate(); + new CssFromConfigInt("dock/icon_height", ".toplevel-icon {-gtk-icon-size:", "px;}"); IconProvider::load_custom_icons(); /* At this point, wayland connection has been initialized, @@ -113,7 +115,19 @@ void WfDockApp::handle_new_toplevel(zwlr_foreign_toplevel_handle_v1 *handle) void WfDockApp::handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 *handle) { - priv->toplevels.erase(handle); + priv->toplevels[handle]->close(); + WfOption use_close_animations{"dock/show_close"}; + if (use_close_animations) + { + Glib::signal_timeout().connect([=] + { + priv->toplevels.erase(handle); + return false; + }, 2000); + } else + { + priv->toplevels.erase(handle); + } } WfDockApp& WfDockApp::get() @@ -133,12 +147,11 @@ void WfDockApp::create(int argc, char **argv) throw std::logic_error("Running WfDockApp twice!"); } - instance = std::unique_ptr{new WfDockApp(argc, argv)}; - instance->run(); + instance = std::unique_ptr{new WfDockApp()}; + instance->run(argc, argv); } -WfDockApp::WfDockApp(int argc, char **argv) : - WayfireShellApp(argc, argv), priv(new WfDockApp::impl()) +WfDockApp::WfDockApp() : WayfireShellApp(), priv(new WfDockApp::impl()) {} WfDockApp::~WfDockApp() = default; diff --git a/src/dock/dock.cpp b/src/dock/dock.cpp index 165fbb30..ba461c21 100644 --- a/src/dock/dock.cpp +++ b/src/dock/dock.cpp @@ -1,25 +1,25 @@ #include +#include #include -#include - -#include -#include +#include #include #include -#include +#include #include #include "dock.hpp" #include "../util/gtk-utils.hpp" +#include + class WfDock::impl { WayfireOutput *output; std::unique_ptr window; wl_surface *_wl_surface; - - Gtk::HBox box; + Gtk::Box out_box; + Gtk::Box box; WfOption css_path{"dock/css_path"}; WfOption dock_height{"dock/dock_height"}; @@ -30,56 +30,49 @@ class WfDock::impl this->output = output; window = std::unique_ptr( new WayfireAutohidingWindow(output, "dock")); - - window->set_size_request(dock_height, dock_height); gtk_layer_set_layer(window->gobj(), GTK_LAYER_SHELL_LAYER_TOP); + gtk_layer_set_anchor(window->gobj(), GTK_LAYER_SHELL_EDGE_LEFT, true); + gtk_layer_set_anchor(window->gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, true); + gtk_layer_set_margin(window->gobj(), GTK_LAYER_SHELL_EDGE_LEFT, 0); + gtk_layer_set_margin(window->gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, 0); + out_box.append(box); + out_box.get_style_context()->add_class("out-box"); + box.get_style_context()->add_class("box"); + window->set_child(out_box); + + window->get_style_context()->add_class("wf-dock"); - window->signal_size_allocate().connect_notify( - sigc::mem_fun(this, &WfDock::impl::on_allocation)); - window->add(box); + out_box.set_halign(Gtk::Align::CENTER); if ((std::string)css_path != "") { auto css = load_css_from_path(css_path); if (css) { - auto screen = Gdk::Screen::get_default(); - auto style_context = Gtk::StyleContext::create(); - style_context->add_provider_for_screen( - screen, css, GTK_STYLE_PROVIDER_PRIORITY_USER); + auto display = Gdk::Display::get_default(); + Gtk::StyleContext::add_provider_for_display(display, css, GTK_STYLE_PROVIDER_PRIORITY_USER); } } - window->show_all(); - _wl_surface = gdk_wayland_window_get_wl_surface( - window->get_window()->gobj()); + window->present(); + _wl_surface = gdk_wayland_surface_get_wl_surface( + window->get_surface()->gobj()); + + box.add_tick_callback([=] (Glib::RefPtr fc) + { + set_clickable_region(); + return true; + }); } void add_child(Gtk::Widget& widget) { - box.pack_end(widget); - box.show_all(); + box.append(widget); } void rem_child(Gtk::Widget& widget) { this->box.remove(widget); - - /* We now need to resize the dock so it fits the remaining widgets. */ - int total_width = 0; - int total_height = last_height; - box.foreach([&] (Gtk::Widget& child) - { - Gtk::Requisition min_req, pref_req; - child.get_preferred_size(min_req, pref_req); - - total_width += min_req.width; - total_height = std::max(total_height, min_req.height); - }); - - total_width = std::min(total_height, 100); - this->window->resize(total_width, total_height); - this->window->set_size_request(total_width, total_height); } wl_surface *get_wl_surface() @@ -87,14 +80,23 @@ class WfDock::impl return this->_wl_surface; } - int32_t last_width = 100, last_height = 100; - void on_allocation(Gtk::Allocation& alloc) + /* Sets the central section as clickable and transparent edges as click-through + * Gets called regularly to ensure css size changes all register */ + void set_clickable_region() { - if ((last_width != alloc.get_width()) || (last_height != alloc.get_height())) - { - last_width = alloc.get_width(); - last_height = alloc.get_height(); - } + auto surface = window->get_surface(); + auto widget_bounds = box.compute_bounds(*window); + + auto rect = Cairo::RectangleInt{ + (int)widget_bounds->get_x(), + (int)widget_bounds->get_y(), + (int)widget_bounds->get_width(), + (int)widget_bounds->get_height() + }; + + auto region = Cairo::Region::create(rect); + + surface->set_input_region(region); } }; diff --git a/src/dock/dock.hpp b/src/dock/dock.hpp index 63941246..012c8e80 100644 --- a/src/dock/dock.hpp +++ b/src/dock/dock.hpp @@ -1,12 +1,12 @@ #ifndef WF_DOCK_HPP #define WF_DOCK_HPP -#include -#include +#include #include -#include "toplevel-icon.hpp" #include "wf-shell-app.hpp" +#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" +#include class WfDock { @@ -44,7 +44,7 @@ class WfDockApp : public WayfireShellApp void handle_output_removed(WayfireOutput *output) override; private: - WfDockApp(int argc, char **argv); + WfDockApp(); class impl; std::unique_ptr priv; diff --git a/src/dock/toplevel-icon.cpp b/src/dock/toplevel-icon.cpp index d0dbfe5e..9572bc0e 100644 --- a/src/dock/toplevel-icon.cpp +++ b/src/dock/toplevel-icon.cpp @@ -1,12 +1,12 @@ #include #include -#include +#include #include #include #include #include -#include +#include #include "dock.hpp" #include "toplevel.hpp" @@ -29,6 +29,7 @@ class WfToplevelIcon::impl wl_output *output; uint32_t state; + bool closing = false; Gtk::Button button; Gtk::Image image; @@ -41,17 +42,13 @@ class WfToplevelIcon::impl this->handle = handle; this->output = output; - button.add(image); + button.set_child(image); button.set_tooltip_text("none"); button.get_style_context()->add_class("flat"); - button.show_all(); + button.get_style_context()->add_class("toplevel-icon"); - button.signal_clicked().connect_notify( - sigc::mem_fun(this, &WfToplevelIcon::impl::on_clicked)); - button.signal_size_allocate().connect_notify( - sigc::mem_fun(this, &WfToplevelIcon::impl::on_allocation_changed)); - button.property_scale_factor().signal_changed() - .connect(sigc::mem_fun(this, &WfToplevelIcon::impl::on_scale_update)); + button.signal_clicked().connect( + sigc::mem_fun(*this, &WfToplevelIcon::impl::on_clicked)); auto dock = WfDockApp::get().dock_for_wl_output(output); assert(dock); // ToplevelIcon is created only for existing outputs @@ -60,6 +57,11 @@ class WfToplevelIcon::impl void on_clicked() { + if (closing) + { + return; + } + if (!(state & WF_TOPLEVEL_STATE_ACTIVATED)) { auto gseat = Gdk::Display::get_default()->get_default_seat(); @@ -78,18 +80,13 @@ class WfToplevelIcon::impl } } - void on_allocation_changed(Gtk::Allocation& alloc) - { - send_rectangle_hint(); - } - - void on_scale_update() - { - set_app_id(app_id); - } - void set_app_id(std::string app_id) { + if (closing) + { + return; + } + this->app_id = app_id; IconProvider::set_image_from_icon(image, app_id, @@ -99,6 +96,11 @@ class WfToplevelIcon::impl void send_rectangle_hint() { + if (closing) + { + return; + } + Gtk::Widget *widget = &this->button; int x = 0, y = 0; @@ -124,21 +126,55 @@ class WfToplevelIcon::impl void set_title(std::string title) { + if (closing) + { + return; + } + button.set_tooltip_text(title); } + void close() + { + button.get_style_context()->add_class("closing"); + closing = true; + } + void set_state(uint32_t state) { + if (closing) + { + return; + } + bool was_activated = this->state & WF_TOPLEVEL_STATE_ACTIVATED; this->state = state; bool is_activated = this->state & WF_TOPLEVEL_STATE_ACTIVATED; - + bool is_min = state & WF_TOPLEVEL_STATE_MINIMIZED; + bool is_max = state & WF_TOPLEVEL_STATE_MAXIMIZED; + auto style = this->button.get_style_context(); if (!was_activated && is_activated) { - this->button.get_style_context()->remove_class("flat"); + style->remove_class("flat"); } else if (was_activated && !is_activated) { - this->button.get_style_context()->add_class("flat"); + style->add_class("flat"); + } + + if (is_min) + { + style->add_class("minimized"); + } else + { + style->remove_class("minimized"); + } + + if (is_max) + { + style->add_class("maximized"); + } else + { + style->remove_class("maximized"); } } @@ -172,6 +208,11 @@ void WfToplevelIcon::set_state(uint32_t state) return pimpl->set_state(state); } +void WfToplevelIcon::close() +{ + return pimpl->close(); +} + /* Icon loading functions */ namespace IconProvider { @@ -216,13 +257,7 @@ bool set_custom_icon(Gtk::Image& image, std::string app_id, int size, int scale) return false; } - auto pb = load_icon_pixbuf_safe(custom_icons[app_id], size * scale); - if (!pb.get()) - { - return false; - } - - set_image_pixbuf(image, pb, scale); + image_set_icon(&image, custom_icons[app_id]); return true; } @@ -286,6 +321,7 @@ void set_image_from_icon(Gtk::Image& image, /* Wayfire sends a list of app-id's in space separated format, other compositors * send a single app-id, but in any case this works fine */ + auto display = image.get_display(); while (stream >> app_id) { /* Try first method: custom icon file provided by the user */ @@ -302,7 +338,7 @@ void set_image_from_icon(Gtk::Image& image, if (!icon) { /* Finally try directly looking up the icon, if it exists */ - if (Gtk::IconTheme::get_default()->lookup_icon(app_id, 24)) + if (Gtk::IconTheme::get_for_display(display)->lookup_icon(app_id, 24)) { icon_name = app_id; } @@ -313,7 +349,7 @@ void set_image_from_icon(Gtk::Image& image, WfIconLoadOptions options; options.user_scale = scale; - set_image_icon(image, icon_name, size, options); + image_set_icon(&image, icon_name); /* finally found some icon */ if (icon_name != "unknown") diff --git a/src/dock/toplevel-icon.hpp b/src/dock/toplevel-icon.hpp index 75f8758c..7c589b05 100644 --- a/src/dock/toplevel-icon.hpp +++ b/src/dock/toplevel-icon.hpp @@ -12,6 +12,7 @@ class WfToplevelIcon void set_app_id(std::string app_id); void set_title(std::string title); void set_state(uint32_t state); + void close(); class impl; diff --git a/src/dock/toplevel.cpp b/src/dock/toplevel.cpp index e04d1bd2..a8bb94cd 100644 --- a/src/dock/toplevel.cpp +++ b/src/dock/toplevel.cpp @@ -86,6 +86,14 @@ class WfToplevel::impl icon.second->set_state(state); } } + + void close() + { + for (auto& icon : icons) + { + icon.second->close(); + } + } }; @@ -99,6 +107,11 @@ void WfToplevel::handle_output_leave(wl_output *output) pimpl->handle_output_leave(output); } +void WfToplevel::close() +{ + pimpl->close(); +} + using toplevel_t = zwlr_foreign_toplevel_handle_v1*; static void handle_toplevel_title(void *data, toplevel_t, const char *title) { diff --git a/src/dock/toplevel.hpp b/src/dock/toplevel.hpp index ef12f3a2..64202dbd 100644 --- a/src/dock/toplevel.hpp +++ b/src/dock/toplevel.hpp @@ -19,6 +19,7 @@ class WfToplevel WfToplevel(zwlr_foreign_toplevel_handle_v1 *handle); ~WfToplevel(); + void close(); void handle_output_leave(wl_output *output); class impl; diff --git a/src/panel/meson.build b/src/panel/meson.build index 0a511c76..f086c614 100644 --- a/src/panel/meson.build +++ b/src/panel/meson.build @@ -1,29 +1,44 @@ -widget_sources = ['widgets/battery.cpp', - 'widgets/menu.cpp', - 'widgets/clock.cpp', - 'widgets/command-output.cpp', - 'widgets/launchers.cpp', - 'widgets/network.cpp', - 'widgets/spacing.cpp', - 'widgets/separator.cpp', - 'widgets/window-list/window-list.cpp', - 'widgets/window-list/toplevel.cpp', - 'widgets/notifications/daemon.cpp', - 'widgets/notifications/single-notification.cpp', - 'widgets/notifications/notification-info.cpp', - 'widgets/notifications/notification-center.cpp', - 'widgets/tray/watcher.cpp', - 'widgets/tray/tray.cpp', - 'widgets/tray/item.cpp', - 'widgets/tray/host.cpp'] +widget_sources = [ + 'widgets/battery.cpp', + 'widgets/menu.cpp', + 'widgets/clock.cpp', + 'widgets/command-output.cpp', + 'widgets/launchers.cpp', + 'widgets/network.cpp', + 'widgets/spacing.cpp', + 'widgets/separator.cpp', + 'widgets/window-list/window-list.cpp', + 'widgets/window-list/toplevel.cpp', + 'widgets/window-list/layout.cpp', + 'widgets/notifications/daemon.cpp', + 'widgets/notifications/single-notification.cpp', + 'widgets/notifications/notification-info.cpp', + 'widgets/notifications/notification-center.cpp', + 'widgets/tray/watcher.cpp', + 'widgets/tray/tray.cpp', + 'widgets/tray/item.cpp', + 'widgets/tray/host.cpp', + 'widgets/tray/dbusmenu.cpp', +] -deps = [gtkmm, wayland_client, libutil, wf_protos, wfconfig, gtklayershell, dbusmenu_gtk] +deps = [ + gtkmm, + wayland_client, + libutil, + wf_protos, + wfconfig, + gtklayershell, + dbusmenu_gtk, +] if libpulse.found() widget_sources += 'widgets/volume.cpp' deps += [libpulse, libgvc] endif -executable('wf-panel', ['panel.cpp'] + widget_sources, - dependencies: deps, - install: true) +executable( + 'wf-panel', + ['panel.cpp'] + widget_sources, + dependencies: deps, + install: true, +) \ No newline at end of file diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index 566dd517..d684d17c 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -1,20 +1,17 @@ #include #include #include -#include +#include #include -#include -#include +#include +#include -#include #include #include #include -#include - +#include #include "panel.hpp" -#include "../util/gtk-utils.hpp" #include "widgets/battery.hpp" #include "widgets/command-output.hpp" @@ -37,8 +34,8 @@ class WayfirePanel::impl { std::unique_ptr window; - Gtk::HBox content_box; - Gtk::HBox left_box, center_box, right_box; + Gtk::CenterBox content_box; + Gtk::Box left_box, center_box, right_box; using Widget = std::unique_ptr; using WidgetContainer = std::vector; @@ -46,39 +43,6 @@ class WayfirePanel::impl WayfireOutput *output; - WfOption bg_color{"panel/background_color"}; - std::function on_window_color_updated = [=] () - { - if ((std::string)bg_color == "gtk_default") - { - return window->unset_background_color(); - } - - Gdk::RGBA rgba; - if ((std::string)bg_color == "gtk_headerbar") - { - Gtk::HeaderBar headerbar; - rgba = headerbar.get_style_context()->get_background_color(); - } else - { - auto color = wf::option_type::from_string( - ((wf::option_sptr_t)bg_color)->get_value_str()); - if (!color) - { - std::cerr << "Invalid panel background color in" - " config file" << std::endl; - return; - } - - rgba.set_red(color.value().r); - rgba.set_green(color.value().g); - rgba.set_blue(color.value().b); - rgba.set_alpha(color.value().a); - } - - window->override_background_color(rgba); - }; - WfOption panel_layer{"panel/layer"}; std::function set_panel_layer = [=] () { @@ -108,47 +72,41 @@ class WayfirePanel::impl void create_window() { window = std::make_unique(output, "panel"); - window->set_size_request(1, minimal_panel_height); + + window->set_default_size(0, minimal_panel_height); window->get_style_context()->add_class("wf-panel"); panel_layer.set_callback(set_panel_layer); set_panel_layer(); // initial setting - gtk_layer_set_anchor(window->gobj(), GTK_LAYER_SHELL_EDGE_LEFT, true); gtk_layer_set_anchor(window->gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, true); + gtk_layer_set_margin(window->gobj(), GTK_LAYER_SHELL_EDGE_LEFT, 0); + gtk_layer_set_margin(window->gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, 0); - bg_color.set_callback(on_window_color_updated); - on_window_color_updated(); // set initial color - - window->show_all(); + window->present(); init_widgets(); init_layout(); - - window->signal_delete_event().connect( - sigc::mem_fun(this, &WayfirePanel::impl::on_delete)); - } - - bool on_delete(GdkEventAny *ev) - { - /* We ignore close events, because the panel's lifetime is bound to - * the lifetime of the output */ - return true; } void init_layout() { left_box.get_style_context()->add_class("left"); - center_box.get_style_context()->add_class("center"); right_box.get_style_context()->add_class("right"); - content_box.pack_start(left_box, false, false); - content_box.pack_end(right_box, false, false); + center_box.get_style_context()->add_class("center"); + content_box.set_start_widget(left_box); if (!center_box.get_children().empty()) { content_box.set_center_widget(center_box); } - center_box.show_all(); - window->add(content_box); - window->show_all(); + content_box.set_end_widget(right_box); + + content_box.set_hexpand(true); + + left_box.set_halign(Gtk::Align::START); + center_box.set_halign(Gtk::Align::CENTER); + right_box.set_halign(Gtk::Align::END); + + window->set_child(content_box); } std::optional widget_with_value(std::string value, std::string prefix) @@ -262,14 +220,24 @@ class WayfirePanel::impl } void reload_widgets(std::string list, WidgetContainer& container, - Gtk::HBox& box) + Gtk::Box& box) { const auto lock_sn_watcher = Watcher::Instance(); const auto lock_notification_daemon = Daemon::Instance(); + for (auto child : box.get_children()) + { + box.remove(*child); + } + container.clear(); auto widgets = tokenize(list); for (auto widget_name : widgets) { + if (widget_name == "window-list") + { + box.set_hexpand(true); + } + auto widget = widget_from_name(widget_name); if (!widget) { @@ -278,6 +246,7 @@ class WayfirePanel::impl widget->widget_name = widget_name; widget->init(&box); + container.push_back(std::move(widget)); } } @@ -378,60 +347,22 @@ void WayfirePanelApp::on_config_reload() } } -void WayfirePanelApp::on_css_reload() -{ - clear_css_rules(); - /* Add user directory */ - std::string ext(".css"); - for (auto & p : std::filesystem::directory_iterator(get_css_config_dir())) - { - if (p.path().extension() == ext) - { - int priority = GTK_STYLE_PROVIDER_PRIORITY_USER; - if (p.path().filename() == "default.css") - { - priority = GTK_STYLE_PROVIDER_PRIORITY_APPLICATION; - } - - add_css_file(p.path().string(), priority); - } - } - - /* Add one user file */ - auto custom_css_config = WfOption{"panel/css_path"}; - std::string custom_css = custom_css_config; - if (custom_css != "") - { - add_css_file(custom_css, GTK_STYLE_PROVIDER_PRIORITY_USER); - } -} - -void WayfirePanelApp::clear_css_rules() -{ - auto screen = Gdk::Screen::get_default(); - auto style_context = Gtk::StyleContext::create(); - for (auto css_provider : css_rules) - { - style_context->remove_provider_for_screen(screen, css_provider); - } - - css_rules.clear(); -} - -void WayfirePanelApp::add_css_file(std::string file, int priority) +void WayfirePanelApp::on_activate() { - auto screen = Gdk::Screen::get_default(); - auto style_context = Gtk::StyleContext::create(); - if (file != "") - { - auto css_provider = load_css_from_path(file); - if (css_provider) - { - style_context->add_provider_for_screen( - screen, css_provider, priority); - css_rules.push_back(css_provider); - } - } + WayfireShellApp::on_activate(); + new CssFromConfigInt("panel/launchers_size", ".menu-button,.launcher{-gtk-icon-size:", "px;}"); + new CssFromConfigInt("panel/launchers_spacing", ".launcher{padding: 0px ", "px;}"); + new CssFromConfigInt("panel/battery_icon_size", ".battery image{-gtk-icon-size:", "px;}"); + new CssFromConfigInt("panel/network_icon_size", ".network{-gtk-icon-size:", "px;}"); + new CssFromConfigInt("panel/volume_icon_size", ".volume{-gtk-icon-size:", "px;}"); + new CssFromConfigInt("panel/notifications_icon_size", ".notification-center{-gtk-icon-size:", "px;}"); + new CssFromConfigInt("panel/tray_icon_size", ".tray-button{-gtk-icon-size:", "px;}"); + new CssFromConfigString("panel/background_color", ".wf-panel{background-color:", ";}"); + new CssFromConfigBool("panel/battery_icon_invert", ".battery image{filter:invert(100%);}", ""); + new CssFromConfigBool("panel/network_icon_invert_color", ".network-icon{filter:invert(100%);}", ""); + + new CssFromConfigFont("panel/battery_font", ".battery {", "}"); + new CssFromConfigFont("panel/clock_font", ".clock {", "}"); } void WayfirePanelApp::handle_new_output(WayfireOutput *output) @@ -475,13 +406,12 @@ void WayfirePanelApp::create(int argc, char **argv) throw std::logic_error("Running WayfirePanelApp twice!"); } - instance = std::unique_ptr(new WayfirePanelApp{argc, argv}); - instance->run(); + instance = std::unique_ptr(new WayfirePanelApp{}); + instance->run(argc, argv); } WayfirePanelApp::~WayfirePanelApp() = default; -WayfirePanelApp::WayfirePanelApp(int argc, char **argv) : - WayfireShellApp(argc, argv), priv(new impl()) +WayfirePanelApp::WayfirePanelApp() : WayfireShellApp(), priv(new impl()) {} int main(int argc, char **argv) diff --git a/src/panel/panel.hpp b/src/panel/panel.hpp index c20f94b9..cae9903e 100644 --- a/src/panel/panel.hpp +++ b/src/panel/panel.hpp @@ -33,19 +33,16 @@ class WayfirePanelApp : public WayfireShellApp static void create(int argc, char **argv); ~WayfirePanelApp(); + void on_activate() override; void handle_new_output(WayfireOutput *output) override; void handle_output_removed(WayfireOutput *output) override; void on_config_reload() override; - void on_css_reload() override; private: - WayfirePanelApp(int argc, char **argv); + WayfirePanelApp(); - void clear_css_rules(); - void add_css_file(std::string file, int priority); class impl; - std::vector> css_rules; std::unique_ptr priv; }; diff --git a/src/panel/widget.hpp b/src/panel/widget.hpp index 35576008..6f85c344 100644 --- a/src/panel/widget.hpp +++ b/src/panel/widget.hpp @@ -1,7 +1,7 @@ #ifndef WIDGET_HPP #define WIDGET_HPP -#include +#include #include #include @@ -17,7 +17,7 @@ class WayfireWidget public: std::string widget_name; // for WayfirePanel use, widgets shouldn't change it - virtual void init(Gtk::HBox *container) = 0; + virtual void init(Gtk::Box *container) = 0; virtual void handle_config_reload() {} virtual ~WayfireWidget() diff --git a/src/panel/widgets/battery.cpp b/src/panel/widgets/battery.cpp index 2491044d..090e1f23 100644 --- a/src/panel/widgets/battery.cpp +++ b/src/panel/widgets/battery.cpp @@ -74,11 +74,7 @@ void WayfireBatteryInfo::update_icon() { Glib::Variant icon_name; display_device->get_cached_property(icon_name, ICON); - - WfIconLoadOptions options; - options.invert = invert_opt; - options.user_scale = button.get_scale_factor(); - set_image_icon(icon, icon_name.get(), size_opt, options); + icon.set_from_icon_name(icon_name.get()); } static std::string state_descriptions[] = { @@ -115,17 +111,6 @@ static std::string uint_to_time(int64_t time) return format_digit(hrs) + ":" + format_digit(min); } -void WayfireBatteryInfo::update_font() -{ - if ((std::string)font_opt == "default") - { - label.unset_font(); - } else - { - label.override_font(Pango::FontDescription((std::string)font_opt)); - } -} - void WayfireBatteryInfo::update_details() { Glib::Variant type; @@ -183,7 +168,7 @@ void WayfireBatteryInfo::update_state() bool WayfireBatteryInfo::setup_dbus() { auto cancellable = Gio::Cancellable::create(); - connection = Gio::DBus::Connection::get_sync(Gio::DBus::BUS_TYPE_SYSTEM, cancellable); + connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SYSTEM, cancellable); if (!connection) { std::cerr << "Failed to connect to dbus" << std::endl; @@ -213,7 +198,7 @@ bool WayfireBatteryInfo::setup_dbus() if (present.get()) { display_device->signal_properties_changed().connect( - sigc::mem_fun(this, &WayfireBatteryInfo::on_properties_changed)); + sigc::mem_fun(*this, &WayfireBatteryInfo::on_properties_changed)); return true; } @@ -223,33 +208,27 @@ bool WayfireBatteryInfo::setup_dbus() // TODO: simplify config loading -static const std::string default_font = "default"; -void WayfireBatteryInfo::init(Gtk::HBox *container) +void WayfireBatteryInfo::init(Gtk::Box *container) { if (!setup_dbus()) { return; } - button_box.add(icon); + button_box.append(icon); button.get_style_context()->add_class("battery"); button.get_style_context()->add_class("flat"); status_opt.set_callback([=] () { update_details(); }); - font_opt.set_callback([=] () { update_font(); }); - size_opt.set_callback([=] () { update_icon(); }); - invert_opt.set_callback([=] () { update_icon(); }); update_details(); - update_font(); update_icon(); - container->pack_start(button, Gtk::PACK_SHRINK); - button_box.add(label); + container->append(button); + button_box.append(label); + button_box.set_spacing(5); - button.add(button_box); + button.set_child(button_box); button.property_scale_factor().signal_changed() - .connect(sigc::mem_fun(this, &WayfireBatteryInfo::update_icon)); - - button.show_all(); + .connect(sigc::mem_fun(*this, &WayfireBatteryInfo::update_icon)); } diff --git a/src/panel/widgets/battery.hpp b/src/panel/widgets/battery.hpp index b2a5d986..a3366e19 100644 --- a/src/panel/widgets/battery.hpp +++ b/src/panel/widgets/battery.hpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include @@ -22,13 +22,10 @@ class wayfire_config; class WayfireBatteryInfo : public WayfireWidget { WfOption status_opt{"panel/battery_status"}; - WfOption font_opt{"panel/battery_font"}; - WfOption size_opt{"panel/battery_icon_size"}; - WfOption invert_opt{"panel/battery_icon_invert"}; Gtk::Button button; Gtk::Label label; - Gtk::HBox button_box; + Gtk::Box button_box; Gtk::Image icon; @@ -37,7 +34,6 @@ class WayfireBatteryInfo : public WayfireWidget bool setup_dbus(); - void update_font(); void update_icon(); void update_details(); void update_state(); @@ -47,7 +43,7 @@ class WayfireBatteryInfo : public WayfireWidget const std::vector& invalidated); public: - virtual void init(Gtk::HBox *container); + virtual void init(Gtk::Box *container); virtual ~WayfireBatteryInfo() = default; }; diff --git a/src/panel/widgets/clock.cpp b/src/panel/widgets/clock.cpp index 8d1c1b05..616e9d8d 100644 --- a/src/panel/widgets/clock.cpp +++ b/src/panel/widgets/clock.cpp @@ -2,11 +2,11 @@ #include #include "clock.hpp" -void WayfireClock::init(Gtk::HBox *container) +void WayfireClock::init(Gtk::Box *container) { button = std::make_unique("panel"); button->get_style_context()->add_class("clock"); - button->add(label); + button->set_child(label); button->show(); label.show(); @@ -14,19 +14,14 @@ void WayfireClock::init(Gtk::HBox *container) calendar.show(); button->get_popover()->get_style_context()->add_class("clock-popover"); - button->get_popover()->add(calendar); - button->get_popover()->signal_show().connect_notify( - sigc::mem_fun(this, &WayfireClock::on_calendar_shown)); + button->get_popover()->set_child(calendar); + button->get_popover()->signal_show().connect( + sigc::mem_fun(*this, &WayfireClock::on_calendar_shown)); - container->pack_start(*button, false, false); + container->append(*button); timeout = Glib::signal_timeout().connect_seconds( - sigc::mem_fun(this, &WayfireClock::update_label), 1); - - // initially set font - set_font(); - - font.set_callback([=] () { set_font(); }); + sigc::mem_fun(*this, &WayfireClock::update_label), 1); } void WayfireClock::on_calendar_shown() @@ -34,8 +29,8 @@ void WayfireClock::on_calendar_shown() auto now = Glib::DateTime::create_now_local(); /* GDateTime uses month in 1-12 format while GClender uses 0-11 */ - calendar.select_month(now.get_month() - 1, now.get_year()); - calendar.select_day(now.get_day_of_month()); + // calendar.set_month(now.get_month() - 1, now.get_year()); + calendar.select_day(now); } bool WayfireClock::update_label() @@ -59,17 +54,6 @@ bool WayfireClock::update_label() return 1; } -void WayfireClock::set_font() -{ - if ((std::string)font == "default") - { - label.unset_font(); - } else - { - label.override_font(Pango::FontDescription((std::string)font)); - } -} - WayfireClock::~WayfireClock() { timeout.disconnect(); diff --git a/src/panel/widgets/clock.hpp b/src/panel/widgets/clock.hpp index 415f3d10..91486fb1 100644 --- a/src/panel/widgets/clock.hpp +++ b/src/panel/widgets/clock.hpp @@ -14,13 +14,11 @@ class WayfireClock : public WayfireWidget sigc::connection timeout; WfOption format{"panel/clock_format"}; - WfOption font{"panel/clock_font"}; - void set_font(); void on_calendar_shown(); public: - void init(Gtk::HBox *container) override; + void init(Gtk::Box *container) override; bool update_label(); ~WayfireClock(); }; diff --git a/src/panel/widgets/command-output.cpp b/src/panel/widgets/command-output.cpp index 91f96279..23597099 100644 --- a/src/panel/widgets/command-output.cpp +++ b/src/panel/widgets/command-output.cpp @@ -7,6 +7,7 @@ #include #include +#include #include @@ -18,7 +19,7 @@ static void label_set_from_command(std::string command_line, Glib::Pid pid; int output_fd; Glib::spawn_async_with_pipes("", Glib::shell_parse_argv(command_line), - Glib::SPAWN_DO_NOT_REAP_CHILD | Glib::SPAWN_SEARCH_PATH_FROM_ENVP, + Glib::SpawnFlags::DO_NOT_REAP_CHILD | Glib::SpawnFlags::SEARCH_PATH_FROM_ENVP, Glib::SlotSpawnChildSetup{}, &pid, nullptr, &output_fd, nullptr); Glib::signal_child_watch().connect([=, &label] (Glib::Pid pid, int exit_status) { @@ -48,21 +49,22 @@ WfCommandOutputButtons::CommandOutput::CommandOutput(const std::string & name, const std::string & icon_name, int icon_size, const std::string & icon_position) { + this->tooltip_command = tooltip_command; if (icon_size > 0) { - set_image_icon(icon, icon_name, icon_size, {}); + image_set_icon(&icon, icon_name); } get_style_context()->add_class("command-output"); get_style_context()->add_class("icon-" + icon_position); - main_label.set_ellipsize(Pango::ELLIPSIZE_END); + main_label.set_ellipsize(Pango::EllipsizeMode::END); main_label.set_max_width_chars(max_chars_opt); max_chars_opt.set_callback([=] { main_label.set_max_width_chars(max_chars_opt); }); - main_label.set_alignment(Gtk::ALIGN_CENTER); + // main_label.set_alignment(Gtk::ALIGN_CENTER); max_chars_opt.set_callback([this] { main_label.set_max_width_chars(max_chars_opt); @@ -71,16 +73,16 @@ WfCommandOutputButtons::CommandOutput::CommandOutput(const std::string & name, box.set_orientation( icon_position == "bottom" || icon_position == - "top" ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL); + "top" ? Gtk::Orientation::VERTICAL : Gtk::Orientation::HORIZONTAL); if ((icon_position == "right") || (icon_position == "bottom")) { - box.pack_start(main_label); - box.pack_start(icon); + box.append(main_label); + box.append(icon); } else { - box.pack_start(icon); - box.pack_start(main_label); + box.append(icon); + box.append(main_label); } if (icon_name.empty()) @@ -88,9 +90,8 @@ WfCommandOutputButtons::CommandOutput::CommandOutput(const std::string & name, box.remove(icon); } - box.show_all(); - add(box); - set_relief(Gtk::RELIEF_NONE); + set_child(box); + // set_relief(Gtk::RELIEF_NONE); const auto update_output = [=] () { @@ -113,34 +114,37 @@ WfCommandOutputButtons::CommandOutput::CommandOutput(const std::string & name, update_output(); } - const auto update_tooltip = [=] - { - if (std::time(nullptr) - last_tooltip_update < 1) - { - return; - } - - label_set_from_command(tooltip_command, tooltip_label); - }; - if (!tooltip_command.empty()) { set_has_tooltip(); tooltip_label.show(); - signal_query_tooltip().connect([=] (int, int, bool, - const Glib::RefPtr& tooltip) - { - update_tooltip(); - tooltip->set_custom(tooltip_label); - return true; - }); + signal_query_tooltip().connect(sigc::mem_fun(*this, + &WfCommandOutputButtons::CommandOutput::query_tooltip), false); } } -void WfCommandOutputButtons::init(Gtk::HBox *container) +bool WfCommandOutputButtons::CommandOutput::query_tooltip(int i, int j, bool k, + const std::shared_ptr& tooltip) +{ + this->update_tooltip(); + tooltip->set_custom(tooltip_label); + return true; +} + +void WfCommandOutputButtons::CommandOutput::update_tooltip() +{ + if (std::time(nullptr) - last_tooltip_update < 1) + { + return; + } + + label_set_from_command(tooltip_command, tooltip_label); +} + +void WfCommandOutputButtons::init(Gtk::Box *container) { box.get_style_context()->add_class("command-output-box"); - container->pack_start(box, false, false); + container->append(box); update_buttons(); commands_list_opt.set_callback([=] { update_buttons(); }); } @@ -148,6 +152,11 @@ void WfCommandOutputButtons::init(Gtk::HBox *container) void WfCommandOutputButtons::update_buttons() { const auto & opt_value = commands_list_opt.value(); + for (auto child : box.get_children()) + { + box.remove(*child); + } + buttons.clear(); buttons.reserve(opt_value.size()); for (const auto & command_info : opt_value) @@ -156,8 +165,6 @@ void WfCommandOutputButtons::update_buttons() { return std::make_unique(args...); }, command_info)); - box.pack_start(*buttons.back(), false, false); + box.append(*buttons.back()); } - - box.show_all(); } diff --git a/src/panel/widgets/command-output.hpp b/src/panel/widgets/command-output.hpp index 78e156fa..308b7127 100644 --- a/src/panel/widgets/command-output.hpp +++ b/src/panel/widgets/command-output.hpp @@ -23,6 +23,8 @@ class WfCommandOutputButtons : public WayfireWidget Gtk::Label tooltip_label; time_t last_tooltip_update = 0; + std::string tooltip_command; + WfOption max_chars_opt{"panel/commands_output_max_chars"}; void init(); @@ -35,21 +37,22 @@ class WfCommandOutputButtons : public WayfireWidget CommandOutput(const CommandOutput&) = delete; CommandOutput& operator =(CommandOutput&&) = delete; CommandOutput& operator =(const CommandOutput&) = delete; - + bool query_tooltip(int i, int j, bool k, const std::shared_ptr& tooltip); + void update_tooltip(); ~CommandOutput() override { timeout_connection.disconnect(); } }; - Gtk::HBox box; + Gtk::Box box; std::vector> buttons; WfOption> commands_list_opt{"panel/commands"}; public: - void init(Gtk::HBox *container) override; + void init(Gtk::Box *container) override; void update_buttons(); }; diff --git a/src/panel/widgets/launchers.cpp b/src/panel/widgets/launchers.cpp index aaf2fb96..46da4d35 100644 --- a/src/panel/widgets/launchers.cpp +++ b/src/panel/widgets/launchers.cpp @@ -23,16 +23,16 @@ bool WfLauncherButton::initialize(std::string name, std::string icon, std::strin } else { // Generate a .desktop file in memory - auto keyfile = Glib::KeyFile(); - keyfile.set_string("Desktop Entry", "Type", "Application"); - keyfile.set_string("Desktop Entry", "Exec", "/bin/sh -c \"" + name + "\""); - keyfile.set_string("Desktop Entry", "Icon", icon); + auto keyfile = Glib::KeyFile::create(); + keyfile->set_string("Desktop Entry", "Type", "Application"); + keyfile->set_string("Desktop Entry", "Exec", "/bin/sh -c \"" + name + "\""); + keyfile->set_string("Desktop Entry", "Icon", icon); if (label == "") { label = name; } - keyfile.set_string("Desktop Entry", "Name", label); + keyfile->set_string("Desktop Entry", "Name", label); // Hand off to have a custom launcher app_info = Gio::DesktopAppInfo::create_from_keyfile(keyfile); @@ -44,15 +44,12 @@ bool WfLauncherButton::initialize(std::string name, std::string icon, std::strin return false; } - button.set_image(m_icon); + button.set_child(m_icon); auto style = button.get_style_context(); style->add_class("flat"); style->add_class("launcher"); button.signal_clicked().connect([=] () { launch(); }); - button.property_scale_factor().signal_changed() - .connect([=] () {update_icon(); }); - icon_size.set_callback([=] () { update_icon(); }); update_icon(); @@ -62,14 +59,15 @@ bool WfLauncherButton::initialize(std::string name, std::string icon, std::strin void WfLauncherButton::update_icon() { - set_image_icon(m_icon, app_info->get_icon()->to_string(), icon_size); + image_set_icon(&m_icon, app_info->get_icon()->to_string()); } void WfLauncherButton::launch() { if (app_info) { - app_info->launch(std::vector>()); + auto ctx = Gdk::Display::get_default()->get_app_launch_context(); + app_info->launch(std::vector>(), ctx); } } @@ -146,22 +144,23 @@ launcher_container WayfireLaunchers::get_launchers_from_config() return launchers; } -void WayfireLaunchers::init(Gtk::HBox *container) +void WayfireLaunchers::init(Gtk::Box *container) { box.get_style_context()->add_class("launchers"); - container->pack_start(box, false, false); + container->append(box); handle_config_reload(); } void WayfireLaunchers::handle_config_reload() { - box.set_spacing(WfOption{"panel/launchers_spacing"}); + for (auto child : box.get_children()) + { + box.remove(*child); + } launchers = get_launchers_from_config(); for (auto& l : launchers) { - box.pack_start(l->button, false, false); + box.append(l->button); } - - box.show_all(); } diff --git a/src/panel/widgets/launchers.hpp b/src/panel/widgets/launchers.hpp index 677efed2..4f1fb696 100644 --- a/src/panel/widgets/launchers.hpp +++ b/src/panel/widgets/launchers.hpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include @@ -14,7 +14,6 @@ struct WfLauncherButton Gtk::Image m_icon; Gtk::Button button; Glib::RefPtr app_info; - WfOption icon_size{"panel/launchers_size"}; WfLauncherButton(); WfLauncherButton(const WfLauncherButton& other) = delete; @@ -29,12 +28,12 @@ struct WfLauncherButton using launcher_container = std::vector>; class WayfireLaunchers : public WayfireWidget { - Gtk::HBox box; + Gtk::Box box; launcher_container launchers; launcher_container get_launchers_from_config(); public: - virtual void init(Gtk::HBox *container); + virtual void init(Gtk::Box *container); virtual void handle_config_reload(); virtual ~WayfireLaunchers() {} diff --git a/src/panel/widgets/menu.cpp b/src/panel/widgets/menu.cpp index 00759611..12a9b146 100644 --- a/src/panel/widgets/menu.cpp +++ b/src/panel/widgets/menu.cpp @@ -2,20 +2,17 @@ #include #include -#include +#include #include #include -#include +#include #include -#include -#include #include "menu.hpp" #include "gtk-utils.hpp" #include "launchers.hpp" #include "wf-autohide-window.hpp" -#define MAX_LAUNCHER_NAME_LENGTH 11 const std::string default_icon = "wayfire"; WfMenuCategory::WfMenuCategory(std::string _name, std::string _icon_name) : @@ -36,22 +33,21 @@ WfMenuCategoryButton::WfMenuCategoryButton(WayfireMenu *_menu, std::string _cate std::string _icon_name) : Gtk::Button(), menu(_menu), category(_category), label(_label), icon_name(_icon_name) { - m_image.set_from_icon_name(icon_name, - (Gtk::IconSize)Gtk::ICON_SIZE_LARGE_TOOLBAR); + m_image.set_from_icon_name(icon_name); m_image.set_pixel_size(32); m_label.set_text(label); m_label.set_xalign(0.0); - m_box.pack_start(m_image, false, false, 2); - m_box.pack_end(m_label, true, true, 2); + m_box.append(m_image); + m_box.append(m_label); m_box.set_homogeneous(false); - this->add(m_box); + this->set_child(m_box); this->get_style_context()->add_class("flat"); this->get_style_context()->add_class("app-category"); - this->signal_clicked().connect_notify( - sigc::mem_fun(this, &WfMenuCategoryButton::on_click)); + this->signal_clicked().connect( + sigc::mem_fun(*this, &WfMenuCategoryButton::on_click)); } void WfMenuCategoryButton::on_click() @@ -62,106 +58,90 @@ void WfMenuCategoryButton::on_click() WfMenuMenuItem::WfMenuMenuItem(WayfireMenu *_menu, Glib::RefPtr app) : Gtk::FlowBoxChild(), menu(_menu), m_app_info(app) { - m_image.set((const Glib::RefPtr&)app->get_icon(), - (Gtk::IconSize)Gtk::ICON_SIZE_LARGE_TOOLBAR); + m_image.set((const Glib::RefPtr&)app->get_icon()); m_image.set_pixel_size(48); m_label.set_text(app->get_name()); - auto event_box = new Gtk::EventBox(); - + m_label.set_xalign(0.0); + m_label.set_hexpand(true); m_has_actions = app->list_actions().size() > 0; + m_button_box.append(m_image); + m_button_box.append(m_label); + + m_button.set_child(m_button_box); + m_button.signal_clicked().connect( + [this] () + { + this->on_click(); + }); + m_padding_box.append(m_button); + m_label.set_ellipsize(Pango::EllipsizeMode::END); + m_label.set_max_width_chars(5); + m_button.get_style_context()->add_class("flat"); + m_extra_actions_button.get_style_context()->add_class("flat"); + m_extra_actions_button.get_style_context()->add_class("app-button-extras"); + m_extra_actions_button.set_halign(Gtk::Align::END); + m_extra_actions_button.set_direction(Gtk::ArrowType::RIGHT); + m_extra_actions_button.set_has_frame(false); + m_extra_actions_button.set_icon_name("arrow-right"); + m_menu = Gio::Menu::create(); + m_actions = Gio::SimpleActionGroup::create(); + m_extra_actions_button.hide(); if (menu->menu_list) { + m_padding_box.append(m_extra_actions_button); this->set_size_request(menu->menu_min_content_width, 48); - m_list_box.pack_start(m_image, false, false); - m_list_box.pack_start(m_label, true, true); - m_label.set_xalign(0.0); - event_box->add(m_list_box); - m_padding_box.pack_start(*event_box); - - if (m_has_actions) + for (auto action : app->list_actions()) { - auto action_button = new Gtk::Button(); - action_button->set_image_from_icon_name("pan-end"); - action_button->get_style_context()->add_class("flat"); - action_button->get_style_context()->add_class("app-button-extras"); - m_padding_box.pack_start(*action_button, false, false); - - action_button->signal_clicked().connect( - [this] () - { - m_action_menu.popup_at_widget(this, Gdk::GRAVITY_NORTH_EAST, Gdk::GRAVITY_NORTH_WEST, NULL); - }); - } - } else - { - m_label.set_max_width_chars(MAX_LAUNCHER_NAME_LENGTH); - m_label.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END); - m_button_box.pack_start(m_image, false, false); - m_button_box.pack_end(m_label, false, false); + std::stringstream ss; + ss << "app." << action; + std::string full_action = ss.str(); - event_box->add(m_button_box); + auto menu_item = Gio::MenuItem::create(m_app_info->get_action_name(action), full_action); - /* Wrap the button box into a HBox, with left/right padding. - * This way, the button doesn't fill the whole area allocated for an entry - * in the flowbox */ - m_padding_box.pack_start(m_left_pad); - m_padding_box.pack_start(*event_box); - m_padding_box.pack_start(m_right_pad); - } - - for (auto action : app->list_actions()) - { - auto menu_item = new Gtk::MenuItem(); - menu_item->set_label(m_app_info->get_action_name(action)); - menu_item->show(); - menu_item->signal_activate().connect( - [this, action] () - { - m_app_info->launch_action(action); - menu->hide_menu(); - }); - m_action_menu.append(*menu_item); - } + auto action_obj = Gio::SimpleAction::create(action); + action_obj->signal_activate().connect( + [this, action] (Glib::VariantBase vb) + { + auto ctx = Gdk::Display::get_default()->get_app_launch_context(); + m_app_info->launch_action(action, ctx); + menu->hide_menu(); + }); + m_menu->append_item(menu_item); + m_actions->add_action(action_obj); - add(m_padding_box); - get_style_context()->add_class("app-button"); - /* Can accept Enter/Return from search_box */ - set_can_default(true); - - /* Called when activated by Enter/Return */ - this->signal_activate().connect_notify( - sigc::mem_fun(this, &WfMenuMenuItem::on_click)); - /* Click doesn't automatically result in activate, here we get it ourselves - * Doubles up as a right-click catcher for action menu popup */ - event_box->signal_button_release_event().connect( - [this] (GdkEventButton *ev) -> bool - { - if ((ev->button == GDK_BUTTON_PRIMARY) && (ev->type == GDK_BUTTON_RELEASE)) - { - on_click(); + m_extra_actions_button.show(); } - if (m_has_actions && (ev->button == GDK_BUTTON_SECONDARY) && (ev->type == GDK_BUTTON_RELEASE)) - { - if (menu->menu_list) - { - m_action_menu.popup_at_widget(this, Gdk::GRAVITY_NORTH_EAST, Gdk::GRAVITY_NORTH_WEST, NULL); - } else - { - m_action_menu.popup_at_widget(this, Gdk::GRAVITY_SOUTH, Gdk::GRAVITY_NORTH, NULL); - } + m_extra_actions_button.set_menu_model(m_menu); + } else + {} - return true; - } + set_child(m_padding_box); + get_style_context()->add_class("app-button"); + set_has_tooltip(); + signal_query_tooltip().connect([=] (int x, int y, bool key_mode, + const std::shared_ptr& tooltip) -> bool + { + tooltip->set_text(app->get_name()); + return true; + }, false); + m_extra_actions_button.insert_action_group("app", m_actions); - return false; + auto click_gesture = Gtk::GestureClick::create(); + click_gesture->set_button(3); + click_gesture->signal_pressed().connect([=] (int count, double x, double y) + { + m_extra_actions_button.activate(); + click_gesture->set_state(Gtk::EventSequenceState::CLAIMED); }); + m_button.add_controller(click_gesture); } void WfMenuMenuItem::on_click() { - m_app_info->launch(std::vector>()); + auto ctx = Gdk::Display::get_default()->get_app_launch_context(); + m_app_info->launch(std::vector>(), ctx); menu->hide_menu(); } @@ -288,8 +268,7 @@ void WayfireMenu::load_menu_item(AppInfo app_info) return; } - auto desktop_app_info = Glib::RefPtr::cast_dynamic(app_info); - if (desktop_app_info && desktop_app_info->get_nodisplay()) + if (app_info->get_nodisplay()) { return; } @@ -313,25 +292,25 @@ void WayfireMenu::load_menu_item(AppInfo app_info) /* Check if this has a 'OnlyShownIn' for a different desktop env * If so, we throw it in a pile at the bottom just to be safe */ - if (!desktop_app_info->should_show()) + if (!app_info->should_show()) { - add_category_app("Hidden", desktop_app_info); + add_category_app("Hidden", app_info); return; } - add_category_app("All", desktop_app_info); + add_category_app("All", app_info); /* Split the Categories, iterate to place into submenus */ - std::stringstream categories_stream(desktop_app_info->get_categories()); + std::stringstream categories_stream(app_info->get_categories()); std::string segment; while (std::getline(categories_stream, segment, ';')) { - add_category_app(segment, desktop_app_info); + add_category_app(segment, app_info); } } -void WayfireMenu::add_category_app(std::string category, Glib::RefPtr app) +void WayfireMenu::add_category_app(std::string category, AppInfo app) { /* Filter for allowed categories */ if (category_list.count(category) == 1) @@ -345,7 +324,7 @@ void WayfireMenu::populate_menu_categories() // Ensure the category list is empty for (auto child : category_box.get_children()) { - gtk_widget_destroy(GTK_WIDGET(child->gobj())); + category_box.remove(*child); } // Iterate allowed categories in order @@ -359,15 +338,13 @@ void WayfireMenu::populate_menu_categories() auto icon_name = category->get_icon_name(); auto name = category->get_name(); auto category_button = new WfMenuCategoryButton(this, category_name, name, icon_name); - category_box.pack_start(*category_button); + category_box.append(*category_button); } } else { std::cerr << "Category in orderlist without Category object : " << category << std::endl; } } - - category_box.show_all(); } void WayfireMenu::populate_menu_items(std::string category) @@ -375,16 +352,14 @@ void WayfireMenu::populate_menu_items(std::string category) /* Ensure the flowbox is empty */ for (auto child : flowbox.get_children()) { - gtk_widget_destroy(GTK_WIDGET(child->gobj())); + flowbox.remove(*child); } for (auto app_info : category_list[category]->items) { auto app = new WfMenuMenuItem(this, app_info); - flowbox.add(*app); + flowbox.append(*app); } - - flowbox.show_all(); } static bool ends_with(std::string text, std::string pattern) @@ -431,7 +406,8 @@ void WayfireMenu::load_menu_items_all() auto app_list = Gio::AppInfo::get_all(); for (auto app : app_list) { - load_menu_item(app); + auto desktop_app = std::dynamic_pointer_cast(app); + load_menu_item(desktop_app); } load_menu_items_from_dir(home_dir + "/Desktop"); @@ -439,10 +415,10 @@ void WayfireMenu::load_menu_items_all() void WayfireMenu::on_search_changed() { - auto value = search_box.get_text(); + search_entry.set_text(search_contents); if (menu_show_categories) { - if (value.length() == 0) + if (search_contents.length() == 0) { /* Text has been unset, show categories again */ populate_menu_items(category); @@ -452,13 +428,10 @@ void WayfireMenu::on_search_changed() { /* User is filtering, hide categories, ignore chosen category */ populate_menu_items("All"); - category_scrolled_window.hide(); - app_scrolled_window.set_min_content_width(int(menu_min_content_width) + - int(menu_min_category_width)); } } - m_sort_names = value.length() == 0; + m_sort_names = search_contents.length() == 0; fuzzy_filter = false; count_matches = 0; flowbox.unselect_all(); @@ -482,7 +455,7 @@ bool WayfireMenu::on_filter(Gtk::FlowBoxChild *child) auto button = dynamic_cast(child); assert(button); - auto text = search_box.get_text(); + auto text = search_contents; uint32_t match_score = this->fuzzy_filter ? button->fuzzy_match(text) : button->matches(text); @@ -512,7 +485,8 @@ bool WayfireMenu::on_sort(Gtk::FlowBoxChild *a, Gtk::FlowBoxChild *b) void WayfireMenu::on_popover_shown() { - search_box.set_text(""); + search_contents = ""; + on_search_changed(); set_category("All"); flowbox.unselect_all(); } @@ -528,7 +502,7 @@ bool WayfireMenu::update_icon() icon = menu_icon; } - set_image_icon(main_image, icon, menu_size); + image_set_icon(&main_image, icon); return true; } @@ -537,67 +511,104 @@ void WayfireMenu::update_popover_layout() /* First time updating layout, need to setup everything */ if (popover_layout_box.get_parent() == nullptr) { - button->get_popover()->add(popover_layout_box); + button->get_popover()->set_child(popover_layout_box); - flowbox.set_selection_mode(Gtk::SelectionMode::SELECTION_SINGLE); + flowbox.set_selection_mode(Gtk::SelectionMode::SINGLE); flowbox.set_activate_on_single_click(true); - flowbox.set_valign(Gtk::ALIGN_START); + flowbox.set_valign(Gtk::Align::START); flowbox.set_homogeneous(true); - flowbox.set_sort_func(sigc::mem_fun(this, &WayfireMenu::on_sort)); - flowbox.set_filter_func(sigc::mem_fun(this, &WayfireMenu::on_filter)); + flowbox.set_sort_func(sigc::mem_fun(*this, &WayfireMenu::on_sort)); + flowbox.set_filter_func(sigc::mem_fun(*this, &WayfireMenu::on_filter)); flowbox.get_style_context()->add_class("app-list"); + flowbox.set_size_request(int(menu_min_content_width), int(menu_min_content_height)); - flowbox_container.add(bottom_pad); - flowbox_container.add(flowbox); + flowbox_container.append(flowbox); - scroll_pair.add(category_scrolled_window); - scroll_pair.add(app_scrolled_window); + scroll_pair.append(category_scrolled_window); + scroll_pair.append(app_scrolled_window); scroll_pair.set_homogeneous(false); app_scrolled_window.set_min_content_width(int(menu_min_content_width)); app_scrolled_window.set_min_content_height(int(menu_min_content_height)); - app_scrolled_window.add(flowbox_container); + app_scrolled_window.set_child(flowbox_container); app_scrolled_window.get_style_context()->add_class("app-list-scroll"); + app_scrolled_window.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC); category_box.get_style_context()->add_class("category-list"); + category_box.set_orientation(Gtk::Orientation::VERTICAL); category_scrolled_window.set_min_content_width(int(menu_min_category_width)); category_scrolled_window.set_min_content_height(int(menu_min_content_height)); - category_scrolled_window.add(category_box); + category_scrolled_window.set_child(category_box); category_scrolled_window.get_style_context()->add_class("categtory-list-scroll"); + category_scrolled_window.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC); + + search_entry.get_style_context()->add_class("app-search"); + search_entry.set_icon_from_icon_name("search", Gtk::Entry::IconPosition::SECONDARY); + search_entry.set_can_focus(false); - search_box.set_can_default(false); - search_box.set_activates_default(true); - search_box.property_margin().set_value(20); - search_box.set_icon_from_icon_name("search", Gtk::ENTRY_ICON_SECONDARY); - search_box.get_style_context()->add_class("app-search"); - - /* Cursor moved in flowbox */ - flowbox.signal_selected_children_changed().connect_notify( - sigc::mem_fun(this, &WayfireMenu::set_default_to_selection)); - /* Search value changed */ - search_box.signal_changed().connect_notify( - sigc::mem_fun(this, &WayfireMenu::on_search_changed)); + auto typing_gesture = Gtk::EventControllerKey::create(); + typing_gesture->set_propagation_phase(Gtk::PropagationPhase::CAPTURE); + typing_gesture->signal_key_pressed().connect([=] (guint keyval, guint keycode, + Gdk::ModifierType state) + { + if (keyval == GDK_KEY_BackSpace) + { + if (search_contents.length() > 0) + { + search_contents.pop_back(); + } + + on_search_changed(); + return true; + } else if ((keyval == GDK_KEY_Return) || (keyval == GDK_KEY_KP_Enter)) + { + auto children = flowbox.get_selected_children(); + if (children.size() == 1) + { + auto child = dynamic_cast(children[0]); + child->on_click(); + } + + return true; + } else + { + std::string input = gdk_keyval_name(keyval); + if (input.length() == 1) + { + search_contents = search_contents + input; + on_search_changed(); + return true; + } + } + + return false; + }, false); + button->get_popover()->add_controller(typing_gesture); } else { /* Layout was already initialized, make sure to remove widgets before * adding them again */ - popover_layout_box.remove(search_box); + popover_layout_box.remove(search_entry); popover_layout_box.remove(scroll_pair); + popover_layout_box.remove(separator); + popover_layout_box.remove(hbox_bottom); } if ((std::string)panel_position == WF_WINDOW_POSITION_TOP) { - popover_layout_box.pack_start(search_box); - popover_layout_box.pack_start(scroll_pair); + popover_layout_box.append(search_entry); + popover_layout_box.append(scroll_pair); + popover_layout_box.append(separator); + popover_layout_box.append(hbox_bottom); } else { - popover_layout_box.pack_start(scroll_pair); - popover_layout_box.pack_start(search_box); + popover_layout_box.append(scroll_pair); + popover_layout_box.append(search_entry); + popover_layout_box.append(separator); + popover_layout_box.append(hbox_bottom); } - popover_layout_box.show_all(); - if (!menu_show_categories) { category_scrolled_window.hide(); @@ -607,49 +618,42 @@ void WayfireMenu::update_popover_layout() void WayfireLogoutUI::on_logout_click() { ui.hide(); - bg.hide(); g_spawn_command_line_async(std::string(logout_command).c_str(), NULL); } void WayfireLogoutUI::on_reboot_click() { ui.hide(); - bg.hide(); g_spawn_command_line_async(std::string(reboot_command).c_str(), NULL); } void WayfireLogoutUI::on_shutdown_click() { ui.hide(); - bg.hide(); g_spawn_command_line_async(std::string(shutdown_command).c_str(), NULL); } void WayfireLogoutUI::on_suspend_click() { ui.hide(); - bg.hide(); g_spawn_command_line_async(std::string(suspend_command).c_str(), NULL); } void WayfireLogoutUI::on_hibernate_click() { ui.hide(); - bg.hide(); g_spawn_command_line_async(std::string(hibernate_command).c_str(), NULL); } void WayfireLogoutUI::on_switchuser_click() { ui.hide(); - bg.hide(); g_spawn_command_line_async(std::string(switchuser_command).c_str(), NULL); } void WayfireLogoutUI::on_cancel_click() { ui.hide(); - bg.hide(); } #define LOGOUT_BUTTON_SIZE 125 @@ -659,74 +663,76 @@ void WayfireLogoutUI::create_logout_ui_button(WayfireLogoutUIButton *button, con const char *label) { button->button.set_size_request(LOGOUT_BUTTON_SIZE, LOGOUT_BUTTON_SIZE); - button->image.set_from_icon_name(icon, Gtk::ICON_SIZE_DIALOG); + button->image.set_from_icon_name(icon); button->label.set_text(label); - button->layout.pack_start(button->image, true, false); - button->layout.pack_start(button->label, true, false); - button->button.add(button->layout); + button->layout.set_orientation(Gtk::Orientation::VERTICAL); + button->layout.set_halign(Gtk::Align::CENTER); + button->layout.append(button->image); + button->image.set_icon_size(Gtk::IconSize::LARGE); + button->image.set_vexpand(true); + button->layout.append(button->label); + button->button.set_child(button->layout); } WayfireLogoutUI::WayfireLogoutUI() { create_logout_ui_button(&suspend, "emblem-synchronizing", "Suspend"); - suspend.button.signal_clicked().connect_notify( - sigc::mem_fun(this, &WayfireLogoutUI::on_suspend_click)); - top_layout.pack_start(suspend.button, true, false); + suspend.button.signal_clicked().connect( + sigc::mem_fun(*this, &WayfireLogoutUI::on_suspend_click)); + + main_layout.attach(suspend.button, 0, 0, 1, 1); create_logout_ui_button(&hibernate, "weather-clear-night", "Hibernate"); - hibernate.button.signal_clicked().connect_notify( - sigc::mem_fun(this, &WayfireLogoutUI::on_hibernate_click)); - top_layout.pack_start(hibernate.button, true, false); + hibernate.button.signal_clicked().connect( + sigc::mem_fun(*this, &WayfireLogoutUI::on_hibernate_click)); + main_layout.attach(hibernate.button, 1, 0, 1, 1); create_logout_ui_button(&switchuser, "system-users", "Switch User"); - switchuser.button.signal_clicked().connect_notify( - sigc::mem_fun(this, &WayfireLogoutUI::on_switchuser_click)); - top_layout.pack_start(switchuser.button, true, false); + switchuser.button.signal_clicked().connect( + sigc::mem_fun(*this, &WayfireLogoutUI::on_switchuser_click)); + main_layout.attach(switchuser.button, 2, 0, 1, 1); create_logout_ui_button(&logout, "system-log-out", "Log Out"); - logout.button.signal_clicked().connect_notify( - sigc::mem_fun(this, &WayfireLogoutUI::on_logout_click)); - middle_layout.pack_start(logout.button, true, false); + logout.button.signal_clicked().connect( + sigc::mem_fun(*this, &WayfireLogoutUI::on_logout_click)); + main_layout.attach(logout.button, 0, 1, 1, 1); create_logout_ui_button(&reboot, "system-reboot", "Reboot"); - reboot.button.signal_clicked().connect_notify( - sigc::mem_fun(this, &WayfireLogoutUI::on_reboot_click)); - middle_layout.pack_start(reboot.button, true, false); + reboot.button.signal_clicked().connect( + sigc::mem_fun(*this, &WayfireLogoutUI::on_reboot_click)); + main_layout.attach(reboot.button, 1, 1, 1, 1); create_logout_ui_button(&shutdown, "system-shutdown", "Shut Down"); - shutdown.button.signal_clicked().connect_notify( - sigc::mem_fun(this, &WayfireLogoutUI::on_shutdown_click)); - middle_layout.pack_start(shutdown.button, true, false); + shutdown.button.signal_clicked().connect( + sigc::mem_fun(*this, &WayfireLogoutUI::on_shutdown_click)); + main_layout.attach(shutdown.button, 2, 1, 1, 1); cancel.button.set_size_request(100, 50); cancel.button.set_label("Cancel"); - bottom_layout.pack_start(cancel.button, true, false); - cancel.button.signal_clicked().connect_notify( - sigc::mem_fun(this, &WayfireLogoutUI::on_cancel_click)); - - top_layout.set_spacing(LOGOUT_BUTTON_MARGIN); - middle_layout.set_spacing(LOGOUT_BUTTON_MARGIN); - bottom_layout.set_spacing(LOGOUT_BUTTON_MARGIN); - main_layout.set_spacing(LOGOUT_BUTTON_MARGIN); - main_layout.add(top_layout); - main_layout.add(middle_layout); - main_layout.add(bottom_layout); - /* Work around spacing bug for immediate child of window */ - hspacing_layout.pack_start(main_layout, true, false, LOGOUT_BUTTON_MARGIN); - vspacing_layout.pack_start(hspacing_layout, true, false, LOGOUT_BUTTON_MARGIN); + main_layout.attach(cancel.button, 1, 2, 1, 1); + cancel.button.signal_clicked().connect( + sigc::mem_fun(*this, &WayfireLogoutUI::on_cancel_click)); + + main_layout.set_row_spacing(LOGOUT_BUTTON_MARGIN); + main_layout.set_column_spacing(LOGOUT_BUTTON_MARGIN); /* Make surfaces layer shell */ - gtk_layer_init_for_window(bg.gobj()); - gtk_layer_set_layer(bg.gobj(), GTK_LAYER_SHELL_LAYER_TOP); - gtk_layer_set_exclusive_zone(bg.gobj(), -1); gtk_layer_init_for_window(ui.gobj()); gtk_layer_set_layer(ui.gobj(), GTK_LAYER_SHELL_LAYER_OVERLAY); - ui.add(vspacing_layout); - bg.set_opacity(0.5); - auto css_provider = Gtk::CssProvider::create(); - auto style_context = Gtk::StyleContext::create(); - bg.set_name("logout_background"); - css_provider->load_from_data("window#logout_background { background-color: black; }"); - style_context->add_provider_for_screen(Gdk::Screen::get_default(), + + gtk_layer_set_anchor(ui.gobj(), GTK_LAYER_SHELL_EDGE_TOP, true); + gtk_layer_set_anchor(ui.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, true); + gtk_layer_set_anchor(ui.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, true); + gtk_layer_set_anchor(ui.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, true); + main_layout.set_valign(Gtk::Align::CENTER); + hbox.set_center_widget(main_layout); + hbox.set_hexpand(true); + hbox.set_vexpand(true); + ui.set_child(hbox); + ui.get_style_context()->add_class("logout"); + auto display = ui.get_display(); + auto css_provider = Gtk::CssProvider::create(); + css_provider->load_from_data("window.logout { background-color: rgba(0, 0, 0, 0.5); }"); + Gtk::StyleContext::add_provider_for_display(display, css_provider, GTK_STYLE_PROVIDER_PRIORITY_USER); } @@ -741,12 +747,6 @@ void WayfireMenu::on_logout_click() /* If no command specified for logout, show our own logout window */ logout_ui->ui.present(); - logout_ui->ui.show_all(); - logout_ui->bg.show_all(); - - /* Set the background window to the same size of the screen */ - auto gdk_screen = Gdk::Screen::get_default(); - logout_ui->bg.set_size_request(gdk_screen->get_width(), gdk_screen->get_height()); } void WayfireMenu::refresh() @@ -759,13 +759,12 @@ void WayfireMenu::refresh() for (auto child : flowbox.get_children()) { - gtk_widget_destroy(GTK_WIDGET(child->gobj())); + flowbox.remove(*child); } load_menu_items_all(); populate_menu_categories(); populate_menu_items("All"); - flowbox.show_all(); } static void app_info_changed(GAppInfoMonitor *gappinfomonitor, gpointer user_data) @@ -775,7 +774,7 @@ static void app_info_changed(GAppInfoMonitor *gappinfomonitor, gpointer user_dat menu->refresh(); } -void WayfireMenu::init(Gtk::HBox *container) +void WayfireMenu::init(Gtk::Box *container) { /* https://specifications.freedesktop.org/menu-spec/latest/apa.html#main-category-registry * Using the 'Main' categories, with names and icons assigned @@ -808,10 +807,9 @@ void WayfireMenu::init(Gtk::HBox *container) main_image.get_style_context()->add_class("menu-icon"); - output->toggle_menu_signal().connect(sigc::mem_fun(this, &WayfireMenu::toggle_menu)); + output->toggle_menu_signal().connect(sigc::mem_fun(*this, &WayfireMenu::toggle_menu)); menu_icon.set_callback([=] () { update_icon(); }); - menu_size.set_callback([=] () { update_icon(); }); menu_min_category_width.set_callback([=] () { update_category_width(); }); menu_min_content_height.set_callback([=] () { update_content_height(); }); menu_min_content_width.set_callback([=] () { update_content_width(); }); @@ -820,14 +818,13 @@ void WayfireMenu::init(Gtk::HBox *container) menu_list.set_callback([=] () { update_popover_layout(); }); button = std::make_unique("panel"); - button->add(main_image); + button->set_child(main_image); auto style = button->get_style_context(); style->add_class("menu-button"); style->add_class("flat"); - button->get_popover()->set_constrain_to(Gtk::POPOVER_CONSTRAINT_NONE); button->get_popover()->get_style_context()->add_class("menu-popover"); - button->get_popover()->signal_show().connect_notify( - sigc::mem_fun(this, &WayfireMenu::on_popover_shown)); + button->get_popover()->signal_show().connect( + sigc::mem_fun(*this, &WayfireMenu::on_popover_shown)); if (!update_icon()) { @@ -837,17 +834,27 @@ void WayfireMenu::init(Gtk::HBox *container) button->property_scale_factor().signal_changed().connect( [=] () {update_icon(); }); - container->pack_start(hbox, false, false); - hbox.pack_start(*button, false, false); + container->append(hbox); + hbox.append(*button); + + auto click_gesture = Gtk::GestureClick::create(); + click_gesture->signal_pressed().connect([=] (int count, double x, double y) + { + toggle_menu(); + }); + hbox.add_controller(click_gesture); - logout_button.set_image_from_icon_name("system-shutdown", Gtk::ICON_SIZE_DIALOG); - logout_button.signal_clicked().connect_notify( - sigc::mem_fun(this, &WayfireMenu::on_logout_click)); - logout_button.property_margin().set_value(20); - logout_button.set_margin_right(35); - hbox_bottom.pack_end(logout_button, false, false); - popover_layout_box.pack_end(hbox_bottom); - popover_layout_box.pack_end(separator); + logout_image.set_icon_size(Gtk::IconSize::LARGE); + logout_image.set_from_icon_name("system-shutdown"); + logout_button.get_style_context()->add_class("flat"); + logout_button.signal_clicked().connect( + sigc::mem_fun(*this, &WayfireMenu::on_logout_click)); + logout_button.set_margin_end(35); + logout_button.set_child(logout_image); + hbox_bottom.append(logout_button); + hbox_bottom.set_halign(Gtk::Align::END); + + popover_layout_box.set_orientation(Gtk::Orientation::VERTICAL); logout_ui = std::make_unique(); @@ -856,22 +863,6 @@ void WayfireMenu::init(Gtk::HBox *container) populate_menu_categories(); populate_menu_items("All"); - /* Catch keypresses on menu popover, forward to Entry */ - button->get_popover()->signal_key_press_event().connect( - [this] (GdkEventKey *ev) -> bool - { - /* It has string value, send it to searchbox - * This quickly filters navigation keys out */ - std::string input = ev->string; - if (input.length() > 0) - { - return this->search_box.inject(ev); - } - - /* Continue with normal event processing */ - return false; - }); - app_info_monitor_changed_handler_id = g_signal_connect(app_info_monitor, "changed", G_CALLBACK(app_info_changed), this); @@ -898,6 +889,8 @@ void WayfireMenu::update_content_width() void WayfireMenu::toggle_menu() { + search_contents = ""; + search_entry.set_text(""); if (button->get_active()) { button->set_active(false); @@ -918,33 +911,15 @@ void WayfireMenu::set_category(std::string in_category) populate_menu_items(in_category); } -void WayfireMenu::set_default_to_selection() -{ - auto children = flowbox.get_selected_children(); - if (children.size() == 1) - { - children[0]->grab_default(); - children[0]->grab_focus(); - } -} - void WayfireMenu::select_first_flowbox_item() { - for (auto child : flowbox.get_children()) + auto child = flowbox.get_child_at_index(0); + if (child) { - if (child->get_mapped()) + auto cast_child = dynamic_cast(child); + if (cast_child) { - Gtk::FlowBoxChild *cast_child = dynamic_cast(child); - if (cast_child) - { - flowbox.select_child(*cast_child); - return; - } + flowbox.select_child(*cast_child); } } } - -bool WayfireMenuInjectionEntry::inject(GdkEventKey *ev) -{ - return on_key_press_event(ev); -} diff --git a/src/panel/widgets/menu.hpp b/src/panel/widgets/menu.hpp index b5f31c2b..d3a0acff 100644 --- a/src/panel/widgets/menu.hpp +++ b/src/panel/widgets/menu.hpp @@ -4,17 +4,11 @@ #include "../widget.hpp" #include "wf-popover.hpp" #include -#include -#include -#include -#include -#include -#include -#include +#include #include class WayfireMenu; -using AppInfo = Glib::RefPtr; +using AppInfo = Glib::RefPtr; class WfMenuCategory { @@ -22,7 +16,7 @@ class WfMenuCategory WfMenuCategory(std::string name, std::string icon_name); std::string get_name(); std::string get_icon_name(); - std::vector> items; + std::vector items; private: std::string name; @@ -36,7 +30,7 @@ class WfMenuCategoryButton : public Gtk::Button private: WayfireMenu *menu; - Gtk::HBox m_box; + Gtk::Box m_box; Gtk::Label m_label; Gtk::Image m_image; @@ -49,35 +43,38 @@ class WfMenuCategoryButton : public Gtk::Button class WfMenuMenuItem : public Gtk::FlowBoxChild { public: - WfMenuMenuItem(WayfireMenu *menu, Glib::RefPtr app); + WfMenuMenuItem(WayfireMenu *menu, AppInfo app); uint32_t matches(Glib::ustring text); uint32_t fuzzy_match(Glib::ustring text); bool operator <(const WfMenuMenuItem& other); void set_search_value(uint32_t value); uint32_t get_search_value(); + void on_click(); private: WayfireMenu *menu; Gtk::Box m_left_pad, m_right_pad; - Gtk::HBox m_padding_box; - Gtk::VBox m_button_box; - Gtk::HBox m_list_box; + Gtk::Box m_padding_box; + Gtk::Box m_button_box; + Gtk::Button m_button; + Gtk::Box m_list_box; Gtk::Image m_image; Gtk::Label m_label; - Gtk::Menu m_action_menu; + Glib::RefPtr m_menu; + Glib::RefPtr m_actions; + Gtk::MenuButton m_extra_actions_button; bool m_has_actions = false; uint32_t m_search_value = 0; - Glib::RefPtr m_app_info; - void on_click(); + AppInfo m_app_info; }; class WayfireLogoutUIButton { public: - Gtk::VBox layout; + Gtk::Box layout; Gtk::Image image; Gtk::Label label; Gtk::Button button; @@ -93,8 +90,7 @@ class WayfireLogoutUI WfOption suspend_command{"panel/suspend_command"}; WfOption hibernate_command{"panel/hibernate_command"}; WfOption switchuser_command{"panel/switchuser_command"}; - Gtk::Window ui, bg; - Gtk::HBox bg_box; + Gtk::Window ui; WayfireLogoutUIButton logout; WayfireLogoutUIButton reboot; WayfireLogoutUIButton shutdown; @@ -102,8 +98,8 @@ class WayfireLogoutUI WayfireLogoutUIButton hibernate; WayfireLogoutUIButton switchuser; WayfireLogoutUIButton cancel; - Gtk::VBox main_layout, vspacing_layout; - Gtk::HBox top_layout, middle_layout, bottom_layout, hspacing_layout; + Gtk::CenterBox hbox; + Gtk::Grid main_layout; void create_logout_ui_button(WayfireLogoutUIButton *button, const char *icon, const char *label); void on_logout_click(); @@ -115,26 +111,24 @@ class WayfireLogoutUI void on_cancel_click(); }; -class WayfireMenuInjectionEntry : public Gtk::Entry -{ - public: - bool inject(GdkEventKey *ev); -}; class WayfireMenu : public WayfireWidget { WayfireOutput *output; + std::string search_contents = ""; + Gtk::Box flowbox_container; - Gtk::HBox hbox, hbox_bottom, scroll_pair; - Gtk::VBox bottom_pad; - Gtk::VBox popover_layout_box; - Gtk::VBox category_box; + Gtk::Box hbox, hbox_bottom, scroll_pair; + Gtk::Box bottom_pad; + Gtk::Box popover_layout_box; + Gtk::Box category_box; Gtk::Separator separator; Gtk::Image main_image; - WayfireMenuInjectionEntry search_box; + Gtk::Entry search_entry; Gtk::FlowBox flowbox; Gtk::Button logout_button; + Gtk::Image logout_image; Gtk::ScrolledWindow app_scrolled_window, category_scrolled_window; std::unique_ptr button; std::unique_ptr logout_ui; @@ -173,7 +167,6 @@ class WayfireMenu : public WayfireWidget WfOption fuzzy_search_enabled{"panel/menu_fuzzy_search"}; WfOption panel_position{"panel/position"}; WfOption menu_icon{"panel/menu_icon"}; - WfOption menu_size{"panel/launchers_size"}; WfOption menu_min_category_width{"panel/menu_min_category_width"}; WfOption menu_min_content_height{"panel/menu_min_content_height"}; WfOption menu_show_categories{"panel/menu_show_categories"}; @@ -185,10 +178,10 @@ class WayfireMenu : public WayfireWidget void on_logout_click(); void key_press_search(); void select_first_flowbox_item(); - void set_default_to_selection(); public: - void init(Gtk::HBox *container) override; + void arrow_key(Gtk::DirectionType dir); + void init(Gtk::Box *container) override; void populate_menu_items(std::string category); void populate_menu_categories(); void toggle_menu(); diff --git a/src/panel/widgets/network.cpp b/src/panel/widgets/network.cpp index dd1ca0fe..0ed71819 100644 --- a/src/panel/widgets/network.cpp +++ b/src/panel/widgets/network.cpp @@ -36,6 +36,11 @@ struct NoConnectionInfo : public WfNetworkConnectionInfo return 0; } + std::string get_strength_str() + { + return "none"; + } + std::string get_ip() { return "127.0.0.1"; @@ -60,8 +65,8 @@ struct WifiConnectionInfo : public WfNetworkConnectionInfo if (ap) { - ap->signal_properties_changed().connect_notify( - sigc::mem_fun(this, &WifiConnectionInfo::on_properties_changed)); + ap->signal_properties_changed().connect( + sigc::mem_fun(*this, &WifiConnectionInfo::on_properties_changed)); } } @@ -187,6 +192,11 @@ struct EthernetConnectionInfo : public WfNetworkConnectionInfo return "Ethernet - " + connection_name; } + std::string get_strength_str() + { + return "excellent"; + } + virtual int get_connection_strength() { return 100; @@ -220,10 +230,7 @@ void WayfireNetworkInfo::update_icon() { auto icon_name = info->get_icon_name( get_connection_state(active_connection_proxy)); - WfIconLoadOptions options; - options.invert = icon_invert_opt; - options.user_scale = icon.get_scale_factor(); - set_image_icon(icon, icon_name, icon_size_opt, options); + icon.set_from_icon_name(icon_name); } struct status_color @@ -271,12 +278,13 @@ void WayfireNetworkInfo::update_status() status.set_text(description); button.set_tooltip_text(description); + status.get_style_context()->remove_class("excellent"); + status.get_style_context()->remove_class("good"); + status.get_style_context()->remove_class("weak"); + status.get_style_context()->remove_class("none"); if (status_color_opt) { - status.override_color(get_color_for_pc(info->get_connection_strength())); - } else - { - status.unset_color(); + status.get_style_context()->add_class(info->get_strength_str()); } } @@ -357,7 +365,7 @@ void WayfireNetworkInfo::on_nm_properties_changed( bool WayfireNetworkInfo::setup_dbus() { auto cancellable = Gio::Cancellable::create(); - connection = Gio::DBus::Connection::get_sync(Gio::DBus::BUS_TYPE_SYSTEM, cancellable); + connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SYSTEM, cancellable); if (!connection) { std::cerr << "Failed to connect to dbus" << std::endl; @@ -374,8 +382,8 @@ bool WayfireNetworkInfo::setup_dbus() return false; } - nm_proxy->signal_properties_changed().connect_notify( - sigc::mem_fun(this, &WayfireNetworkInfo::on_nm_properties_changed)); + nm_proxy->signal_properties_changed().connect( + sigc::mem_fun(*this, &WayfireNetworkInfo::on_nm_properties_changed)); return true; } @@ -391,7 +399,7 @@ void WayfireNetworkInfo::on_click() } } -void WayfireNetworkInfo::init(Gtk::HBox *container) +void WayfireNetworkInfo::init(Gtk::Box *container) { if (!setup_dbus()) { @@ -403,20 +411,22 @@ void WayfireNetworkInfo::init(Gtk::HBox *container) style->add_class("flat"); style->add_class("network"); - container->add(button); - button.add(button_content); + container->append(button); + button.set_child(button_content); button.get_style_context()->add_class("flat"); - button.signal_clicked().connect_notify( - sigc::mem_fun(this, &WayfireNetworkInfo::on_click)); + button.signal_clicked().connect( + sigc::mem_fun(*this, &WayfireNetworkInfo::on_click)); - button_content.set_valign(Gtk::ALIGN_CENTER); - button_content.pack_start(icon, Gtk::PACK_SHRINK); - button_content.pack_start(status, Gtk::PACK_SHRINK); + button_content.set_valign(Gtk::Align::CENTER); + button_content.append(icon); + button_content.append(status); + button_content.set_spacing(6); - icon.set_valign(Gtk::ALIGN_CENTER); + icon.set_valign(Gtk::Align::CENTER); icon.property_scale_factor().signal_changed().connect( - sigc::mem_fun(this, &WayfireNetworkInfo::update_icon)); + sigc::mem_fun(*this, &WayfireNetworkInfo::update_icon)); + icon.get_style_context()->add_class("network-icon"); update_active_connection(); handle_config_reload(); @@ -424,15 +434,6 @@ void WayfireNetworkInfo::init(Gtk::HBox *container) void WayfireNetworkInfo::handle_config_reload() { - if ((std::string)status_font_opt == "default") - { - status.unset_font(); - } else - { - status.override_font( - Pango::FontDescription((std::string)status_font_opt)); - } - if (status_opt.value() == NETWORK_STATUS_ICON) { if (status.get_parent()) @@ -443,8 +444,7 @@ void WayfireNetworkInfo::handle_config_reload() { if (!status.get_parent()) { - button_content.pack_start(status); - button_content.show_all(); + button_content.append(status); } } diff --git a/src/panel/widgets/network.hpp b/src/panel/widgets/network.hpp index 7fb96f21..1ef806ef 100644 --- a/src/panel/widgets/network.hpp +++ b/src/panel/widgets/network.hpp @@ -39,6 +39,7 @@ struct WfNetworkConnectionInfo virtual std::string get_icon_name(WfConnectionState state) = 0; virtual int get_connection_strength() = 0; virtual std::string get_ip() = 0; + virtual std::string get_strength_str() = 0; virtual ~WfNetworkConnectionInfo() {} @@ -56,14 +57,12 @@ class WayfireNetworkInfo : public WayfireWidget std::unique_ptr info; Gtk::Button button; - Gtk::HBox button_content; + Gtk::Box button_content; Gtk::Image icon; Gtk::Label status; bool enabled = true; WfOption status_opt{"panel/network_status"}; - WfOption icon_size_opt{"panel/network_icon_size"}; - WfOption icon_invert_opt{"panel/network_icon_invert_color"}; WfOption status_color_opt{"panel/network_status_use_color"}; WfOption status_font_opt{"panel/network_status_font"}; WfOption click_command_opt{"panel/network_onclick_command"}; @@ -79,7 +78,7 @@ class WayfireNetworkInfo : public WayfireWidget void update_icon(); void update_status(); - void init(Gtk::HBox *container); + void init(Gtk::Box *container); void handle_config_reload(); virtual ~WayfireNetworkInfo(); }; diff --git a/src/panel/widgets/notifications/daemon.cpp b/src/panel/widgets/notifications/daemon.cpp index 3f9e7c29..cf7199b0 100644 --- a/src/panel/widgets/notifications/daemon.cpp +++ b/src/panel/widgets/notifications/daemon.cpp @@ -150,9 +150,9 @@ std::shared_ptr Daemon::Instance() } Daemon::Daemon() : - owner_id(Gio::DBus::own_name(Gio::DBus::BUS_TYPE_SESSION, FDN_NAME, - sigc::mem_fun(this, &Daemon::on_bus_acquired), - {}, {}, Gio::DBus::BUS_NAME_OWNER_FLAGS_REPLACE)) + owner_id(Gio::DBus::own_name(Gio::DBus::BusType::SESSION, FDN_NAME, + sigc::mem_fun(*this, &Daemon::on_bus_acquired), + {}, {}, Gio::DBus::BusNameOwnerFlags::REPLACE)) {} Daemon::~Daemon() diff --git a/src/panel/widgets/notifications/daemon.hpp b/src/panel/widgets/notifications/daemon.hpp index 5be584ba..8496de72 100644 --- a/src/panel/widgets/notifications/daemon.hpp +++ b/src/panel/widgets/notifications/daemon.hpp @@ -72,7 +72,8 @@ class Daemon notification_signal signal_notification_replaced; notification_signal signal_notification_closed; - const Gio::DBus::InterfaceVTable interface_vtable{sigc::mem_fun(this, &Daemon::on_interface_method_call)}; + const Gio::DBus::InterfaceVTable interface_vtable{sigc::mem_fun(*this, + &Daemon::on_interface_method_call)}; Daemon(); diff --git a/src/panel/widgets/notifications/notification-center.cpp b/src/panel/widgets/notifications/notification-center.cpp index d91bdd1f..9b6940a0 100644 --- a/src/panel/widgets/notifications/notification-center.cpp +++ b/src/panel/widgets/notifications/notification-center.cpp @@ -1,39 +1,41 @@ #include "notification-center.hpp" #include +#include #include #include "single-notification.hpp" -void WayfireNotificationCenter::init(Gtk::HBox *container) +void WayfireNotificationCenter::init(Gtk::Box *container) { button = std::make_unique("panel"); button->get_style_context()->add_class("notification-center"); updateIcon(); - button->add(icon); - container->add(*button); - button->show_all(); + button->set_child(icon); + container->append(*button); auto *popover = button->get_popover(); popover->set_size_request(WIDTH, HEIGHT); popover->get_style_context()->add_class("notification-popover"); - vbox.set_valign(Gtk::ALIGN_START); - scrolled_window.add(vbox); - scrolled_window.show_all(); - popover->add(scrolled_window); + vbox.set_valign(Gtk::Align::START); + vbox.set_orientation(Gtk::Orientation::VERTICAL); + scrolled_window.set_child(vbox); + popover->set_child(scrolled_window); button->set_tooltip_text("Middle click to toggle DND mode."); - button->signal_button_press_event().connect_notify([=] (GdkEventButton *ev) + + auto click_gesture = Gtk::GestureClick::create(); + click_gesture->set_button(2); + click_gesture->signal_pressed().connect([=] (int count, double x, double y) { - if (ev->button == 2) - { - dnd_enabled = !dnd_enabled; - updateIcon(); - } + dnd_enabled = !dnd_enabled; + updateIcon(); + click_gesture->set_state(Gtk::EventSequenceState::CLAIMED); }); + button->add_controller(click_gesture); for (const auto & [id, _] : daemon->getNotifications()) { @@ -57,8 +59,7 @@ void WayfireNotificationCenter::newNotification(Notification::id_type id, bool s g_assert(notification_widgets.count(id) == 0); notification_widgets.insert({id, std::make_unique(notification)}); auto & widget = notification_widgets.at(id); - vbox.pack_end(*widget); - vbox.show_all(); + vbox.append(*widget); widget->set_reveal_child(); if (show_popup && !dnd_enabled || (show_critical_in_dnd && (notification.hints.urgency == 2))) { @@ -115,9 +116,9 @@ void WayfireNotificationCenter::updateIcon() { if (dnd_enabled) { - set_image_icon(icon, "notifications-disabled", icon_size); + icon.set_from_icon_name("notifications-disabled"); } else { - set_image_icon(icon, "notifications", icon_size); + icon.set_from_icon_name("notifications"); } } diff --git a/src/panel/widgets/notifications/notification-center.hpp b/src/panel/widgets/notifications/notification-center.hpp index d8106539..f6944aad 100644 --- a/src/panel/widgets/notifications/notification-center.hpp +++ b/src/panel/widgets/notifications/notification-center.hpp @@ -21,7 +21,7 @@ class WayfireNotificationCenter : public WayfireWidget Gtk::Image icon; std::unique_ptr button; Gtk::ScrolledWindow scrolled_window; - Gtk::VBox vbox; + Gtk::Box vbox; std::map> notification_widgets = {}; @@ -33,11 +33,10 @@ class WayfireNotificationCenter : public WayfireWidget sigc::connection popover_timeout; WfOption timeout{"panel/notifications_autohide_timeout"}; WfOption show_critical_in_dnd{"panel/notifications_critical_in_dnd"}; - WfOption icon_size{"panel/notifications_icon_size"}; bool dnd_enabled = false; public: - void init(Gtk::HBox *container) override; + void init(Gtk::Box *container) override; ~WayfireNotificationCenter() override { notification_new_conn.disconnect(); diff --git a/src/panel/widgets/notifications/notification-info.cpp b/src/panel/widgets/notifications/notification-info.cpp index 0e304e97..4048d15d 100644 --- a/src/panel/widgets/notifications/notification-info.cpp +++ b/src/panel/widgets/notifications/notification-info.cpp @@ -1,6 +1,8 @@ #include "notification-info.hpp" #include +#include +#include namespace { @@ -46,7 +48,7 @@ Glib::RefPtr pixbufFromVariant(const Glib::VariantBase & variant) auto *data_ptr = new auto(std::move(data)); return Gdk::Pixbuf::create_from_data(data_ptr->data(), - Gdk::COLORSPACE_RGB, has_alpha, bits_per_sample, width, height, + Gdk::Colorspace::RGB, has_alpha, bits_per_sample, width, height, rowstride, [data_ptr] (auto*) { delete data_ptr; }); } } // namespace diff --git a/src/panel/widgets/notifications/notification-info.hpp b/src/panel/widgets/notifications/notification-info.hpp index abe5f797..17e3ff9f 100644 --- a/src/panel/widgets/notifications/notification-info.hpp +++ b/src/panel/widgets/notifications/notification-info.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include diff --git a/src/panel/widgets/notifications/single-notification.cpp b/src/panel/widgets/notifications/single-notification.cpp index 20d054b1..fbb5fb0b 100644 --- a/src/panel/widgets/notifications/single-notification.cpp +++ b/src/panel/widgets/notifications/single-notification.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include @@ -49,16 +49,10 @@ WfSingleNotification::WfSingleNotification(const Notification & notification) { if (is_file_uri(notification.app_icon)) { - auto file_name = path_from_uri(notification.app_icon); - int height = Gtk::IconSize(Gtk::ICON_SIZE_LARGE_TOOLBAR); - auto pixbuf = load_icon_pixbuf_safe(file_name, height); - app_icon.set(pixbuf); - } else if (Gtk::IconTheme::get_default()->has_icon(notification.app_icon)) - { - app_icon.set_from_icon_name(notification.app_icon, Gtk::ICON_SIZE_LARGE_TOOLBAR); + app_icon.set(path_from_uri(notification.app_icon)); } else { - app_icon.set_from_icon_name("dialog-information", Gtk::ICON_SIZE_LARGE_TOOLBAR); + app_icon.set_from_icon_name(notification.app_icon); } get_style_context()->add_class("notification"); @@ -66,13 +60,14 @@ WfSingleNotification::WfSingleNotification(const Notification & notification) app_icon.get_style_context()->add_class("app-icon"); top_bar.get_style_context()->add_class("top-bar"); - top_bar.pack_start(app_icon, false, true); + top_bar.append(app_icon); app_name.set_label(notification.app_name); - app_name.set_halign(Gtk::ALIGN_START); - app_name.set_ellipsize(Pango::ELLIPSIZE_END); + app_name.set_halign(Gtk::Align::START); + app_name.set_ellipsize(Pango::EllipsizeMode::END); app_name.get_style_context()->add_class("app-name"); - top_bar.pack_start(app_name); + top_bar.append(app_name); + time_label.set_sensitive(false); time_label.set_label(format_recv_time(notification.additional_info.recv_time)); @@ -85,31 +80,23 @@ WfSingleNotification::WfSingleNotification(const Notification & notification) }, // updating once a day doesn't work with system suspending/hybernating 10000, Glib::PRIORITY_LOW); - top_bar.pack_start(time_label, false, true); + top_bar.append(time_label); - close_image.set_from_icon_name("window-close", Gtk::ICON_SIZE_LARGE_TOOLBAR); - close_button.add(close_image); + close_image.set_from_icon_name("window-close"); + close_button.set_child(close_image); close_button.get_style_context()->add_class("flat"); close_button.get_style_context()->add_class("close"); close_button.signal_clicked().connect( [=] { Daemon::Instance()->closeNotification(notification.id, Daemon::CloseReason::Dismissed); }); - top_bar.pack_start(close_button, false, true); + top_bar.set_spacing(5); - child.add(top_bar); + child.append(top_bar); - Gtk::IconSize body_image_size = Gtk::ICON_SIZE_DIALOG; if (notification.hints.image_data) { int width; int height; - Gtk::IconSize::lookup(body_image_size, width, height); - auto image_pixbuf = notification.hints.image_data; - if (image_pixbuf->get_width() > width) - { - image_pixbuf = image_pixbuf->scale_simple(width, - width * image_pixbuf->get_height() / image_pixbuf->get_width(), Gdk::INTERP_BILINEAR); - } image.set(image_pixbuf); } else if (!notification.hints.image_path.empty()) @@ -119,16 +106,16 @@ WfSingleNotification::WfSingleNotification(const Notification & notification) image.set(path_from_uri(notification.hints.image_path)); } else { - image.set_from_icon_name(notification.hints.image_path, body_image_size); + image.set_from_icon_name(notification.hints.image_path); } } content.get_style_context()->add_class("notification-contents"); - content.pack_end(image); + content.append(image); - text.set_halign(Gtk::ALIGN_START); - text.set_line_wrap(); - text.set_line_wrap_mode(Pango::WRAP_CHAR); + text.set_halign(Gtk::Align::START); + text.set_wrap(); + text.set_wrap_mode(Pango::WrapMode::CHAR); if (notification.body.empty()) { text.set_markup(notification.summary); @@ -138,9 +125,9 @@ WfSingleNotification::WfSingleNotification(const Notification & notification) text.set_markup("" + notification.summary + "" + "\n" + notification.body); } - content.pack_start(text); + content.append(text); - child.add(content); + child.append(content); actions.get_style_context()->add_class("actions"); @@ -153,33 +140,32 @@ WfSingleNotification::WfSingleNotification(const Notification & notification) auto action_button = Glib::RefPtr(new Gtk::Button(notification.actions[i + 1])); action_button->signal_clicked().connect( [id = notification.id, action_key] { Daemon::Instance()->invokeAction(id, action_key); }); - actions.add(*action_button.get()); + actions.append(*action_button.get()); } else { - default_action_ev_box.signal_button_press_event().connect( - [id = notification.id, action_key] (GdkEventButton *ev) + auto click_gesture = Gtk::GestureClick::create(); + click_gesture->signal_pressed().connect( + [id = notification.id, action_key, click_gesture] (int count, double x, double y) { - if (ev->button == GDK_BUTTON_PRIMARY) - { - Daemon::Instance()->invokeAction(id, action_key); - return false; - } - - return true; + Daemon::Instance()->invokeAction(id, action_key); + click_gesture->set_state(Gtk::EventSequenceState::CLAIMED); }); + default_action_ev_box.add_controller(click_gesture); } } if (!actions.get_children().empty()) { actions.set_homogeneous(); - child.add(actions); + child.append(actions); } } - default_action_ev_box.add(child); - add(default_action_ev_box); - set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP); + default_action_ev_box.set_child(child); + outer_box.append(default_action_ev_box); + outer_box.append(close_button); + set_child(outer_box); + set_transition_type(Gtk::RevealerTransitionType::SLIDE_UP); } WfSingleNotification::~WfSingleNotification() diff --git a/src/panel/widgets/notifications/single-notification.hpp b/src/panel/widgets/notifications/single-notification.hpp index 28f35bf8..4f481aea 100644 --- a/src/panel/widgets/notifications/single-notification.hpp +++ b/src/panel/widgets/notifications/single-notification.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -17,13 +16,14 @@ class WfSingleNotification : public Gtk::Revealer static const int WIDTH = 300; private: - Gtk::EventBox default_action_ev_box; + Gtk::Box outer_box; + Gtk::Button default_action_ev_box; /// The revealer's child, containing all other widgets - Gtk::VBox child; + Gtk::Box child; - Gtk::HBox top_bar; - Gtk::HBox content; + Gtk::Box top_bar; + Gtk::Box content; Gtk::Image app_icon; Gtk::Label app_name; diff --git a/src/panel/widgets/separator.cpp b/src/panel/widgets/separator.cpp index 88fdc6e6..8f8636b9 100644 --- a/src/panel/widgets/separator.cpp +++ b/src/panel/widgets/separator.cpp @@ -7,9 +7,8 @@ WayfireSeparator::WayfireSeparator(int pixels) separator.set_margin_end(half); } -void WayfireSeparator::init(Gtk::HBox *container) +void WayfireSeparator::init(Gtk::Box *container) { separator.get_style_context()->add_class("separator"); - container->pack_start(separator); - separator.show_all(); + container->append(separator); } diff --git a/src/panel/widgets/separator.hpp b/src/panel/widgets/separator.hpp index 8f735192..74b615bb 100644 --- a/src/panel/widgets/separator.hpp +++ b/src/panel/widgets/separator.hpp @@ -11,7 +11,7 @@ class WayfireSeparator : public WayfireWidget public: WayfireSeparator(int pixels); - virtual void init(Gtk::HBox *container); + virtual void init(Gtk::Box *container); virtual ~WayfireSeparator() {} }; diff --git a/src/panel/widgets/spacing.cpp b/src/panel/widgets/spacing.cpp index c91aaa4a..75371ce7 100644 --- a/src/panel/widgets/spacing.cpp +++ b/src/panel/widgets/spacing.cpp @@ -5,9 +5,8 @@ WayfireSpacing::WayfireSpacing(int pixels) box.set_size_request(pixels, 1); } -void WayfireSpacing::init(Gtk::HBox *container) +void WayfireSpacing::init(Gtk::Box *container) { box.get_style_context()->add_class("spacing"); - container->pack_start(box); - box.show_all(); + container->append(box); } diff --git a/src/panel/widgets/spacing.hpp b/src/panel/widgets/spacing.hpp index 193d19e3..0c2e0915 100644 --- a/src/panel/widgets/spacing.hpp +++ b/src/panel/widgets/spacing.hpp @@ -5,12 +5,12 @@ class WayfireSpacing : public WayfireWidget { - Gtk::HBox box; + Gtk::Box box; public: WayfireSpacing(int pixels); - virtual void init(Gtk::HBox *container); + virtual void init(Gtk::Box *container); virtual ~WayfireSpacing() {} }; diff --git a/src/panel/widgets/tray/dbusmenu.cpp b/src/panel/widgets/tray/dbusmenu.cpp new file mode 100644 index 00000000..ef6bc3de --- /dev/null +++ b/src/panel/widgets/tray/dbusmenu.cpp @@ -0,0 +1,221 @@ +#include "dbusmenu.hpp" + +#include +#include + +static void menu_updated(DbusmenuMenuitem *item, gpointer user_data) +{ + DbusMenuModel *menu = (DbusMenuModel*)user_data; + if (menu != nullptr) + { + menu->layout_updated(item); + } +} + +DbusMenuModel::DbusMenuModel() +{ + actions = Gio::SimpleActionGroup::create(); +} + +DbusMenuModel::~DbusMenuModel() +{ + if (client) + { + g_object_unref(client); + } +} + +type_signal_action_group DbusMenuModel::signal_action_group() +{ + return signal; +} + +void DbusMenuModel::connect(const Glib::ustring & dbus_name, const Glib::ustring & menu_path, + const Glib::ustring & pref) +{ + prefix = pref; + client = dbusmenu_client_new(dbus_name.c_str(), menu_path.c_str()); + auto gclient = G_OBJECT(client); + g_signal_connect( + gclient, + DBUSMENU_CLIENT_SIGNAL_LAYOUT_UPDATED, + G_CALLBACK(menu_updated), + this); + auto root = dbusmenu_client_get_root(client); + reconstitute(root); +} + +void DbusMenuModel::reconstitute(DbusmenuMenuitem *rootItem) +{ + remove_all(); + auto action_list = actions->list_actions(); + for (auto action_name : action_list) + { + actions->remove_action(action_name); + } + + iterate_children(this, rootItem, 0); + + signal.emit(); // Tell the SNI that the menu actions have changed. Seemed necessary, as if it was taking a + // copy not a ref +} + +int DbusMenuModel::iterate_children(Gio::Menu *parent_menu, DbusmenuMenuitem *parent, int stupid_count) +{ + if (DBUSMENU_IS_MENUITEM(parent)) + { + auto list = dbusmenu_menuitem_get_children(parent); + auto current_section = Gio::Menu::create(); + for (; list != NULL; list = g_list_next(list)) + { + auto child = DBUSMENU_MENUITEM(list->data); + auto label = dbusmenu_menuitem_property_get(child, DBUSMENU_MENUITEM_PROP_LABEL); + if (label == nullptr) // A null label is a separator, I think... + { + auto item = Gio::MenuItem::create(current_section); + item->set_section(current_section); + current_section = Gio::Menu::create(); + parent_menu->append_item(item); + continue; + } + + std::string menutype = ""; + std::string icon_name = ""; + std::string toggle_type = ""; + + if (dbusmenu_menuitem_property_exist(child, DBUSMENU_MENUITEM_PROP_TYPE)) + { + menutype = std::string(dbusmenu_menuitem_property_get(child, DBUSMENU_MENUITEM_PROP_TYPE)); + } + + bool enabled = dbusmenu_menuitem_property_get_bool(child, DBUSMENU_MENUITEM_PROP_ENABLED); + if (dbusmenu_menuitem_property_exist(child, DBUSMENU_MENUITEM_PROP_ICON_NAME)) + { + icon_name = dbusmenu_menuitem_property_get(child, DBUSMENU_MENUITEM_PROP_ICON_NAME); + } + + int toggle_state = dbusmenu_menuitem_property_get_int(child, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE); + if (dbusmenu_menuitem_property_exist(child, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE)) + { + toggle_type = dbusmenu_menuitem_property_get(child, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE); + } + + bool has_children = dbusmenu_menuitem_get_children(child) != NULL; + + /* #Icons are set, but from what I read they won't be shown in gtk4. + * for some reason TOGGLE_TYPE is always returning as if not set. + * So checkboxes and radios work but are never sent + */ + if (has_children) + { + auto submenu = Gio::Menu::create(); + stupid_count = iterate_children(submenu.get(), child, stupid_count); + current_section->append_submenu(label, submenu); + } else + { + auto item = Gio::MenuItem::create(label, "a"); + if (enabled) + { + auto action_name = label_to_action_name(label, stupid_count); + std::stringstream ss; + ss << prefix << "." << action_name; + std::string action_string = ss.str(); + + if (toggle_type == DBUSMENU_MENUITEM_TOGGLE_RADIO) + { + item->set_action_and_target(action_string, + Glib::wrap(g_variant_new_string(action_name.c_str()))); + + // Radio action + auto boolean_action = Gio::SimpleAction::create_radio_string(action_name, + toggle_state ? action_name : ""); + boolean_action->signal_activate().connect([=] (Glib::VariantBase vb) + { + GVariant *data = g_variant_new_int32(0); + dbusmenu_menuitem_handle_event(child, "clicked", data, 0); + }); + actions->add_action(boolean_action); + } else if (toggle_type == DBUSMENU_MENUITEM_TOGGLE_CHECK) + { + item->set_action_and_target(action_string, + Glib::wrap(g_variant_new_boolean(toggle_state))); + + // Checkbox action + auto boolean_action = Gio::SimpleAction::create_bool(action_name, toggle_state); + boolean_action->signal_activate().connect([=] (Glib::VariantBase vb) + { + GVariant *data = g_variant_new_int32(0); + dbusmenu_menuitem_handle_event(child, "clicked", data, 0); + }); + actions->add_action(boolean_action); + } else + { + item->set_action(action_string); + + // Plain actions + actions->add_action(action_name, [=] () + { + GVariant *data = g_variant_new_int32(0); + dbusmenu_menuitem_handle_event(child, "clicked", data, 0); + }); + } + } + + current_section->append_item(item); + } + + stupid_count++; + } + + auto item = Gio::MenuItem::create(current_section); + item->set_section(current_section); + current_section = Gio::Menu::create(); + parent_menu->append_item(item); + } + + return stupid_count; +} + +void DbusMenuModel::layout_updated(DbusmenuMenuitem *item) +{ + if (client == nullptr) + { + std::cerr << "Nullptr in dbusmenu client" << std::endl; + return; + } + + auto root = dbusmenu_client_get_root(client); + reconstitute(root); +} + +Glib::RefPtr DbusMenuModel::get_action_group() +{ + return actions; +} + +/* + * Strip non-alphabetical characters and add a unique numeric ID. + * Keeping alphacharacters can help with debugging but isn't strictly necessary. + * + * Watch out for this in localisations. I'm betting it's a source of many bugs to come + * + * Once we're sure nothing really stupid is happening here, we could have a letter followed by unique number. + */ +std::string DbusMenuModel::label_to_action_name(std::string label, int numeric) +{ + std::string ret = label; + transform(ret.begin(), ret.end(), ret.begin(), ::tolower); + for (int i = 0; i < (int)ret.size(); i++) + { + if ((ret[i] < 'a') || + (ret[i] > 'z')) + { + ret.erase(i, 1); + i--; + } + } + + std::stringstream stream; + stream << "a" << ret << numeric; + return stream.str(); +} diff --git a/src/panel/widgets/tray/dbusmenu.hpp b/src/panel/widgets/tray/dbusmenu.hpp new file mode 100644 index 00000000..474d096f --- /dev/null +++ b/src/panel/widgets/tray/dbusmenu.hpp @@ -0,0 +1,39 @@ +#ifndef TRAY_DBUSMENU_HPP +#define TRAY_DBUSMENU_HPP + +#include +#include +#include +#include + +#include +#include + +#include + +using type_signal_action_group = sigc::signal; + +class DbusMenuModel : public Gio::Menu +{ + DbusmenuClient *client; + std::string prefix; + type_signal_action_group signal; + + std::string label_to_action_name(std::string, int counter); + + public: + explicit DbusMenuModel(); + ~DbusMenuModel(); + void connect(const Glib::ustring & service, const Glib::ustring & menu_path, + const Glib::ustring & prefix); + void layout_updated(DbusmenuMenuitem *item); + void reconstitute(DbusmenuMenuitem *item); + int iterate_children(Gio::Menu *parent_menu, DbusmenuMenuitem *parent, int counter); + Glib::RefPtr get_action_group(); + + Glib::RefPtr actions; + + type_signal_action_group signal_action_group(); +}; + +#endif diff --git a/src/panel/widgets/tray/host.cpp b/src/panel/widgets/tray/host.cpp index 105f202e..80c4489e 100644 --- a/src/panel/widgets/tray/host.cpp +++ b/src/panel/widgets/tray/host.cpp @@ -3,7 +3,7 @@ #include "watcher.hpp" StatusNotifierHost::StatusNotifierHost(WayfireStatusNotifier *tray) : - dbus_name_id(Gio::DBus::own_name(Gio::DBus::BUS_TYPE_SESSION, + dbus_name_id(Gio::DBus::own_name(Gio::DBus::BusType::SESSION, "org.kde.StatusNotifierHost-" + std::to_string(getpid()) + "-" + std::to_string(++hosts_counter), sigc::mem_fun(*this, &StatusNotifierHost::on_bus_acquired))), diff --git a/src/panel/widgets/tray/item.cpp b/src/panel/widgets/tray/item.cpp index 98c5a1d3..f6486df1 100644 --- a/src/panel/widgets/tray/item.cpp +++ b/src/panel/widgets/tray/item.cpp @@ -4,8 +4,13 @@ #include #include +#include +#include +#include +#include -#include +#include +#include static std::pair name_and_obj_path(const Glib::ustring & service) { @@ -41,18 +46,19 @@ static Glib::RefPtr extract_pixbuf(IconData && pixbuf_data) auto *data_ptr = new auto(std::move(data)); return Gdk::Pixbuf::create_from_data( - data_ptr->data(), Gdk::Colorspace::COLORSPACE_RGB, true, 8, width, height, + data_ptr->data(), Gdk::Colorspace::RGB, true, 8, width, height, 4 * width, [data_ptr] (auto*) { delete data_ptr; }); } StatusNotifierItem::StatusNotifierItem(const Glib::ustring & service) { - set_image(icon); + set_child(icon); + menu = std::make_shared(); const auto & [name, path] = name_and_obj_path(service); dbus_name = name; Gio::DBus::Proxy::create_for_bus( - Gio::DBus::BUS_TYPE_SESSION, name, path, "org.kde.StatusNotifierItem", + Gio::DBus::BusType::SESSION, name, path, "org.kde.StatusNotifierItem", [this] (const Glib::RefPtr & result) { item_proxy = Gio::DBus::Proxy::create_for_bus_finish(result); @@ -63,123 +69,87 @@ StatusNotifierItem::StatusNotifierItem(const Glib::ustring & service) }); } +StatusNotifierItem::~StatusNotifierItem() +{ + gtk_widget_unparent(GTK_WIDGET(popover.gobj())); +} + void StatusNotifierItem::init_widget() { update_icon(); - icon_size.set_callback([this] { update_icon(); }); setup_tooltip(); init_menu(); auto style = get_style_context(); style->add_class("tray-button"); style->add_class("flat"); + gtk_widget_set_parent(GTK_WIDGET(popover.gobj()), GTK_WIDGET(gobj())); - signal_clicked().connect([this] () -> void + auto scroll_gesture = Gtk::EventControllerScroll::create(); + scroll_gesture->set_flags(Gtk::EventControllerScroll::Flags::BOTH_AXES); + scroll_gesture->signal_scroll().connect([=] (double dx, double dy) -> bool { + using ScrollParams = Glib::Variant>; + item_proxy->call("Scroll", ScrollParams::create({dx, "horizontal"})); + item_proxy->call("Scroll", ScrollParams::create({dy, "vertical"})); + return true; + }, true); + + auto click_gesture = Gtk::GestureClick::create(); + click_gesture->set_button(0); + click_gesture->signal_pressed().connect([=] (int count, double x, double y) + { + int butt = click_gesture->get_current_button(); const auto ev_coords = Glib::Variant>::create({0, 0}); - if (get_item_property("ItemIsMenu", true)) + + const int primary_click = 1; + const int secondary_click = menu_on_middle_click ? 2 : 3; + const int tertiary_click = menu_on_middle_click ? 3 : 2; + if (butt == primary_click) { - if (menu) + if (get_item_property("ItemIsMenu", true)) { - /* Under all tests I tried this places sensibly */ - menu->popup_at_widget(&icon, Gdk::GRAVITY_NORTH_EAST, Gdk::GRAVITY_SOUTH_EAST, NULL); + if (has_menu) + { + popover.popup(); + } else + { + item_proxy->call("ContextMenu", ev_coords); + } } else { - item_proxy->call("ContextMenu", ev_coords); + item_proxy->call("Activate", ev_coords); } - } else - { - item_proxy->call("Activate", ev_coords); - } - }); - - signal_button_press_event().connect([this] (GdkEventButton *ev) -> bool - { - if (ev->button == GDK_BUTTON_PRIMARY) - { - return true; - } - - const auto ev_coords = Glib::Variant>::create({ev->x, ev->y}); - const guint menu_btn = menu_on_middle_click ? GDK_BUTTON_MIDDLE : GDK_BUTTON_SECONDARY; - const guint secondary_activate_btn = menu_on_middle_click ? GDK_BUTTON_SECONDARY : GDK_BUTTON_MIDDLE; - if (get_item_property("ItemIsMenu", true) || (ev->button == menu_btn)) + } else if (butt == secondary_click) { - if (menu) + if (has_menu) { - menu->popup_at_widget(&icon, Gdk::GRAVITY_NORTH_EAST, Gdk::GRAVITY_SOUTH_EAST, NULL); + popover.popup(); } else { item_proxy->call("ContextMenu", ev_coords); } - } else if (ev->button == secondary_activate_btn) + } else if (butt == tertiary_click) { item_proxy->call("SecondaryActivate", ev_coords); - } - - return true; - }); - - add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); - - signal_scroll_event().connect([this] (GdkEventScroll *ev) - { - int dx = 0; - int dy = 0; - switch (ev->direction) - { - case GDK_SCROLL_UP: - dy = -1; - break; - - case GDK_SCROLL_DOWN: - dy = 1; - break; - - case GDK_SCROLL_LEFT: - dx = -1; - break; - - case GDK_SCROLL_RIGHT: - dx = 1; - break; - - case GDK_SCROLL_SMOOTH: - distance_scrolled_x += ev->delta_x; - distance_scrolled_y += ev->delta_y; - if (std::abs(distance_scrolled_x) >= smooth_scolling_threshold) - { - dx = std::lround(distance_scrolled_x); - distance_scrolled_x = 0; - } - - if (std::abs(distance_scrolled_y) >= smooth_scolling_threshold) - { - dy = std::lround(distance_scrolled_y); - distance_scrolled_y = 0; - } - - break; - } - - using ScrollParams = Glib::Variant>; - if (dx != 0) - { - item_proxy->call("Scroll", ScrollParams::create({dx, "hozirontal"})); - } - - if (dy != 0) + } else { - item_proxy->call("Scroll", ScrollParams::create({dy, "vertical"})); + // Don't claim other buttons + click_gesture->set_state(Gtk::EventSequenceState::DENIED); + return; } - return true; + click_gesture->set_state(Gtk::EventSequenceState::CLAIMED); + return; }); + + add_controller(scroll_gesture); + add_controller(click_gesture); } void StatusNotifierItem::setup_tooltip() { set_has_tooltip(); - signal_query_tooltip().connect([this] (int, int, bool, const Glib::RefPtr & tooltip) + signal_query_tooltip().connect([this] (int, int, bool, const std::shared_ptr & tooltip) { auto [tooltip_icon_name, tooltip_icon_data, tooltip_title, tooltip_text] = get_item_property>("ToolTip"); @@ -191,22 +161,19 @@ void StatusNotifierItem::setup_tooltip() get_item_property("Title"); const auto pixbuf = extract_pixbuf(std::move(tooltip_icon_data)); - - bool icon_shown = true; - if (icon_theme->has_icon(tooltip_icon_name)) - { - tooltip->set_icon_from_icon_name(tooltip_icon_name, Gtk::ICON_SIZE_LARGE_TOOLBAR); - } else if (pixbuf) + bool icon_shown = false; + if (pixbuf) { tooltip->set_icon(pixbuf); + icon_shown = true; } else { - icon_shown = false; + // tooltip->set_icon_from_name(tooltip_icon_name); } tooltip->set_markup(tooltip_label_text); return icon_shown || !tooltip_label_text.empty(); - }); + }, true); } void StatusNotifierItem::update_icon() @@ -218,38 +185,50 @@ void StatusNotifierItem::update_icon() icon_theme->add_resource_path(icon_theme_path); } else { - icon_theme = Gtk::IconTheme::get_default(); + icon_theme = Gtk::IconTheme::get_for_display(get_display()); } const Glib::ustring icon_type_name = get_item_property("Status") == "NeedsAttention" ? "AttentionIcon" : "Icon"; + const bool hide = get_item_property("Status") == "Passive"; const auto icon_name = get_item_property(icon_type_name + "Name"); const auto pixmap_data = extract_pixbuf(get_item_property(icon_type_name + "Pixmap")); - if (icon_theme->lookup_icon(icon_name, icon_size)) + if (pixmap_data) { - set_image_icon(icon, icon_name, icon_size, {}, icon_theme); - } else if (pixmap_data) + icon.set(pixmap_data); + } else { - icon.set(pixmap_data->scale_simple(icon_size, icon_size, Gdk::INTERP_BILINEAR)); + icon.set_from_icon_name(icon_name); + } + + if (hide) + { + this->hide(); + } else + { + this->show(); } } void StatusNotifierItem::init_menu() { - const auto menu_path = get_item_property("Menu"); + menu_path = get_item_property("Menu"); + if (menu_path.empty()) { return; } - auto *raw_menu = dbusmenu_gtkmenu_new((gchar*)dbus_name.data(), (gchar*)menu_path.data()); - if (raw_menu == nullptr) - { - return; - } + auto action_prefix = dbus_name_as_prefix(); - menu = std::move(*Glib::wrap(GTK_MENU(raw_menu))); - menu->attach_to_widget(*this); + menu->connect(dbus_name, menu_path, action_prefix); + menu->signal_action_group().connect([=] () + { + auto action_group = menu->get_action_group(); + insert_action_group(action_prefix, action_group); + }); + popover.set_menu_model(menu); + has_menu = true; } void StatusNotifierItem::handle_signal(const Glib::ustring & signal, @@ -281,7 +260,7 @@ void StatusNotifierItem::handle_signal(const Glib::ustring & signal, } void StatusNotifierItem::fetch_property(const Glib::ustring & property_name, - const sigc::slot & callback) + const sigc::slot & callback) { item_proxy->call( "org.freedesktop.DBus.Properties.Get", @@ -300,3 +279,36 @@ void StatusNotifierItem::fetch_property(const Glib::ustring & property_name, Glib::Variant>::create({"org.kde.StatusNotifierItem", property_name})); } + +std::string StatusNotifierItem::get_unique_name() +{ + std::stringstream ss; + ss << dbus_name << "_" << menu_path; + return ss.str(); +} + +/* + * DBUS names are in the format of :1.61 + * I have no idea what this means, I frankly don't care, but I need a way to generate an acceptable action + * group name from this + * such that it always gets the same unique output for the same input + */ + +const std::string CHARS_IN = ":0123456789."; +const std::string CHARS_OUT = "zabcdefghijk"; +std::string StatusNotifierItem::dbus_name_as_prefix() +{ + std::unordered_map map; + for (int i = 0; i < (int)CHARS_IN.length(); i++) + { + map[CHARS_IN[i]] = CHARS_OUT[i]; + } + + std::stringstream ss; + for (int i = 0; i < (int)dbus_name.length(); i++) + { + ss << map[dbus_name[i]]; + } + + return ss.str(); +} diff --git a/src/panel/widgets/tray/item.hpp b/src/panel/widgets/tray/item.hpp index 05da26df..f9689487 100644 --- a/src/panel/widgets/tray/item.hpp +++ b/src/panel/widgets/tray/item.hpp @@ -2,33 +2,39 @@ #define TRAY_ITEM_HPP #include -#include -#include -#include -#include -#include +#include #include +#include +#include "dbusmenu.hpp" +#include +#include #include class StatusNotifierItem : public Gtk::Button { + guint menu_handler_id; + WfOption smooth_scolling_threshold{"panel/tray_smooth_scrolling_threshold"}; - WfOption icon_size{"panel/tray_icon_size"}; WfOption menu_on_middle_click{"panel/tray_menu_on_middle_click"}; - Glib::ustring dbus_name; + Glib::ustring dbus_name, menu_path; Glib::RefPtr item_proxy; + Gtk::PopoverMenu popover; + std::shared_ptr menu; + + bool has_menu = false; + Gtk::Image icon; - std::optional menu; gdouble distance_scrolled_x = 0; gdouble distance_scrolled_y = 0; - Glib::RefPtr icon_theme = Gtk::IconTheme::get_default(); + + Glib::RefPtr icon_theme; template T get_item_property(const Glib::ustring & name, const T & default_value = {}) const @@ -48,10 +54,15 @@ class StatusNotifierItem : public Gtk::Button void update_icon(); void setup_tooltip(); - void fetch_property(const Glib::ustring & property_name, const sigc::slot & callback = {}); + void fetch_property(const Glib::ustring & property_name, const sigc::slot & callback = {}); + + std::string dbus_name_as_prefix(); public: + void menu_update(DbusmenuClient *client); explicit StatusNotifierItem(const Glib::ustring & service); + ~StatusNotifierItem(); + std::string get_unique_name(); }; #endif diff --git a/src/panel/widgets/tray/tray.cpp b/src/panel/widgets/tray/tray.cpp index 6bfc181f..bf9a0c3a 100644 --- a/src/panel/widgets/tray/tray.cpp +++ b/src/panel/widgets/tray/tray.cpp @@ -1,9 +1,10 @@ #include "tray.hpp" -void WayfireStatusNotifier::init(Gtk::HBox *container) +void WayfireStatusNotifier::init(Gtk::Box *container) { icons_hbox.get_style_context()->add_class("tray"); - container->add(icons_hbox); + icons_hbox.set_spacing(5); + container->append(icons_hbox); } void WayfireStatusNotifier::add_item(const Glib::ustring & service) @@ -14,11 +15,16 @@ void WayfireStatusNotifier::add_item(const Glib::ustring & service) } items.emplace(service, service); - icons_hbox.pack_start(items.at(service)); - icons_hbox.show_all(); + icons_hbox.append(items.at(service)); } void WayfireStatusNotifier::remove_item(const Glib::ustring & service) { + if (items.count(service) == 0) + { + return; + } + + icons_hbox.remove(items.at(service)); items.erase(service); } diff --git a/src/panel/widgets/tray/tray.hpp b/src/panel/widgets/tray/tray.hpp index d8e9f9d5..0fbfb881 100644 --- a/src/panel/widgets/tray/tray.hpp +++ b/src/panel/widgets/tray/tray.hpp @@ -11,11 +11,11 @@ class WayfireStatusNotifier : public WayfireWidget private: StatusNotifierHost host = StatusNotifierHost(this); - Gtk::HBox icons_hbox; + Gtk::Box icons_hbox; std::map items; public: - void init(Gtk::HBox *container) override; + void init(Gtk::Box *container) override; void add_item(const Glib::ustring & service); void remove_item(const Glib::ustring & service); diff --git a/src/panel/widgets/tray/watcher.cpp b/src/panel/widgets/tray/watcher.cpp index 0e80e400..7ac87975 100644 --- a/src/panel/widgets/tray/watcher.cpp +++ b/src/panel/widgets/tray/watcher.cpp @@ -32,8 +32,8 @@ static const auto introspection_data = Gio::DBus::NodeInfo::create_for_xml( )")->lookup_interface(); Watcher::Watcher() : - dbus_name_id(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION, SNW_NAME, - sigc::mem_fun(this, &Watcher::on_bus_acquired))) + dbus_name_id(Gio::DBus::own_name(Gio::DBus::BusType::SESSION, SNW_NAME, + sigc::mem_fun(*this, &Watcher::on_bus_acquired))) {} std::shared_ptr Watcher::Launch() @@ -80,15 +80,24 @@ void Watcher::register_status_notifier_item(const Glib::RefPtr & connection, const Glib::ustring & name) { - Gio::DBus::unwatch_name(sn_items_id.at(full_obj_path)); - sn_items_id.erase(full_obj_path); - emit_signal("StatusNotifierItemUnregistered", full_obj_path); + if (sn_items_id.count(full_obj_path) == 1) + { + Gio::DBus::unwatch_name(sn_items_id.at(full_obj_path)); + sn_items_id.erase(full_obj_path); + emit_signal("StatusNotifierItemUnregistered", full_obj_path); + } })); } diff --git a/src/panel/widgets/volume.cpp b/src/panel/widgets/volume.cpp index 4535cfa2..04dcfcf0 100644 --- a/src/panel/widgets/volume.cpp +++ b/src/panel/widgets/volume.cpp @@ -1,3 +1,4 @@ +#include #include #include #include "volume.hpp" @@ -6,18 +7,7 @@ WayfireVolumeScale::WayfireVolumeScale() { - this->signal_draw().connect_notify( - [=] (const Cairo::RefPtr& ctx) - { - if (this->current_volume.running()) - { - value_changed.block(); - this->set_value(this->current_volume); - value_changed.unblock(); - } - }, true); - - value_changed = this->signal_value_changed().connect_notify([=] () + value_changed = this->signal_value_changed().connect([=] () { this->current_volume.animate(this->get_value(), this->get_value()); if (this->user_changed_callback) @@ -30,7 +20,16 @@ WayfireVolumeScale::WayfireVolumeScale() void WayfireVolumeScale::set_target_value(double value) { this->current_volume.animate(value); - this->queue_draw(); + add_tick_callback(sigc::mem_fun(*this, &WayfireVolumeScale::update_animation)); +} + +gboolean WayfireVolumeScale::update_animation(Glib::RefPtr frame_clock) +{ + value_changed.block(); + this->set_value(this->current_volume); + value_changed.unblock(); + // Once we've finished fading, stop this callback + return this->current_volume.running() ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE; } double WayfireVolumeScale::get_target_value() const @@ -80,7 +79,7 @@ void WayfireVolume::update_icon() if (gvc_stream && gvc_mixer_stream_get_is_muted(gvc_stream)) { - set_image_icon(main_image, "audio-volume-muted", icon_size); + main_image.set_from_icon_name("audio-volume-muted"); return; } @@ -92,22 +91,18 @@ void WayfireVolume::update_icon() {VOLUME_LEVEL_OOR, "audio-volume-muted"}, }; - set_image_icon(main_image, icon_name_from_state.at(current), icon_size); + main_image.set_from_icon_name(icon_name_from_state.at(current)); } bool WayfireVolume::on_popover_timeout(int timer) { - button->get_popover()->popdown(); + popover.popdown(); return false; } void WayfireVolume::check_set_popover_timeout() { popover_timeout.disconnect(); - if (this->button->is_popover_focused()) - { - return; - } popover_timeout = Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(*this, &WayfireVolume::on_popover_timeout), 0), timeout * 1000); @@ -122,39 +117,18 @@ void WayfireVolume::set_volume(pa_volume_t volume, set_volume_flags_t flags) gvc_mixer_stream_push_volume(gvc_stream); } - if ((flags & VOLUME_FLAG_SHOW_POPOVER) && - !button->get_popover()->is_visible()) - { - button->get_popover()->popup(); - } - - update_icon(); -} - -void WayfireVolume::on_volume_scroll(GdkEventScroll *event) -{ - set_volume(std::clamp(volume_scale.get_target_value() - event->delta_y * max_norm * scroll_sensitivity, - 0.0, max_norm)); - - button->grab_focus(); - check_set_popover_timeout(); -} - -void WayfireVolume::on_volume_button_press(GdkEventButton *event) -{ - if ((event->button == 2) && (event->type == GDK_BUTTON_PRESS)) + if (flags & VOLUME_FLAG_SHOW_POPOVER) { - /* Toggle mute on middle click */ - if (gvc_mixer_stream_get_is_muted(gvc_stream)) + if (!popover.is_visible()) { - gvc_mixer_stream_change_is_muted(gvc_stream, false); - gvc_mixer_stream_set_is_muted(gvc_stream, false); + popover.popup(); } else { - gvc_mixer_stream_change_is_muted(gvc_stream, true); - gvc_mixer_stream_set_is_muted(gvc_stream, true); + check_set_popover_timeout(); } } + + update_icon(); } void WayfireVolume::on_volume_changed_external() @@ -162,12 +136,7 @@ void WayfireVolume::on_volume_changed_external() auto volume = gvc_mixer_stream_get_volume(gvc_stream); if (volume != (pa_volume_t)this->volume_scale.get_target_value()) { - /* When the volume changes externally, we want to temporarily show the - * popover. However it shouldn't grab focus, because we're just displaying - * a notification. */ - button->set_keyboard_interactive(false); set_volume(volume, VOLUME_FLAG_SHOW_POPOVER); - button->set_keyboard_interactive(true); } check_set_popover_timeout(); @@ -241,38 +210,61 @@ static void default_sink_changed(GvcMixerControl *gvc_control, void WayfireVolume::on_volume_value_changed() { /* User manually changed volume */ - button->grab_focus(); set_volume(volume_scale.get_target_value()); } -void WayfireVolume::init(Gtk::HBox *container) +void WayfireVolume::init(Gtk::Box *container) { - icon_size.set_callback([=] () { update_icon(); }); - /* Setup button */ - button = std::make_unique("panel"); - auto style = button->get_style_context(); + button.signal_clicked().connect([=] + { + if (!popover.is_visible()) + { + popover.popup(); + } else + { + popover.popdown(); + } + }); + auto style = button.get_style_context(); style->add_class("volume"); style->add_class("flat"); - button->set_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK | Gdk::BUTTON_PRESS_MASK); - button->signal_scroll_event().connect_notify( - sigc::mem_fun(this, &WayfireVolume::on_volume_scroll)); - button->signal_button_press_event().connect_notify( - sigc::mem_fun(this, &WayfireVolume::on_volume_button_press)); - button->property_scale_factor().signal_changed().connect( - [=] () {update_icon(); }); + + gtk_widget_set_parent(GTK_WIDGET(popover.gobj()), GTK_WIDGET(button.gobj())); + + auto scroll_gesture = Gtk::EventControllerScroll::create(); + scroll_gesture->signal_scroll().connect([=] (double dx, double dy) + { + int change = 0; + if (scroll_gesture->get_unit() == Gdk::ScrollUnit::WHEEL) + { + // +- number of clicks. + change = dy * max_norm * scroll_sensitivity; + } else + { + // Number of pixels expected to have scrolled. usually in 100s + change = (dy / 100.0) * max_norm * scroll_sensitivity; + } + + set_volume(std::clamp(volume_scale.get_target_value() - change, + 0.0, max_norm)); + return true; + }, true); + scroll_gesture->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL); + button.add_controller(scroll_gesture); /* Setup popover */ - auto popover = button->get_popover(); - popover->add(volume_scale); - popover->set_modal(false); - popover->get_style_context()->add_class("volume-popover"); + popover.set_autohide(false); + popover.set_child(volume_scale); + // popover->set_modal(false); + popover.get_style_context()->add_class("volume-popover"); + popover.add_controller(scroll_gesture); volume_scale.set_draw_value(false); volume_scale.set_size_request(300, 0); volume_scale.set_user_changed_callback([=] () { on_volume_value_changed(); }); - volume_scale.signal_state_flags_changed().connect_notify( + volume_scale.signal_state_flags_changed().connect( [=] (Gtk::StateFlags) { check_set_popover_timeout(); }); /* Setup gvc control */ @@ -281,15 +273,25 @@ void WayfireVolume::init(Gtk::HBox *container) "default-sink-changed", G_CALLBACK(default_sink_changed), this); gvc_mixer_control_open(gvc_control); + /* Middle click toggle mute */ + auto middle_click_gesture = Gtk::GestureClick::create(); + middle_click_gesture->set_button(2); + middle_click_gesture->signal_pressed().connect([=] (int count, double x, double y) + { + bool muted = !(gvc_stream && gvc_mixer_stream_get_is_muted(gvc_stream)); + gvc_mixer_stream_change_is_muted(gvc_stream, muted); + gvc_mixer_stream_push_volume(gvc_stream); + }); + button.add_controller(middle_click_gesture); + /* Setup layout */ - container->pack_start(*button, false, false); - button->add(main_image); - button->show_all(); - volume_scale.show_all(); + container->append(button); + button.set_child(main_image); } WayfireVolume::~WayfireVolume() { + gtk_widget_unparent(GTK_WIDGET(popover.gobj())); disconnect_gvc_stream_signals(); if (notify_default_sink_changed) diff --git a/src/panel/widgets/volume.hpp b/src/panel/widgets/volume.hpp index 69f1d9c1..39d3dd25 100644 --- a/src/panel/widgets/volume.hpp +++ b/src/panel/widgets/volume.hpp @@ -28,20 +28,22 @@ class WayfireVolumeScale : public Gtk::Scale void set_target_value(double value); /** Set the callback when the user changes the scale value */ void set_user_changed_callback(std::function callback); + /** Callback to animate volume control */ + gboolean update_animation(Glib::RefPtr clock); }; class WayfireVolume : public WayfireWidget { Gtk::Image main_image; WayfireVolumeScale volume_scale; - std::unique_ptr button; + Gtk::Button button; + Gtk::Popover popover; - WfOption icon_size{"panel/volume_icon_size"}; WfOption timeout{"panel/volume_display_timeout"}; WfOption scroll_sensitivity{"panel/volume_scroll_sensitivity"}; - void on_volume_scroll(GdkEventScroll *event); - void on_volume_button_press(GdkEventButton *event); + // void on_volume_scroll(GdkEventScroll *event); + // void on_volume_button_press(GdkEventButton *event); void on_volume_value_changed(); bool on_popover_timeout(int timer); @@ -79,7 +81,7 @@ class WayfireVolume : public WayfireWidget set_volume_flags_t flags = VOLUME_FLAG_FULL); public: - void init(Gtk::HBox *container) override; + void init(Gtk::Box *container) override; virtual ~WayfireVolume(); /** Update the icon based on volume and muted state */ diff --git a/src/panel/widgets/window-list/layout.cpp b/src/panel/widgets/window-list/layout.cpp new file mode 100644 index 00000000..57924f72 --- /dev/null +++ b/src/panel/widgets/window-list/layout.cpp @@ -0,0 +1,83 @@ +#include "toplevel.hpp" +#include +#include "gtk/gtklayoutmanager.h" + +WayfireWindowListLayout::WayfireWindowListLayout(WayfireWindowList* window_list) +{ + this->window_list = window_list; +} + +void WayfireWindowListLayout::allocate_vfunc(const Gtk::Widget& widget, int width, int height, int baseline) +{ + Gtk::Widget& widget_not_const = const_cast(widget); + int child_count = widget.get_children().size(); + if (child_count <= 0) + { + return; + } + int per_child = width / child_count; + // user preference is ignored if too small + int preference = std::max(height, (int)user_size); + // At minimum use ratio of 1:1, at max use user preference + per_child = std::max(per_child, height); + per_child = std::min(per_child, preference); + + int index = 0; + auto alloc = Gtk::Allocation(); + alloc.set_height(height); + alloc.set_width(per_child); + alloc.set_y(0); + for (auto child : widget_not_const.get_children()) + { + if (child == top_widget) + { + alloc.set_x(top_x); + child->size_allocate(alloc, -1); + index++; + continue; + } + + alloc.set_x(per_child * index); + child->size_allocate(alloc, -1); + index++; + } + + for (auto& t : window_list->toplevels) + { + if (t.second) + { + t.second->send_rectangle_hint(); + } + } +} + +void WayfireWindowListLayout::measure_vfunc(const Gtk::Widget& widget, Gtk::Orientation orientation, + int for_size, int& minimum, int& natural, int& minimum_baseline, + int& natural_baseline) const +{ + // What is our preferred width? + if (orientation == Gtk::Orientation::HORIZONTAL) + { + int child_count = widget.get_children().size(); + + // Use max of user preference and ratio 1:1 + int per_child = std::max(for_size, (int)user_size); + + minimum = child_count * for_size; + natural = per_child * child_count; + minimum_baseline = -1; + natural_baseline = -1; + } else + { + minimum = 1; + natural = 1; + minimum_baseline = -1; + natural_baseline = -1; + } +} + +Gtk::SizeRequestMode WayfireWindowListLayout::get_request_mode_vfunc(const Gtk::Widget& widget) const +{ + // Declare our width is based on allocated height. + return Gtk::SizeRequestMode::WIDTH_FOR_HEIGHT; +} diff --git a/src/panel/widgets/window-list/layout.hpp b/src/panel/widgets/window-list/layout.hpp new file mode 100644 index 00000000..5bfd1f73 --- /dev/null +++ b/src/panel/widgets/window-list/layout.hpp @@ -0,0 +1,23 @@ +#include +#include "toplevel.hpp" +#include + + +class WayfireWindowList; + +class WayfireWindowListLayout : public Gtk::LayoutManager +{ + protected: + void allocate_vfunc(const Gtk::Widget& widget, int width, int height, int baseline) override; + void measure_vfunc(const Gtk::Widget& widget, Gtk::Orientation orientation, int for_size, int& minimum, + int& natural, int& minimum_baseline, int& natural_baseline) const override; + Gtk::SizeRequestMode get_request_mode_vfunc(const Gtk::Widget& widget) const override; + std::shared_ptr layout; + WfOption user_size{"panel/window_list_size"}; + WayfireWindowList* window_list; + + public: + WayfireWindowListLayout(WayfireWindowList* window_list); + int top_x = 0; + Gtk::Widget *top_widget = nullptr; +}; diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 05802076..a811f3fd 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -1,14 +1,9 @@ -#include -#include -#include -#include -#include -#include +#include #include #include #include -#include +#include #include #include @@ -35,11 +30,17 @@ class WayfireToplevel::impl uint32_t state; Gtk::Button button; - Gtk::HBox button_contents; + Glib::RefPtr actions; + + Gtk::PopoverMenu popover; + Glib::RefPtr menu; + Glib::RefPtr minimize, maximize, close; + Glib::RefPtr minimize_action, maximize_action, close_action; + // Gtk::Box menu_box; + Gtk::Box button_contents; Gtk::Image image; Gtk::Label label; - Gtk::Menu menu; - Gtk::MenuItem minimize, maximize, close; + // Gtk::PopoverMenu menu; Glib::RefPtr drag_gesture; sigc::connection m_drag_timeout; @@ -58,68 +59,101 @@ class WayfireToplevel::impl &toplevel_handle_v1_impl, this); button.get_style_context()->add_class("window-button"); - button_contents.add(image); - button_contents.add(label); - button_contents.set_halign(Gtk::ALIGN_START); - button.add(button_contents); + button.get_style_context()->add_class("flat"); + button.get_style_context()->remove_class("activated"); + button_contents.append(image); + button_contents.append(label); + button_contents.set_halign(Gtk::Align::START); + button_contents.set_hexpand(true); + button_contents.set_spacing(5); + button.set_child(button_contents); button.set_tooltip_text("none"); - button.signal_clicked().connect_notify( - sigc::mem_fun(this, &WayfireToplevel::impl::on_clicked)); - button.signal_size_allocate().connect_notify( - sigc::mem_fun(this, &WayfireToplevel::impl::on_allocation_changed)); + label.set_ellipsize(Pango::EllipsizeMode::END); + label.set_hexpand(true); + button.property_scale_factor().signal_changed() - .connect(sigc::mem_fun(this, &WayfireToplevel::impl::on_scale_update)); - button.signal_button_press_event().connect( - sigc::mem_fun(this, &WayfireToplevel::impl::on_button_press_event)); - - minimize.set_label("Minimize"); - maximize.set_label("Maximize"); - close.set_label("Close"); - minimize.signal_activate().connect( - sigc::mem_fun(this, &WayfireToplevel::impl::on_menu_minimize)); - maximize.signal_activate().connect( - sigc::mem_fun(this, &WayfireToplevel::impl::on_menu_maximize)); - close.signal_activate().connect( - sigc::mem_fun(this, &WayfireToplevel::impl::on_menu_close)); - menu.attach(minimize, 0, 1, 0, 1); - menu.attach(maximize, 0, 1, 1, 2); - menu.attach(close, 0, 1, 2, 3); - menu.attach_to_widget(button); - menu.show_all(); - - button.drag_dest_set(Gtk::DEST_DEFAULT_MOTION & Gtk::DEST_DEFAULT_HIGHLIGHT, Gdk::DragAction(0)); - - button.signal_drag_motion().connect( - [this] (const Glib::RefPtr, gint x, gint y, guint time) -> bool - { - if (!m_drag_timeout) + .connect(sigc::mem_fun(*this, &WayfireToplevel::impl::on_scale_update)); + + actions = Gio::SimpleActionGroup::create(); + + close_action = Gio::SimpleAction::create("close"); + minimize_action = Gio::SimpleAction::create_bool("minimize", false); + maximize_action = Gio::SimpleAction::create_bool("maximize", false); + close_action->signal_activate().connect(sigc::mem_fun(*this, &WayfireToplevel::impl::on_menu_close)); + minimize_action->signal_change_state().connect(sigc::mem_fun(*this, + &WayfireToplevel::impl::on_menu_minimize)); + maximize_action->signal_change_state().connect(sigc::mem_fun(*this, + &WayfireToplevel::impl::on_menu_maximize)); + + actions->add_action(close_action); + actions->add_action(minimize_action); + actions->add_action(maximize_action); + + // Hey Kids, want to see a really stupid idea? + // Button can only have one child! But setting the parent of a popover still works fine... + gtk_widget_set_parent(GTK_WIDGET(popover.gobj()), GTK_WIDGET(button.gobj())); + + popover.insert_action_group("windowaction", actions); + menu = Gio::Menu::create(); + minimize = Gio::MenuItem::create("Minimize", "windowaction.minimize"); + maximize = Gio::MenuItem::create("Maximize", "windowaction.maximize"); + close = Gio::MenuItem::create("Close", "windowaction.close"); + + menu->append_item(minimize); + menu->append_item(maximize); + menu->append_item(close); + popover.set_menu_model(menu); + + drag_gesture = Gtk::GestureDrag::create(); + drag_gesture->signal_drag_begin().connect( + sigc::mem_fun(*this, &WayfireToplevel::impl::on_drag_begin)); + drag_gesture->signal_drag_update().connect( + sigc::mem_fun(*this, &WayfireToplevel::impl::on_drag_update)); + drag_gesture->signal_drag_end().connect( + sigc::mem_fun(*this, &WayfireToplevel::impl::on_drag_end)); + button.add_controller(drag_gesture); + + auto click_gesture = Gtk::GestureClick::create(); + click_gesture->set_button(0); + click_gesture->signal_pressed().connect( + [=] (int count, double x, double y) + { + int butt = click_gesture->get_current_button(); + if (butt == 3) + { + popover.popup(); + click_gesture->set_state(Gtk::EventSequenceState::CLAIMED); + } else if (butt == 1) { - m_drag_timeout = Glib::signal_timeout().connect(sigc::mem_fun(this, - &WayfireToplevel::impl::drag_paused), 700); + // Don't action it now because the press might be a drag! + click_gesture->set_state(Gtk::EventSequenceState::CLAIMED); + } else if (butt = 2 && middle_click_close) + { + zwlr_foreign_toplevel_handle_v1_close(handle); + click_gesture->set_state(Gtk::EventSequenceState::CLAIMED); } - - return true; }); - button.signal_drag_leave().connect( - [this] (const Glib::RefPtr, guint time) + click_gesture->signal_released().connect( + [=] (int count, double x, double y) { - if (m_drag_timeout) + if (click_gesture->get_current_button() == 1) { - m_drag_timeout.disconnect(); + // Ah, it was a press after all! + if (!ignore_next_click) + { + this->on_clicked(); + } + + ignore_next_click = false; } }); - - drag_gesture = Gtk::GestureDrag::create(button); - drag_gesture->signal_drag_begin().connect_notify( - sigc::mem_fun(this, &WayfireToplevel::impl::on_drag_begin)); - drag_gesture->signal_drag_update().connect_notify( - sigc::mem_fun(this, &WayfireToplevel::impl::on_drag_update)); - drag_gesture->signal_drag_end().connect_notify( - sigc::mem_fun(this, &WayfireToplevel::impl::on_drag_end)); + button.add_controller(click_gesture); this->window_list = window_list; + + send_rectangle_hints(); } int grab_off_x; @@ -129,61 +163,76 @@ class WayfireToplevel::impl bool drag_paused() { - auto gseat = Gdk::Display::get_default()->get_default_seat(); - auto seat = gdk_wayland_seat_get_wl_seat(gseat->gobj()); - zwlr_foreign_toplevel_handle_v1_activate(handle, seat); + /* + * auto gseat = Gdk::Display::get_default()->get_default_seat()->get_wl_seat(); + * //auto seat = gdk_wayland_seat_get_wl_seat(gseat->gobj()); + * zwlr_foreign_toplevel_handle_v1_activate(handle, gseat); + */ return false; } void on_drag_begin(double _x, double _y) { - auto& container = window_list->box; - /* Set grab start, before transforming it to absolute position */ + // Set grab start, before transforming it to absolute position grab_start_x = _x; grab_start_y = _y; set_classes(state); - window_list->box.set_top_widget(&button); + window_list->set_top_widget(&button); - /* Find the distance between pointer X and button origin */ - int x = container.get_absolute_position(_x, button); + // Find the distance between pointer X and button origin + int x = window_list->get_absolute_position(_x, button); grab_abs_start_x = x; - /* Find button corner in window-relative coords */ - int loc_x = container.get_absolute_position(0, button); + // Find button corner in window-relative coords + int loc_x = window_list->get_absolute_position(0, button); grab_off_x = x - loc_x; drag_exceeds_threshold = false; } static constexpr int DRAG_THRESHOLD = 3; - void on_drag_update(double _x, double) + void on_drag_update(double _x, double y) { - auto& container = window_list->box; /* Window was not just clicked, but also dragged. Ignore the next click, * which is the one that happens when the drag gesture ends. */ set_ignore_next_click(); int x = _x + grab_start_x; - x = container.get_absolute_position(x, button); + x = window_list->get_absolute_position(x, button); if (std::abs(x - grab_abs_start_x) > DRAG_THRESHOLD) { drag_exceeds_threshold = true; } - auto hovered_button = container.get_widget_at(x); + auto hovered_button = window_list->get_widget_at(x); + Gtk::Widget *before = window_list->get_widget_before(x); - if ((hovered_button != &button) && hovered_button) + if (hovered_button) { - auto children = container.get_unsorted_widgets(); - auto it = std::find(children.begin(), children.end(), hovered_button); - container.reorder_child(button, it - children.begin()); + // Where are we in the button? + auto allocation = hovered_button->get_allocation(); + int half_width = allocation.get_width() / 2; + int x_in_button = x - allocation.get_x(); + if (x_in_button < half_width) // Left Half + { + if (before == nullptr) + { + gtk_box_reorder_child_after(window_list->gobj(), GTK_WIDGET(button.gobj()), nullptr); + } else + { + window_list->reorder_child_after(button, *before); + } + } else if (x_in_button > half_width) // Right Half + { + window_list->reorder_child_after(button, *hovered_button); + } } /* Make sure the grabbed button always stays at the same relative position * to the DnD position */ int target_x = x - grab_off_x; - window_list->box.set_top_x(target_x); + window_list->set_top_x(target_x); } void on_drag_end(double _x, double _y) @@ -193,7 +242,7 @@ class WayfireToplevel::impl int width = button.get_allocated_width(); int height = button.get_allocated_height(); - window_list->box.set_top_widget(nullptr); + window_list->set_top_widget(nullptr); set_classes(state); /* When a button is dropped after dnd, we ignore the unclick @@ -211,54 +260,52 @@ class WayfireToplevel::impl if (!drag_exceeds_threshold) { unset_ignore_next_click(); + this->on_clicked(); } + + drag_gesture->set_state(Gtk::EventSequenceState::DENIED); + + send_rectangle_hints(); } - bool on_button_press_event(GdkEventButton *event) + void set_hide_text(bool hide_text) { - if (event->type == GDK_BUTTON_PRESS) + if (hide_text) { - if (event->button == 3) - { - menu.popup_at_widget(&button, Gdk::GRAVITY_NORTH, Gdk::GRAVITY_SOUTH, NULL); - return true; // It has been handled. - } else if ((event->button == 2) && middle_click_close) - { - zwlr_foreign_toplevel_handle_v1_close(handle); - return true; - } + label.hide(); + } else + { + label.show(); } - - return false; } - void on_menu_minimize() + void on_menu_minimize(Glib::VariantBase vb) { - menu.popdown(); - if (state & WF_TOPLEVEL_STATE_MINIMIZED) + bool val = g_variant_get_boolean(vb.gobj()); + send_rectangle_hint(); + if (!val) { zwlr_foreign_toplevel_handle_v1_unset_minimized(handle); - } else - { - zwlr_foreign_toplevel_handle_v1_set_minimized(handle); + return; } + + zwlr_foreign_toplevel_handle_v1_set_minimized(handle); } - void on_menu_maximize() + void on_menu_maximize(Glib::VariantBase vb) { - menu.popdown(); - if (state & WF_TOPLEVEL_STATE_MAXIMIZED) + bool val = g_variant_get_boolean(vb.gobj()); + if (!val) { zwlr_foreign_toplevel_handle_v1_unset_maximized(handle); - } else - { - zwlr_foreign_toplevel_handle_v1_set_maximized(handle); + return; } + + zwlr_foreign_toplevel_handle_v1_set_maximized(handle); } - void on_menu_close() + void on_menu_close(Glib::VariantBase vb) { - menu.popdown(); zwlr_foreign_toplevel_handle_v1_close(handle); } @@ -269,15 +316,15 @@ class WayfireToplevel::impl /* Make sure that the view doesn't show clicked on animations while * dragging (this happens only on some themes) */ - button.set_state_flags(Gtk::STATE_FLAG_SELECTED | - Gtk::STATE_FLAG_DROP_ACTIVE | Gtk::STATE_FLAG_PRELIGHT); + button.set_state_flags(Gtk::StateFlags::SELECTED | + Gtk::StateFlags::DROP_ACTIVE | Gtk::StateFlags::PRELIGHT); } void unset_ignore_next_click() { ignore_next_click = false; - button.unset_state_flags(Gtk::STATE_FLAG_SELECTED | - Gtk::STATE_FLAG_DROP_ACTIVE | Gtk::STATE_FLAG_PRELIGHT); + button.unset_state_flags(Gtk::StateFlags::SELECTED | + Gtk::StateFlags::DROP_ACTIVE | Gtk::StateFlags::PRELIGHT); } void on_clicked() @@ -318,12 +365,6 @@ class WayfireToplevel::impl } } - void on_allocation_changed(Gtk::Allocation& alloc) - { - send_rectangle_hint(); - window_list->scrolled_window.queue_allocate(); - } - void on_scale_update() { set_app_id(app_id); @@ -337,82 +378,36 @@ class WayfireToplevel::impl std::min(int(minimal_panel_height), 24), button.get_scale_factor()); } - void send_rectangle_hint() + void send_rectangle_hints() { - Gtk::Widget *widget = &this->button; - auto panel = WayfirePanelApp::get().panel_for_wl_output(window_list->output->wo); - if (panel) + for (const auto& toplevel_button : window_list->toplevels) { - int x, y; - widget->translate_coordinates(panel->get_window(), 0, 0, x, y); - - int width = button.get_allocated_width(); - int height = button.get_allocated_height(); - zwlr_foreign_toplevel_handle_v1_set_rectangle(handle, - panel->get_wl_surface(), x, y, width, height); + if (toplevel_button.second && toplevel_button.second->pimpl) + { + toplevel_button.second->pimpl->send_rectangle_hint(); + } } } - int32_t max_width = 0; - void set_title(std::string title) - { - this->title = title; - button.set_tooltip_text(title); - - set_max_width(max_width); - } - - Glib::ustring shorten_title(int show_chars) + void send_rectangle_hint() { - if (show_chars == 0) - { - return ""; - } - - int title_len = title.length(); - Glib::ustring short_title = title.substr(0, show_chars); - if (title_len - show_chars >= 2) - { - short_title += ".."; - } else if (title_len != show_chars) - { - short_title += "."; + auto panel = WayfirePanelApp::get().panel_for_wl_output(window_list->output->wo); + auto w = button.get_width(); + auto h = button.get_height(); + if (panel && (w > 0) && (h > 0)) + { + double x, y; + button.translate_coordinates(panel->get_window(), 0, 0, x, y); + zwlr_foreign_toplevel_handle_v1_set_rectangle(handle, panel->get_wl_surface(), + x, y, w, h); } - - return short_title; - } - - int get_button_preferred_width() - { - int min_width, preferred_width; - button.get_preferred_width(min_width, preferred_width); - - return preferred_width; } - void set_max_width(int width) + void set_title(std::string title) { - this->max_width = width; - if (max_width == 0) - { - this->button.set_size_request(-1, -1); - this->label.set_label(title); - return; - } - - this->button.set_size_request(width, -1); - - int show_chars = 0; - for (show_chars = title.length(); show_chars > 0; show_chars--) - { - this->label.set_text(shorten_title(show_chars)); - if (get_button_preferred_width() <= max_width) - { - break; - } - } - - label.set_text(shorten_title(show_chars)); + this->title = title; + button.set_tooltip_text(title); + label.set_text(title); } uint32_t get_state() @@ -437,27 +432,8 @@ class WayfireToplevel::impl void remove_button() { - auto& container = window_list->box; - container.remove(button); - } - - void update_menu_item_text() - { - if (state & WF_TOPLEVEL_STATE_MINIMIZED) - { - minimize.set_label("Unminimize"); - } else - { - minimize.set_label("Minimize"); - } - - if (state & WF_TOPLEVEL_STATE_MAXIMIZED) - { - maximize.set_label("Unmaximize"); - } else - { - maximize.set_label("Maximize"); - } + window_list->remove(button); + send_rectangle_hints(); } void set_classes(uint32_t state) @@ -475,17 +451,21 @@ class WayfireToplevel::impl if (state & WF_TOPLEVEL_STATE_MINIMIZED) { button.get_style_context()->add_class("minimized"); + minimize_action->set_state(Glib::wrap(g_variant_new_boolean(true))); } else { button.get_style_context()->remove_class("minimized"); + minimize_action->set_state(Glib::wrap(g_variant_new_boolean(false))); } if (state & WF_TOPLEVEL_STATE_MAXIMIZED) { button.get_style_context()->add_class("maximized"); + maximize_action->set_state(Glib::wrap(g_variant_new_boolean(true))); } else { button.get_style_context()->remove_class("maximized"); + maximize_action->set_state(Glib::wrap(g_variant_new_boolean(false))); } } @@ -493,11 +473,11 @@ class WayfireToplevel::impl { this->state = state; set_classes(state); - update_menu_item_text(); } ~impl() { + gtk_widget_unparent(GTK_WIDGET(popover.gobj())); if (m_drag_timeout) { m_drag_timeout.disconnect(); @@ -513,22 +493,19 @@ class WayfireToplevel::impl return; } - auto& container = window_list->box; if (window_list->output->wo == output) { - container.add(button); - container.show_all(); + window_list->append(button); + send_rectangle_hints(); } - - update_menu_item_text(); } void handle_output_leave(wl_output *output) { - auto& container = window_list->box; if (window_list->output->wo == output) { - container.remove(button); + window_list->remove(button); + send_rectangle_hints(); } } }; @@ -539,29 +516,20 @@ WayfireToplevel::WayfireToplevel(WayfireWindowList *window_list, pimpl(new WayfireToplevel::impl(window_list, handle)) {} -void WayfireToplevel::set_width(int pixels) -{ - return pimpl->set_max_width(pixels); -} std::vector& WayfireToplevel::get_children() { return pimpl->get_children(); } -zwlr_foreign_toplevel_handle_v1*WayfireToplevel::get_parent() -{ - return pimpl->get_parent(); -} - -void WayfireToplevel::set_parent(zwlr_foreign_toplevel_handle_v1 *parent) +uint32_t WayfireToplevel::get_state() { - return pimpl->set_parent(parent); + return pimpl->get_state(); } -uint32_t WayfireToplevel::get_state() +void WayfireToplevel::send_rectangle_hint() { - return pimpl->get_state(); + return pimpl->send_rectangle_hint(); } WayfireToplevel::~WayfireToplevel() = default; @@ -648,8 +616,8 @@ static void remove_child_from_parent(WayfireToplevel::impl *impl, toplevel_t chi static void handle_toplevel_closed(void *data, toplevel_t handle) { - // WayfirePanelApp::get().handle_toplevel_closed(handle); auto impl = static_cast(data); + impl->remove_button(); remove_child_from_parent(impl, handle); impl->window_list->handle_toplevel_closed(handle); } @@ -767,16 +735,16 @@ void set_image_from_icon(Gtk::Image& image, /* Wayfire sends a list of app-id's in space separated format, other compositors * send a single app-id, but in any case this works fine */ + auto display = image.get_display(); while (stream >> app_id) { auto icon = get_from_desktop_app_info(app_id); std::string icon_name = "unknown"; - if (!icon) { /* Perhaps no desktop app info, but we might still be able to * get an icon directly from the icon theme */ - if (Gtk::IconTheme::get_default()->lookup_icon(app_id, size)) + if (Gtk::IconTheme::get_for_display(display)->lookup_icon(app_id, size)) { icon_name = app_id; } @@ -785,9 +753,7 @@ void set_image_from_icon(Gtk::Image& image, icon_name = icon->to_string(); } - WfIconLoadOptions options; - options.user_scale = scale; - set_image_icon(image, icon_name, size, options); + image_set_icon(&image, icon_name); /* finally found some icon */ if (icon_name != "unknown") diff --git a/src/panel/widgets/window-list/toplevel.hpp b/src/panel/widgets/window-list/toplevel.hpp index 3e248b7f..36c98838 100644 --- a/src/panel/widgets/window-list/toplevel.hpp +++ b/src/panel/widgets/window-list/toplevel.hpp @@ -7,6 +7,7 @@ #include #include +#include "layout.hpp" #include "window-list.hpp" class WayfireWindowList; @@ -27,11 +28,10 @@ class WayfireToplevel WayfireToplevel(WayfireWindowList *window_list, zwlr_foreign_toplevel_handle_v1 *handle); uint32_t get_state(); - void set_width(int pixels); - zwlr_foreign_toplevel_handle_v1 *get_parent(); - void set_parent(zwlr_foreign_toplevel_handle_v1*); + void send_rectangle_hint(); std::vector& get_children(); ~WayfireToplevel(); + void set_hide_text(bool hide_text); class impl; diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index 650d7494..362c1e76 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -1,131 +1,11 @@ #include #include -#include +#include #include "toplevel.hpp" #include "window-list.hpp" #include "panel.hpp" -WayfireWindowListBox::WayfireWindowListBox() : Gtk::HBox() -{} - -void WayfireWindowListBox::set_top_widget(Gtk::Widget *top) -{ - this->top_widget = top; - - if (top_widget) - { - /* Set original top_x to where the widget currently is, so that we don't - * mess with it before the real position is set */ - this->top_x = get_absolute_position(0, *top); - } - - set_top_x(top_x); -} - -void WayfireWindowListBox::set_top_x(int x) -{ - /* Make sure that the widget doesn't go outside of the box */ - if (this->top_widget) - { - x = std::min(x, get_allocated_width() - top_widget->get_allocated_width()); - } - - if (this->top_widget) - { - x = std::max(x, 0); - } - - this->top_x = x; - - queue_allocate(); - queue_draw(); - - auto alloc = this->get_allocation(); - on_size_allocate(alloc); -} - -static void for_each_child_callback(GtkWidget *widget, gpointer data) -{ - auto v = (std::vector*)data; - v->push_back(widget); -} - -std::vector WayfireWindowListBox::get_unsorted_widgets() -{ - std::vector children; - HBox::forall_vfunc(true, &for_each_child_callback, &children); - - std::vector result; - for (auto& child : children) - { - result.push_back(Glib::wrap(child)); - } - - return result; -} - -void WayfireWindowListBox::forall_vfunc(gboolean value, GtkCallback callback, gpointer callback_data) -{ - std::vector children; - HBox::forall_vfunc(true, &for_each_child_callback, &children); - - if (top_widget) - { - auto it = std::find(children.begin(), children.end(), top_widget->gobj()); - children.erase(it); - children.push_back(top_widget->gobj()); - } - - for (auto& child : children) - { - callback(child, callback_data); - } -} - -void WayfireWindowListBox::on_size_allocate(Gtk::Allocation& alloc) -{ - HBox::on_size_allocate(alloc); - - if (top_widget) - { - auto alloc = top_widget->get_allocation(); - alloc.set_x(this->top_x); - top_widget->size_allocate(alloc); - } -} - -int WayfireWindowListBox::get_absolute_position(int x, Gtk::Widget& ref) -{ - auto w = &ref; - while (w && w != this) - { - auto allocation = w->get_allocation(); - x += allocation.get_x(); - w = w->get_parent(); - } - - return x; -} - -Gtk::Widget*WayfireWindowListBox::get_widget_at(int x) -{ - Gtk::Allocation given_point{x, get_allocated_height() / 2, 1, 1}; - - /* Widgets are stored bottom to top, so we will return the bottom-most - * widget at the given position */ - auto children = this->get_children(); - for (auto& child : children) - { - if (child->get_allocation().intersects(given_point)) - { - return child; - } - } - - return nullptr; -} - #define DEFAULT_SIZE_PC 0.1 static void handle_manager_toplevel(void *data, zwlr_foreign_toplevel_manager_v1 *manager, @@ -167,7 +47,7 @@ static struct wl_registry_listener registry_listener = ®istry_remove_object }; -void WayfireWindowList::init(Gtk::HBox *container) +void WayfireWindowList::init(Gtk::Box *container) { auto gdk_display = gdk_display_get_default(); auto display = gdk_wayland_display_get_wl_display(gdk_display); @@ -191,64 +71,112 @@ void WayfireWindowList::init(Gtk::HBox *container) zwlr_foreign_toplevel_manager_v1_add_listener(manager, &toplevel_manager_v1_impl, this); - scrolled_window.signal_draw().connect_notify( - sigc::mem_fun(this, &WayfireWindowList::on_draw)); - - box.set_homogeneous(true); - scrolled_window.add(box); + this->set_homogeneous(true); + scrolled_window.set_hexpand(true); + scrolled_window.set_child(*this); scrolled_window.set_propagate_natural_width(true); - scrolled_window.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_NEVER); - container->pack_start(scrolled_window, true, true); - scrolled_window.show_all(); + scrolled_window.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::NEVER); + container->append(scrolled_window); } -void WayfireWindowList::set_button_width(int width) +void WayfireWindowList::set_top_widget(Gtk::Widget *top) { - std::cout << "set width " << width << std::endl; - for (auto& toplevel : toplevels) + this->layout->top_widget = top; + + if (layout->top_widget) { - if (toplevel.second) - { - toplevel.second->set_width(width); - } + /* Set original top_x to where the widget currently is, so that we don't + * mess with it before the real position is set */ + this->layout->top_x = get_absolute_position(0, *top); } + + set_top_x(layout->top_x); } -int WayfireWindowList::get_default_button_width() +void WayfireWindowList::set_top_x(int x) { - return DEFAULT_SIZE_PC * - WayfirePanelApp::get().panel_for_wl_output(output->wo)->get_window() - .get_allocated_width(); + /* Make sure that the widget doesn't go outside of the box */ + if (this->layout->top_widget) + { + x = std::min(x, get_allocated_width() - layout->top_widget->get_allocated_width()); + } + + if (this->layout->top_widget) + { + x = std::max(x, 0); + } + + this->layout->top_x = x; + + if (this->layout->top_widget) + { + // TODO Sensibly cause a reflow to force layout manager to move children + } + + queue_allocate(); + queue_draw(); } -int WayfireWindowList::get_target_button_width() +int WayfireWindowList::get_absolute_position(int x, Gtk::Widget& ref) { - int num_children = box.get_children().size(); - int target_width = get_default_button_width(); - - if (num_children > 0) + auto w = &ref; + while (w && w != this) { - target_width = std::min(target_width, - scrolled_window.get_allocated_width() / num_children); - std::cout << "target button " << scrolled_window.get_allocated_width() << std::endl; + auto allocation = w->get_allocation(); + x += allocation.get_x(); + w = w->get_parent(); } - return target_width; + return x; } -void WayfireWindowList::on_draw(const Cairo::RefPtr& ctx) +Gtk::Widget*WayfireWindowList::get_widget_before(int x) { - int allocated_width = scrolled_window.get_allocated_width(); + Gtk::Allocation given_point{x, get_allocated_height() / 2, 1, 1}; + + /* Widgets are stored bottom to top, so we will return the bottom-most + * widget at the given position */ + Gtk::Widget *previous = nullptr; + auto children = this->get_children(); + for (auto& child : children) + { + if (layout->top_widget && (child == layout->top_widget)) + { + continue; + } + + if (child->get_allocation().intersects(given_point)) + { + return previous; + } + + previous = child; + } + + return nullptr; +} - int minimal_width, preferred_width; - scrolled_window.get_preferred_width(minimal_width, preferred_width); +Gtk::Widget*WayfireWindowList::get_widget_at(int x) +{ + Gtk::Allocation given_point{x, get_allocated_height() / 2, 1, 1}; - /* We have changed the size/number of toplevels. On top of that, our list - * is longer that the max size, so we need to re-layout the buttons */ - if ((preferred_width > allocated_width) && (toplevels.size() > 0)) + /* Widgets are stored bottom to top, so we will return the bottom-most + * widget at the given position */ + auto children = this->get_children(); + for (auto& child : children) { - set_button_width(get_target_button_width()); + if (child == layout->top_widget) + { + continue; + } + + if (child->get_allocation().intersects(given_point)) + { + return child; + } } + + return nullptr; } void WayfireWindowList::add_output(WayfireOutput *output) @@ -264,8 +192,6 @@ void WayfireWindowList::handle_toplevel_manager(zwlr_foreign_toplevel_manager_v1 void WayfireWindowList::handle_new_toplevel(zwlr_foreign_toplevel_handle_v1 *handle) { toplevels[handle] = std::unique_ptr(new WayfireToplevel(this, handle)); - /* The size will be updated in the next on_draw() if needed */ - toplevels[handle]->set_width(get_default_button_width()); } void WayfireWindowList::handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 *handle) @@ -277,14 +203,18 @@ void WayfireWindowList::handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 * { return; } - - /* Recalculate button size */ - set_button_width(get_target_button_width()); } WayfireWindowList::WayfireWindowList(WayfireOutput *output) { this->output = output; + + layout = std::make_shared(this); + set_layout_manager(layout); + user_size.set_callback([=] + { + this->queue_allocate(); + }); } WayfireWindowList::~WayfireWindowList() diff --git a/src/panel/widgets/window-list/window-list.hpp b/src/panel/widgets/window-list/window-list.hpp index 2812fb9f..f38c64ab 100644 --- a/src/panel/widgets/window-list/window-list.hpp +++ b/src/panel/widgets/window-list/window-list.hpp @@ -5,18 +5,35 @@ #include "panel.hpp" #include -#include -#include +#include +#include "toplevel.hpp" class WayfireToplevel; -class WayfireWindowListBox : public Gtk::HBox +class WayfireWindowList : public Gtk::Box, public WayfireWidget { - Gtk::Widget *top_widget = nullptr; - int top_x = 0; + WfOption user_size{"panel/window_list_size"}; + std::shared_ptr layout; public: - WayfireWindowListBox(); + std::map> toplevels; + + zwlr_foreign_toplevel_manager_v1 *manager = NULL; + WayfireOutput *output; + Gtk::ScrolledWindow scrolled_window; + + WayfireWindowList(WayfireOutput *output); + virtual ~WayfireWindowList(); + + void handle_toplevel_manager(zwlr_foreign_toplevel_manager_v1 *manager); + void handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 *handle); + void handle_new_toplevel(zwlr_foreign_toplevel_handle_v1 *handle); + + wayfire_config *get_config(); + + void init(Gtk::Box *container) override; + void add_output(WayfireOutput *output); /** * Set the widget which should always be rendered on top of the other child @@ -25,13 +42,6 @@ class WayfireWindowListBox : public Gtk::HBox /** Set the absolute position of the top widget */ void set_top_x(int x); - /** - * Override some of Gtk::HBox's built-in layouting functions, so that we - * support manually dragging a button - */ - void forall_vfunc(gboolean, GtkCallback callback, gpointer callback_data) override; - void on_size_allocate(Gtk::Allocation& alloc) override; - /** * @param x the x-axis position, relative to ref * @param ref The widget that x is relative to. ref must be a child @@ -48,41 +58,16 @@ class WayfireWindowListBox : public Gtk::HBox * @return The direct child widget or none if it doesn't exist */ Gtk::Widget *get_widget_at(int x); - - /** - * Get the list of widgets sorted from left to right, i.e ignoring the top - * widget setting + /** Find the direct child widget before the given box-relative coordinates, + * ignoring the top widget if possible, i.e if the top widget and some + * other widget are at the given coordinates, then the bottom widget will + * be returned + * + * @return The direct child widget or none if it doesn't exist */ - std::vector get_unsorted_widgets(); -}; - -class WayfireWindowList : public WayfireWidget -{ - public: - std::map> toplevels; - - zwlr_foreign_toplevel_manager_v1 *manager = NULL; - WayfireOutput *output; - WayfireWindowListBox box; - Gtk::ScrolledWindow scrolled_window; - - WayfireWindowList(WayfireOutput *output); - virtual ~WayfireWindowList(); - - void handle_toplevel_manager(zwlr_foreign_toplevel_manager_v1 *manager); - void handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 *handle); - void handle_new_toplevel(zwlr_foreign_toplevel_handle_v1 *handle); - - wayfire_config *get_config(); - - void init(Gtk::HBox *container) override; - void add_output(WayfireOutput *output); + Gtk::Widget *get_widget_before(int x); private: - void on_draw(const Cairo::RefPtr&); - - void set_button_width(int width); int get_default_button_width(); int get_target_button_width(); }; diff --git a/src/util/css-config.cpp b/src/util/css-config.cpp new file mode 100644 index 00000000..a2138453 --- /dev/null +++ b/src/util/css-config.cpp @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include + +void CssFromConfig::add_provider() +{ + Gtk::StyleContext::add_provider_for_display( + Gdk::Display::get_default(), provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); +} + +void CssFromConfig::remove_provider() +{ + Gtk::StyleContext::remove_provider_for_display(Gdk::Display::get_default(), provider); +} + +CssFromConfigBool::CssFromConfigBool(std::string option_name, std::string css_true, std::string css_false) : + option_value{option_name} +{ + provider = Gtk::CssProvider::create(); + option_value.set_callback([=] + { + provider->load_from_string(option_value ? css_true : css_false); + }); + + add_provider(); +} + +CssFromConfigInt::CssFromConfigInt(std::string option_name, std::string css_before, std::string css_after) : + option_value{option_name} +{ + provider = Gtk::CssProvider::create(); + option_value.set_callback([=] + { + // TODO When we go up to c++20 use std::format + std::stringstream ss; + ss << css_before << option_value << css_after; + provider->load_from_string(ss.str()); + }); + std::stringstream ss; + ss << css_before << option_value << css_after; + provider->load_from_string(ss.str()); + + add_provider(); +} + +CssFromConfigString::CssFromConfigString(std::string option_name, std::string css_before, + std::string css_after) : + option_value{option_name} +{ + provider = Gtk::CssProvider::create(); + option_value.set_callback([=] () + { + // TODO When we go up to c++20 use std::format + std::stringstream ss; + ss << css_before << (std::string)option_value << css_after; + provider->load_from_string(ss.str()); + }); + std::stringstream ss; + ss << css_before << (std::string)option_value << css_after; + provider->load_from_string(ss.str()); + + add_provider(); +} + +CssFromConfigFont::CssFromConfigFont(std::string option_name, std::string css_before, std::string css_after) : + option_value{option_name} +{ + this->css_before = css_before; + this->css_after = css_after; + provider = Gtk::CssProvider::create(); + option_value.set_callback([=] () + { + set_from_string(); + }); + + set_from_string(); + add_provider(); +} + +void CssFromConfigFont::set_from_string() +{ + std::regex matcher("(.*?)(\\d+(?:pt|px|rem|em|))(.*?)"); + std::string font_name = (std::string)option_value; + std::smatch matches; + if (std::regex_match(font_name, matches, matcher)) + { + std::ssub_match before = matches[1]; + std::string size = matches[2].str(); + std::ssub_match after = matches[3]; + + std::string unit = ""; + + // If we're using a bare number (ie 15) it now needs to be 15pt to match previous behaviour + if ((size.find("px") == std::string::npos) && + (size.find("pt") == std::string::npos) && + (size.find("em") == std::string::npos)) + { + unit = "pt"; + } + + std::stringstream ss; + ss << css_before << "font: " << size << unit << " " << before.str() << " " << after.str() << ";" << + css_after; + auto css = ss.str(); + provider->load_from_string(css); + + std::cout << "Font " << css << std::endl; + } else + { + std::stringstream ss; + ss << css_before << "font: 1rem " << font_name << ";" << css_after; + auto css = ss.str(); + provider->load_from_string(css); + std::cout << "Font fallback " << css << std::endl; + } +} diff --git a/src/util/css-config.hpp b/src/util/css-config.hpp new file mode 100644 index 00000000..bfd83f04 --- /dev/null +++ b/src/util/css-config.hpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +class CssFromConfig +{ + public: + Glib::RefPtr provider; + + void add_provider(); + void remove_provider(); +}; + +class CssFromConfigBool : public CssFromConfig +{ + WfOption option_value; + + public: + CssFromConfigBool(std::string config_opt, std::string css_true, std::string css_false); +}; + +class CssFromConfigString : public CssFromConfig +{ + WfOption option_value; + + public: + CssFromConfigString(std::string config_opt, std::string css_before, std::string css_after); +}; + +class CssFromConfigFont : public CssFromConfig +{ + WfOption option_value; + std::string css_before, css_after; + + public: + CssFromConfigFont(std::string config_opt, std::string css_before, std::string css_after); + void set_from_string(); +}; + +class CssFromConfigInt : public CssFromConfig +{ + WfOption option_value; + + public: + CssFromConfigInt(std::string config_opt, std::string css_before, std::string css_after); +}; diff --git a/src/util/gtk-utils.cpp b/src/util/gtk-utils.cpp index ae8b9623..80cec7b5 100644 --- a/src/util/gtk-utils.cpp +++ b/src/util/gtk-utils.cpp @@ -62,56 +62,13 @@ void invert_pixbuf(Glib::RefPtr& pbuff) } } -void set_image_pixbuf(Gtk::Image & image, Glib::RefPtr pixbuf, int scale) +void image_set_icon(Gtk::Image *image, std::string path) { - auto pbuff = pixbuf->gobj(); - auto cairo_surface = gdk_cairo_surface_create_from_pixbuf(pbuff, scale, NULL); - - gtk_image_set_from_surface(image.gobj(), cairo_surface); - cairo_surface_destroy(cairo_surface); -} - -void set_image_icon(Gtk::Image& image, std::string icon_name, int size, - const WfIconLoadOptions& options, - const Glib::RefPtr& icon_theme) -{ - int scale = ((options.user_scale == -1) ? - image.get_scale_factor() : options.user_scale); - - /* Create surface above necessary scale to allow zoom effects */ - scale = scale * 2; - int scaled_size = size * scale; - - Glib::RefPtr pbuff = {}; - /* Get from theme if possible */ - if (icon_theme->lookup_icon(icon_name, scaled_size)) - { - pbuff = icon_theme->load_icon(icon_name, scaled_size, Gtk::ICON_LOOKUP_FORCE_SIZE) - ->scale_simple(scaled_size, scaled_size, Gdk::INTERP_BILINEAR); - } - - /* Get from filesystem if necessary */ - if (!pbuff) + if ((path.rfind("/", 0) == 0) || (path.rfind("~", 0) == 0)) { - pbuff = load_icon_pixbuf_safe(icon_name, scaled_size); - } - - if (!pbuff) + image->set(path); + } else { - if (icon_theme->lookup_icon("image-missing", scaled_size)) - { - pbuff = icon_theme->load_icon("image-missing", scaled_size, Gtk::ICON_LOOKUP_FORCE_SIZE) - ->scale_simple(scaled_size, scaled_size, Gdk::INTERP_BILINEAR); - } else - { - return; - } + image->set_from_icon_name(path); } - - if (options.invert) - { - invert_pixbuf(pbuff); - } - - set_image_pixbuf(image, pbuff, scale); } diff --git a/src/util/gtk-utils.hpp b/src/util/gtk-utils.hpp index e78d1b0c..0272f2bf 100644 --- a/src/util/gtk-utils.hpp +++ b/src/util/gtk-utils.hpp @@ -18,16 +18,8 @@ struct WfIconLoadOptions bool invert = false; }; -/* Sets the content of the image to the pixbuf, applying device scale factor "scale" */ -void set_image_pixbuf(Gtk::Image & image, Glib::RefPtr pixbuf, int scale); - -/* Sets the content of the image to the corresponding icon from the default theme, - * using the given options */ -void set_image_icon(Gtk::Image& image, std::string icon_name, int size, - const WfIconLoadOptions& options = {}, - const Glib::RefPtr& icon_theme - = Gtk::IconTheme::get_default()); - void invert_pixbuf(Glib::RefPtr& pbuff); +void image_set_icon(Gtk::Image *image, std::string path); + #endif /* end of include guard: WF_GTK_UTILS */ diff --git a/src/util/meson.build b/src/util/meson.build index 056f79db..552635be 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -1,8 +1,14 @@ -util = static_library('util', ['gtk-utils.cpp', 'wf-shell-app.cpp', 'wf-autohide-window.cpp', 'wf-popover.cpp'], - dependencies: [wf_protos, wayland_client, gtkmm, wfconfig, libinotify, gtklayershell]) +util = static_library( + 'util', + [ + 'gtk-utils.cpp', + 'wf-shell-app.cpp', + 'wf-autohide-window.cpp', + 'wf-popover.cpp', + 'css-config.cpp', + ], + dependencies: [wf_protos, wayland_client, gtkmm, wfconfig, libinotify, gtklayershell], +) util_includes = include_directories('.') -libutil = declare_dependency( - link_with: util, - include_directories: util_includes) - +libutil = declare_dependency(link_with: util, include_directories: util_includes) diff --git a/src/util/wf-autohide-window.cpp b/src/util/wf-autohide-window.cpp index 6e785ee1..5b431b45 100644 --- a/src/util/wf-autohide-window.cpp +++ b/src/util/wf-autohide-window.cpp @@ -1,9 +1,10 @@ #include "wf-autohide-window.hpp" #include "wayfire-shell-unstable-v2-client-protocol.h" -#include +#include #include -#include +#include +#include #include #include @@ -21,7 +22,6 @@ WayfireAutohidingWindow::WayfireAutohidingWindow(WayfireOutput *output, { this->output = output; this->set_decorated(false); - this->set_resizable(false); gtk_layer_init_for_window(this->gobj()); gtk_layer_set_monitor(this->gobj(), output->monitor->gobj()); @@ -30,17 +30,27 @@ WayfireAutohidingWindow::WayfireAutohidingWindow(WayfireOutput *output, this->position.set_callback([=] () { this->update_position(); }); this->update_position(); - this->signal_draw().connect_notify( - [=] (const Cairo::RefPtr&) { update_margin(); }); + auto pointer_gesture = Gtk::EventControllerMotion::create(); + pointer_gesture->signal_enter().connect([=] (double x, double y) + { + if (this->pending_hide.connected()) + { + this->pending_hide.disconnect(); + } - this->signal_focus_out_event().connect_notify( - [=] (const GdkEventFocus*) + this->input_inside_panel = true; + y_position.animate(0); + start_draw_timer(); + }); + pointer_gesture->signal_leave().connect([=] { - if (this->active_button) + this->input_inside_panel = false; + if (this->should_autohide()) { - unset_active_popover(*this->active_button); + this->schedule_hide(AUTOHIDE_HIDE_DELAY); } }); + this->add_controller(pointer_gesture); this->setup_autohide(); @@ -88,8 +98,8 @@ WayfireAutohidingWindow::~WayfireAutohidingWindow() wl_surface*WayfireAutohidingWindow::get_wl_surface() const { - auto gdk_window = const_cast(this->get_window()->gobj()); - return gdk_wayland_window_get_wl_surface(gdk_window); + auto wsurf = GDK_WAYLAND_SURFACE(this->get_surface()->gobj()); + return gdk_wayland_surface_get_wl_surface(wsurf); } /** Verify that position is correct and return a correct position */ @@ -155,7 +165,8 @@ void WayfireAutohidingWindow::update_position() } /* When the position changes, show an animation from the new edge. */ - y_position.animate(-this->get_allocated_height(), -this->get_allocated_height()); + y_position.animate(-this->get_allocated_height(), 0); + start_draw_timer(); m_show_uncertain(); setup_hotspot(); } @@ -276,13 +287,12 @@ void WayfireAutohidingWindow::setup_auto_exclusive_zone() void WayfireAutohidingWindow::update_auto_exclusive_zone() { - int allocated_height = get_allocated_height(); - int new_zone_size = this->auto_exclusive_zone ? allocated_height : 0; - - if (new_zone_size != this->auto_exclusive_zone_size) + if (this->auto_exclusive_zone) { - gtk_layer_set_exclusive_zone(this->gobj(), new_zone_size); - this->auto_exclusive_zone_size = new_zone_size; + gtk_layer_auto_exclusive_zone_enable(this->gobj()); + } else + { + gtk_layer_set_exclusive_zone(this->gobj(), 0); } } @@ -324,10 +334,24 @@ bool WayfireAutohidingWindow::should_autohide() const bool WayfireAutohidingWindow::m_do_hide() { y_position.animate(-get_allocated_height()); + start_draw_timer(); update_margin(); return false; // disconnect } +void WayfireAutohidingWindow::start_draw_timer() +{ + add_tick_callback(sigc::mem_fun(*this, &WayfireAutohidingWindow::update_animation)); +} + +gboolean WayfireAutohidingWindow::update_animation(Glib::RefPtr frame_clock) +{ + update_margin(); + // this->queue_draw(); + // Once we've finished fading, stop this callback + return y_position.running() ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE; +} + void WayfireAutohidingWindow::schedule_hide(int delay) { pending_show.disconnect(); @@ -340,13 +364,14 @@ void WayfireAutohidingWindow::schedule_hide(int delay) if (!pending_hide.connected()) { pending_hide = Glib::signal_timeout().connect( - sigc::mem_fun(this, &WayfireAutohidingWindow::m_do_hide), delay); + sigc::mem_fun(*this, &WayfireAutohidingWindow::m_do_hide), delay); } } bool WayfireAutohidingWindow::m_do_show() { - y_position.animate(std::fmin(0, y_position + 1), 0); + y_position.animate(0); + start_draw_timer(); update_margin(); return false; // disconnect } @@ -363,7 +388,7 @@ void WayfireAutohidingWindow::schedule_show(int delay) if (!pending_show.connected()) { pending_show = Glib::signal_timeout().connect( - sigc::mem_fun(this, &WayfireAutohidingWindow::m_do_show), delay); + sigc::mem_fun(*this, &WayfireAutohidingWindow::m_do_show), delay); } } @@ -375,7 +400,7 @@ bool WayfireAutohidingWindow::update_margin() get_anchor_edge(position), y_position); // queue_draw does not work when the panel is hidden // so calling wl_surface_commit to make WM show the panel back - if (get_window()) + if (get_surface()) { wl_surface_commit(get_wl_surface()); } @@ -400,21 +425,24 @@ void WayfireAutohidingWindow::set_active_popover(WayfireMenuButton& button) this->active_button = &button; this->popover_hide = - this->active_button->m_popover.signal_hide().connect_notify( + this->active_button->m_popover.signal_hide().connect( [this, &button] () { unset_active_popover(button); }); } const bool should_grab_focus = this->active_button->is_keyboard_interactive(); - if (should_grab_focus) - { - // First, set exclusive mode to grab input - gtk_layer_set_keyboard_mode(this->gobj(), GTK_LAYER_SHELL_KEYBOARD_MODE_EXCLUSIVE); - wl_surface_commit(get_wl_surface()); - - // Next, allow releasing of focus when clicking outside of the panel - gtk_layer_set_keyboard_mode(this->gobj(), GTK_LAYER_SHELL_KEYBOARD_MODE_ON_DEMAND); - } + /* + * if (should_grab_focus) + * { + * // First, set exclusive mode to grab input + * gtk_layer_set_keyboard_mode(this->gobj(), GTK_LAYER_SHELL_KEYBOARD_MODE_EXCLUSIVE); + * wl_surface_commit(get_wl_surface()); + * + * // Next, allow releasing of focus when clicking outside of the panel + * gtk_layer_set_keyboard_mode(this->gobj(), GTK_LAYER_SHELL_KEYBOARD_MODE_ON_DEMAND); + * } + */ + // TODO come back for intentional focus steal this->active_button->set_has_focus(should_grab_focus); schedule_show(0); @@ -429,7 +457,6 @@ void WayfireAutohidingWindow::unset_active_popover(WayfireMenuButton& button) this->active_button->set_has_focus(false); this->active_button->set_active(false); - this->active_button->get_popover()->popdown(); this->active_button = nullptr; this->popover_hide.disconnect(); @@ -454,20 +481,7 @@ void WayfireAutohidingWindow::setup_autohide() this->set_auto_exclusive_zone(!(output->output && autohide_opt)); this->update_autohide(); - this->signal_size_allocate().connect_notify( - [=] (Gtk::Allocation&) - { - // std::cerr << "set_auto_exclusive_zone: " << this->auto_exclusive_zone << std::endl; - this->update_auto_exclusive_zone(); - - // We have to check here as well, otherwise it enables hotspot when it shouldn't - if (!output->output || !(output->output && autohide_opt)) - { - return; - } - - this->setup_hotspot(); - }); + this->update_auto_exclusive_zone(); } void WayfireAutohidingWindow::update_autohide() diff --git a/src/util/wf-autohide-window.hpp b/src/util/wf-autohide-window.hpp index b96d14ae..2324c6d0 100644 --- a/src/util/wf-autohide-window.hpp +++ b/src/util/wf-autohide-window.hpp @@ -2,7 +2,7 @@ #define WF_AUTOHIDE_WINDOW_HPP #include -#include +#include #include "wf-popover.hpp" #include #include @@ -101,6 +101,9 @@ class WayfireAutohidingWindow : public Gtk::Window sigc::connection pending_show, pending_hide; bool m_do_show(); bool m_do_hide(); + + void start_draw_timer(); + gboolean update_animation(Glib::RefPtr fc); int autohide_counter = static_cast(autohide_opt); /** Show the window but hide if no pointer input */ diff --git a/src/util/wf-popover.cpp b/src/util/wf-popover.cpp index 7c51595f..a12c0ed7 100644 --- a/src/util/wf-popover.cpp +++ b/src/util/wf-popover.cpp @@ -6,21 +6,21 @@ WayfireMenuButton::WayfireMenuButton(const std::string& section) : panel_position{section + "/position"} { get_style_context()->add_class("flat"); - m_popover.set_constrain_to(Gtk::POPOVER_CONSTRAINT_NONE); + // m_popover.set_constrain_to(Gtk::POPOVER_CONSTRAINT_NONE); auto cb = [=] () { - set_direction((std::string)panel_position == "top" ? - Gtk::ARROW_DOWN : Gtk::ARROW_UP); + // set_direction((std::string)panel_position == "top" ? + // Gtk::Arrow::DOWN : Gtk::Arrow::UP); this->unset_popover(); - m_popover.set_constrain_to(Gtk::POPOVER_CONSTRAINT_NONE); + // m_popover.set_constrain_to(Gtk::POPOVER_CONSTRAINT_NONE); set_popover(m_popover); }; panel_position.set_callback(cb); cb(); - m_popover.signal_show().connect_notify([=] + m_popover.signal_show().connect([=] { set_active_on_window(); }); diff --git a/src/util/wf-shell-app.cpp b/src/util/wf-shell-app.cpp index 7eee3f0a..3b9f4e2c 100644 --- a/src/util/wf-shell-app.cpp +++ b/src/util/wf-shell-app.cpp @@ -1,11 +1,14 @@ #include "wf-shell-app.hpp" #include #include -#include +#include +#include #include #include #include #include +#include +#include #include @@ -64,9 +67,64 @@ std::string WayfireShellApp::get_css_config_dir() return css_directory; } +void WayfireShellApp::on_css_reload() +{ + clear_css_rules(); + /* Add user directory */ + std::string ext(".css"); + for (auto & p : std::filesystem::directory_iterator(get_css_config_dir())) + { + if (p.path().extension() == ext) + { + int priority = GTK_STYLE_PROVIDER_PRIORITY_USER; + if (p.path().filename() == "default.css") + { + priority = GTK_STYLE_PROVIDER_PRIORITY_APPLICATION; + } + + add_css_file(p.path().string(), priority); + } + } + + /* Add one user file */ + auto custom_css_config = WfOption{"panel/css_path"}; + std::string custom_css = custom_css_config; + if (custom_css != "") + { + add_css_file(custom_css, GTK_STYLE_PROVIDER_PRIORITY_USER); + } +} + +void WayfireShellApp::clear_css_rules() +{ + auto display = Gdk::Display::get_default(); + for (auto css_provider : css_rules) + { + Gtk::StyleContext::remove_provider_for_display(display, css_provider); + } + + css_rules.clear(); +} + +void WayfireShellApp::add_css_file(std::string file, int priority) +{ + auto display = Gdk::Display::get_default(); + if (file != "") + { + auto css_provider = load_css_from_path(file); + if (css_provider) + { + Gtk::StyleContext::add_provider_for_display( + display, css_provider, GTK_STYLE_PROVIDER_PRIORITY_USER); + css_rules.push_back(css_provider); + } + } +} + bool WayfireShellApp::parse_cfgfile(const Glib::ustring & option_name, const Glib::ustring & value, bool has_value) { + std::cout << "%%%%%%%%%%%%%%%%%%%%%%%" << std::endl; std::cout << "Using custom config file " << value << std::endl; cmdline_config = value; return true; @@ -172,28 +230,44 @@ void WayfireShellApp::on_activate() Glib::signal_io().connect( sigc::bind<0>(&handle_inotify_event, this), - inotify_fd, Glib::IO_IN | Glib::IO_HUP); + inotify_fd, Glib::IOCondition::IO_IN | Glib::IOCondition::IO_HUP); Glib::signal_io().connect( sigc::bind<0>(&handle_css_inotify_event, this), - inotify_css_fd, Glib::IO_IN | Glib::IO_HUP); + inotify_css_fd, Glib::IOCondition::IO_IN | Glib::IOCondition::IO_HUP); // Hook up monitor tracking - auto display = Gdk::Display::get_default(); - display->signal_monitor_added().connect_notify( - [=] (const GMonitor& monitor) { this->add_output(monitor); }); - display->signal_monitor_removed().connect_notify( - [=] (const GMonitor& monitor) { this->rem_output(monitor); }); + auto display = Gdk::Display::get_default(); + auto monitors = display->get_monitors(); + monitors->signal_items_changed().connect(sigc::mem_fun(*this, &WayfireShellApp::output_list_updated)); // initial monitors - int num_monitors = display->get_n_monitors(); + int num_monitors = monitors->get_n_items(); for (int i = 0; i < num_monitors; i++) { - add_output(display->get_monitor(i)); + auto obj = std::dynamic_pointer_cast(monitors->get_object(i)); + add_output(obj); + } +} + +void WayfireShellApp::output_list_updated(const int pos, const int rem, const int add) +{ + auto display = Gdk::Display::get_default(); + auto monitors = display->get_monitors(); + for (int i = 0; i < add; i++) + { + auto obj = std::dynamic_pointer_cast(monitors->get_object(i + pos)); + add_output(obj); } } void WayfireShellApp::add_output(GMonitor monitor) { + // Remove self when unplugged + monitor->signal_invalidate().connect([=] + { + rem_output(monitor); + }); + // Add to list monitors.push_back( std::make_unique(monitor, this->wf_shell_manager)); handle_new_output(monitors.back().get()); @@ -211,17 +285,17 @@ void WayfireShellApp::rem_output(GMonitor monitor) } } -WayfireShellApp::WayfireShellApp(int argc, char **argv) +WayfireShellApp::WayfireShellApp() { - app = Gtk::Application::create(argc, argv, "", - Gio::APPLICATION_HANDLES_COMMAND_LINE); - app->signal_activate().connect_notify( - sigc::mem_fun(this, &WayfireShellApp::on_activate)); + std::cout << "setting up" << std::endl; + app = Gtk::Application::create("", Gio::Application::Flags::HANDLES_COMMAND_LINE); + app->signal_activate().connect( + sigc::mem_fun(*this, &WayfireShellApp::on_activate)); app->add_main_option_entry( - sigc::mem_fun(this, &WayfireShellApp::parse_cfgfile), + sigc::mem_fun(*this, &WayfireShellApp::parse_cfgfile), "config", 'c', "config file to use", "file"); app->add_main_option_entry( - sigc::mem_fun(this, &WayfireShellApp::parse_cssfile), + sigc::mem_fun(*this, &WayfireShellApp::parse_cssfile), "css", 's', "css style directory to use", "directory"); // Activate app after parsing command line @@ -240,9 +314,9 @@ WayfireShellApp& WayfireShellApp::get() return *instance; } -void WayfireShellApp::run() +void WayfireShellApp::run(int argc, char **argv) { - app->run(); + app->run(argc, argv); } /* -------------------------- WayfireOutput --------------------------------- */ diff --git a/src/util/wf-shell-app.hpp b/src/util/wf-shell-app.hpp index 90f2f2b1..44c1357b 100644 --- a/src/util/wf-shell-app.hpp +++ b/src/util/wf-shell-app.hpp @@ -1,12 +1,12 @@ #ifndef WF_SHELL_APP_HPP #define WF_SHELL_APP_HPP -#include #include #include #include #include +#include #include "wayfire-shell-unstable-v2-client-protocol.h" @@ -36,6 +36,7 @@ class WayfireShellApp { private: std::vector> monitors; + std::vector> css_rules; protected: /** This should be initialized by the subclass in each program which uses @@ -46,6 +47,7 @@ class WayfireShellApp Glib::RefPtr app; + void output_list_updated(int pos, int rem, int add); virtual void add_output(GMonitor monitor); virtual void rem_output(GMonitor monitor); @@ -67,18 +69,19 @@ class WayfireShellApp wf::config::config_manager_t config; zwf_shell_manager_v2 *wf_shell_manager = nullptr; - WayfireShellApp(int argc, char **argv); + WayfireShellApp(); virtual ~WayfireShellApp(); virtual std::string get_config_file(); virtual std::string get_css_config_dir(); - virtual void run(); + virtual void run(int argc, char **argv); virtual void on_config_reload() {} + void on_css_reload(); + void clear_css_rules(); + void add_css_file(std::string file, int priority); - virtual void on_css_reload() - {} /** * WayfireShellApp is a singleton class. diff --git a/subprojects/gtk-layer-shell b/subprojects/gtk-layer-shell deleted file mode 160000 index 0ed957ce..00000000 --- a/subprojects/gtk-layer-shell +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0ed957ce262c1063a011b16b59cda5c5de4b3b47 diff --git a/subprojects/gtk4-layer-shell b/subprojects/gtk4-layer-shell new file mode 160000 index 00000000..fc437389 --- /dev/null +++ b/subprojects/gtk4-layer-shell @@ -0,0 +1 @@ +Subproject commit fc437389895c4cf88d00cc9b37b68004cc30c07f