Layer-shell support (#1)

* proto: added layer-shell support

* added clara header file

helper for parsing the cli options.

* cli: added command line options

* style: fixed style issues

* exiting main loop on close

* port to gtk-layer-shell

* fixup! port to gtk-layer-shell

* implement custom header-bar

* update wayfire-shell-v2
This commit is contained in:
Matteo Sozzi 2019-09-10 23:31:08 +02:00 committed by Ilia Bozhinov
parent fe233eef74
commit d28bd69671
10 changed files with 1553 additions and 247 deletions

View File

@ -16,6 +16,7 @@ project(
gtkmm = dependency('gtkmm-3.0')
wayland_client = dependency('wayland-client')
wayland_protos = dependency('wayland-protocols')
gtkls = dependency('gtk-layer-shell-0')
add_project_link_arguments(['-rdynamic'], language:'cpp')
add_project_arguments(['-Wno-unused-parameter'], language: 'cpp')

View File

@ -15,8 +15,8 @@ wayland_scanner_client = generator(
)
client_protocols = [
'wayfire-shell.xml',
'virtual-keyboard-unstable-v1.xml'
['wayfire-shell-unstable-v2.xml'],
['virtual-keyboard-unstable-v1.xml']
]
wl_protos_src = []

View File

@ -0,0 +1,120 @@
<protocol name="wayfire_shell">
<interface name="zwf_shell_manager_v2" version="1">
<description summary="DE integration">
This protocol provides additional events and requests for special DE
clients like panels, docks, etc.
It is meant as an addition for protocols like wlr-layer-shell.
</description>
<request name="get_wf_output">
<description summary="Create a zwf_output_v2 for the given wl_output"/>
<arg name="output" type="object" interface="wl_output"/>
<arg name="id" type="new_id" interface="zwf_output_v2"/>
</request>
<request name="get_wf_surface">
<description summary="Create a zwf_surface_v2 for the given wl_surface"/>
<arg name="surface" type="object" interface="wl_surface"/>
<arg name="id" type="new_id" interface="zwf_surface_v2"/>
</request>
</interface>
<interface name="zwf_output_v2" version="1">
<description summary="A wrapper for wl_output">
Represents a single output.
Each output is managed independently from the others.
</description>
<event name="enter_fullscreen">
<description summary="A window was fullscreened">
Emitted when a window gets fullscreened on the given output. In this
mode, windows in the TOP layer are not visible.
There will be no two consecutive enter_fullscreen calls, i.e. if
fullscreen mode is entered it will be exited before going into this mode
again.
</description>
</event>
<event name="leave_fullscreen">
<description summary="A window was fullscreened">
Emitted when the output is no longer in fullscreen mode. Each
leave_fullscreen has a corresponding enter_fullscreen before it.
</description>
</event>
<request name="inhibit_output">
<description summary="Don't render the output">
Request the compositor to not render the output, so the output usually
is cleared to black color. To enable output rendering again, call
inhibit_output_done.
</description>
</request>
<request name="inhibit_output_done">
<description summary="Render the output">
Stop inhibiting the output. This must be called as many times as
inhibit_output was called to actually uninhibit rendering.
The inhibit/inhibit_done requests can be called multiple times, even
from different apps, so don't assume that a call to inhibit_done would
always mean actually starting the rendering process.
</description>
</request>
<enum name="hotspot_edge">
<entry name="top" value="1"/>
<entry name="bottom" value="2"/>
<entry name="left" value="4"/>
<entry name="right" value="8"/>
</enum>
<request name="create_hotspot">
<description summary="Create a hotspot on the output">
A hotspot on the output is an edge or a corner region of the
output where the mouse or touch point has been residing for a given
amount of time.
The hotspot can be used for example for autohiding panels, where the
panel is shown when the input hovers on the edge of the output for a
specific amount of time.
</description>
<arg name="hotspot" type="uint" summary="bitwise or of the edges the output"/>
<arg name="threshold" type="uint" summary="distance from the edge of the output"/>
<arg name="timeout" type="uint" summary="minimum time for the mouse to be in the hotspot"/>
<arg name="id" type="new_id" interface="zwf_hotspot_v2"/>
</request>
</interface>
<interface name="zwf_hotspot_v2" version="1">
<description summary="An edge of the output defined by 1 or 2 edges"/>
<event name="enter">
<description summary="Hotspot was triggered">
Means that the mouse and/or touch finger was inside the indicated
hotspot for the given amount of time.
Emitted at most once for each entry of the input inside the hotspot.
</description>
</event>
<event name="leave">
<description summary="Input left hotspot">
This event indicates that the mouse or touch point has left the hotspot
area.
Emitted only once after each enter.
</description>
</event>
</interface>
<interface name="zwf_surface_v2" version="1">
<description summary="A special surface"/>
<request name="interactive_move">
<description summary="Start an interactive move of the surface"/>
</request>
</interface>
</protocol>

View File

@ -1,172 +0,0 @@
<protocol name="wayfire_shell">
<interface name="zwf_shell_manager_v1" version="1">
<description summary="DE integration">
IMPORTANT: most of wayfire-shell is going to be deprecated. Try to
use layer-shell instead.
The purpose of this protocol is to enable the creation of different
desktop-interface windows like panels, backgrounds, docks,
lockscreens, etc. It also aims to allow the creation of full-blown
DEs using Wayfire.
Note that in contrast to some other efforts to create a similar
protocol, such as wlr-layer-shell, this isn't a new "shell" for
giving a role to wl_surfaces. This protocol can be used with any
type of toplevel surface (xdg_toplevel, xdg_toplevel_v6, etc.)
to give them to the corresponding WM role.
</description>
<request name="get_wf_output">
<description summary="Create a wf_output from the given output"/>
<arg name="output" type="object" interface="wl_output"/>
<arg name="id" type="new_id" interface="zwf_output_v1"/>
</request>
<request name="get_wm_surface">
<description summary="Assign a role">
Assign the given role to the given surface and add it to the
given output. A client can specify a null output, in which case
the compositor will assign the surface to the focused output,
if any such output.
The role cannot be changed later, and neither can the surface be
moved to a different output, except by the compositor.
</description>
<arg name="surface" type="object" interface="wl_surface"/>
<arg name="role" type="uint" enum="zwf_wm_surface_v1.role"/>
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
<arg name="id" type="new_id" interface="zwf_wm_surface_v1"/>
</request>
</interface>
<interface name="zwf_output_v1" version="1">
<description summary="A wrapper for wl_output">
Represents a single output.
Each output is managed independently from the others.
</description>
<event name="output_hide_panels">
<description summary="Autohide/Show signal">
Panels are always rendered on top, even above fullscreen windows.
If autohide is 1, the event indicates that the panels should hide
itself, by for example unmapping or sliding outside of the output.
If autohide is 0, this means that the reason for the last request
with autohide == 1 is no longer valid, i.e the panels can show
themselves.
The output_hide_panels can be called multiple times with
autohide = 1, and the panel should show itself only when
it has received a matching number of events with autohide = 0
</description>
<arg name="autohide" type="uint"/>
</event>
<request name="inhibit_output">
<description summary="Don't render the output">
Request the compositor to not render the output, so
the output usually is cleared to black color.
To enable output rendering again, call inhibit_output_done
</description>
</request>
<request name="inhibit_output_done">
<description summary="Render the output">
Stop inhibiting the output. This must be called as many times
as inhibit_output was called to actually uninhibit rendering.
The inhibit/inhibit_done requests can be called multiple times,
even from different apps, so don't assume that a call to
inhibit_done would always mean actually starting the rendering process.
</description>
</request>
</interface>
<interface name="zwf_wm_surface_v1" version="1">
<description summary="Surface with a WM role">
Represents a surface with a specific WM role.
It belongs to the output which it was created for.
</description>
<enum name="role">
<entry name="background" value="1"/>
<entry name="bottom" value="2"/>
<entry name="panel" value="3"/>
<entry name="overlay" value="4"/>
<entry name="desktop_widget" value="5"/>
</enum>
<request name="configure">
<description summary="Move the surface to the given output-local coordinates."/>
<arg name="x" type="int"/>
<arg name="y" type="int"/>
</request>
<enum name="anchor_edge">
<entry name="top" value="1"/>
<entry name="bottom" value="2"/>
<entry name="left" value="4"/>
<entry name="right" value="8"/>
</enum>
<request name="set_anchor">
<description summary="set anchor position">
Sets the position on the screen where the compositor should
position the view. Can be reset by specifying anchor 0. If not
set, the compositor will assume manual positioning via the
configure request.
If one anchor edge is provided, the wm surface is "stuck" to
that edge.
If two anchor edges are provided, the wm surface is considered
anchored to the corner of the screen between them.
Any other anchor edge configuration is considered invalid.
</description>
<arg name="anchors" type="uint"/>
</request>
<request name="set_margin">
<description summary="set margin respective to anchored edges">
Set the offset from the anchored edges to the wm surface. This
is an alternative to the configure request. Using both will
result in undefined results.
Margin has effect only for edges the wm surface is anchored to.
</description>
<arg name="top" type="int"/>
<arg name="bottom" type="int"/>
<arg name="left" type="int"/>
<arg name="right" type="int"/>
</request>
<enum name="keyboard_focus_mode">
<entry name="no_focus" value="0"/>
<entry name="click_to_focus" value="1"/>
<entry name="exclusive_focus" value="2"/>
</enum>
<request name="set_keyboard_mode">
<description summary="Set keyboard focus mode">
Sets how the wm surface will interact with keyboard focus.
Setting no_focus means that the surface will never receive
keyboard focus, click_to_focus means normal focus semantics (i.e
what you expect from "normal" windows), and exclusive focus means
that no other window can get keyboard focus.
</description>
<arg name="mode" type="uint" enum="keyboard_focus_mode"/>
</request>
<request name="set_exclusive_zone">
<description summary="Reserve pixels">
Request the compositor to reserve the given amount of pixels
for the wm surface(like STRUTS in X11). This has effect only
if the surface is anchored to a single edge. Margin doesn't
affect exclusive zone in any way.
</description>
<arg name="size" type="uint"/>
</request>
</interface>
</protocol>

View File

@ -3,6 +3,8 @@
#include <iostream>
#include <linux/input-event-codes.h>
#include "util/clara.hpp"
#define ABC_TOGGLE 0x12345678
#define NUM_TOGGLE 0x87654321
@ -14,11 +16,10 @@ namespace wf
{
namespace osk
{
int spacing = 8;
int default_x = 100;
int default_y = 100;
int spacing = OSK_SPACING;
int default_width = 800;
int default_height = 400;
std::string anchor;
KeyButton::KeyButton(Key key, int width, int height)
{
@ -111,18 +112,14 @@ namespace wf
void Keyboard::set_layout(KeyboardLayout *new_layout)
{
if (this->current_layout)
this->window->remove();
this->current_layout = new_layout;
this->window->add(new_layout->box);
this->window->show_all();
window->set_widget(new_layout->box);
}
Keyboard::Keyboard()
{
window = std::make_unique<WaylandWindow>
(default_x, default_y, default_width, default_height);
(default_width, default_height, anchor);
vk = std::make_unique<VirtualKeyboardDevice> ();
init_layouts();
@ -175,33 +172,25 @@ namespace wf
int main(int argc, char **argv)
{
struct option opts[] = {
{ "geometry", required_argument, NULL, 'g' },
{ 0, 0, NULL, 0 }
};
bool show_help = false;
int c, i;
while((c = getopt_long(argc, argv, "g:", opts, &i)) != -1)
{
using namespace wf::osk;
switch(c)
{
case 'g':
if (sscanf(optarg, "%d,%d %dx%d", &default_x, &default_y,
&default_width, &default_height) != 4)
{
std::cerr << "Invalid geometry: " << optarg << std::endl;
std::exit(-1);
} else
{
std::cout << "Geometry " << default_x << "," << default_y << " "
<< default_width << "x" << default_height;
auto cli = clara::detail::Help(show_help) |
clara::detail::Opt(wf::osk::default_width, "int")["-w"]["--width"]
("keyboard width") |
clara::detail::Opt(wf::osk::default_height, "int")["-h"]["--height"]
("keyboard height") |
clara::detail::Opt(wf::osk::anchor, "top|left|bottom|right")["-a"]
["--anchor"]("where the keyboard should anchor in the screen");
auto res = cli.parse(clara::detail::Args(argc, argv));
if (!res) {
std::cerr << "Error: " << res.errorMessage() << std::endl;
return 1;
}
break;
default:
std::cerr << "Unrecognized argument " << char(c) << std::endl;
}
if (show_help) {
std::cout << cli << std::endl;
return 0;
}
auto app = Gtk::Application::create();

View File

@ -1,3 +1,3 @@
executable('wf-osk', ['main.cpp', 'wayland-window.cpp', 'virtual-keyboard.cpp', 'shared/os-compatibility.c'],
dependencies: [gtkmm, wf_protos],
dependencies: [gtkmm, wf_protos, gtkls],
install: true)

1264
src/util/clara.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,14 +7,18 @@
#include <time.h>
#include <iostream>
#include <gdkmm/display.h>
#include <gdkmm/seat.h>
#include <gdk/gdkwayland.h>
namespace wf
{
VirtualKeyboardDevice::VirtualKeyboardDevice()
{
auto& display = WaylandDisplay::get();
auto seat = Gdk::Display::get_default()->get_default_seat();
vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(
display.vk_manager, display.seat);
display.vk_manager, gdk_wayland_seat_get_wl_seat(seat->gobj()));
this->send_keymap();
}

View File

@ -1,28 +1,31 @@
#include "wayland-window.hpp"
#include <iostream>
#include <algorithm>
#include <gtkmm/icontheme.h>
#include <gtkmm/main.h>
#include <gdk/gdkwayland.h>
#include <wayland-client.h>
#include <gtk-layer-shell.h>
#include <gtkmm/headerbar.h>
#include <gdkmm/display.h>
#include <gdkmm/seat.h>
static constexpr int HEADERBAR_SIZE = 60;
namespace wf
{
// listeners
static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version)
static void registry_add_object(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{
auto display = static_cast<WaylandDisplay*> (data);
if (strcmp(interface, wl_seat_interface.name) == 0 && !display->seat)
if (strcmp(interface, zwf_shell_manager_v2_interface.name) == 0)
{
display->seat = (wl_seat*) wl_registry_bind(registry, name,
&wl_seat_interface, std::min(version, 1u));
}
if (strcmp(interface, zwf_shell_manager_v1_interface.name) == 0)
{
display->wf_manager =
(zwf_shell_manager_v1*) wl_registry_bind(registry, name,
&zwf_shell_manager_v1_interface,
std::min(version, 1u));
display->zwf_manager =
(zwf_shell_manager_v2*) wl_registry_bind(registry, name,
&zwf_shell_manager_v2_interface, std::min(version, 1u));
}
if (strcmp(interface, zwp_virtual_keyboard_manager_v1_interface.name) == 0)
@ -58,12 +61,13 @@ namespace wf
wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, this);
wl_display_dispatch(display);
wl_display_roundtrip(display);
if (!vk_manager || !seat || !wf_manager)
if (!vk_manager)
{
std::cerr << "Compositor doesn't support the virtual-keyboard-v1 "
<< "and/or the wayfire-shell protocols, exiting" << std::endl;
<< "protocol, exiting" << std::endl;
std::exit(-1);
}
}
@ -74,30 +78,108 @@ namespace wf
return instance;
}
WaylandWindow::WaylandWindow(int x, int y, int width, int height)
: Gtk::Window()
int32_t WaylandWindow::check_anchor(std::string anchor)
{
auto display = WaylandDisplay::get();
std::transform(anchor.begin(), anchor.end(), anchor.begin(), ::tolower);
int32_t parsed_anchor = -1;
if (anchor.compare("top") == 0)
{
parsed_anchor = GTK_LAYER_SHELL_EDGE_TOP;
} else if (anchor.compare("bottom") == 0)
{
parsed_anchor = GTK_LAYER_SHELL_EDGE_BOTTOM;
} else if (anchor.compare("left") == 0)
{
parsed_anchor = GTK_LAYER_SHELL_EDGE_LEFT;
} else if (anchor.compare("right") == 0)
{
parsed_anchor = GTK_LAYER_SHELL_EDGE_RIGHT;
}
return parsed_anchor;
}
void WaylandWindow::init(int width, int height, std::string anchor)
{
gtk_layer_init_for_window(this->gobj());
gtk_layer_set_layer(this->gobj(), GTK_LAYER_SHELL_LAYER_OVERLAY);
gtk_layer_set_namespace(this->gobj(), "keyboard");
auto layer_anchor = check_anchor(anchor);
if (layer_anchor > -1)
{
gtk_layer_set_anchor(this->gobj(),
(GtkLayerShellEdge)layer_anchor, true);
}
/* Trick: first show the window, get frame size, then subtract it again */
this->set_size_request(width, height);
this->set_default_size(width, height);
this->set_type_hint(Gdk::WINDOW_TYPE_HINT_DOCK);
this->show_all();
auto gdk_window = this->get_window()->gobj();
auto surface = gdk_wayland_window_get_wl_surface(gdk_window);
if (!surface)
if (surface && WaylandDisplay::get().zwf_manager)
{
std::cerr << "Error: created window was not a wayland surface" << std::endl;
std::exit(-1);
this->wf_surface = zwf_shell_manager_v2_get_wf_surface(
WaylandDisplay::get().zwf_manager, surface);
}
}
wm_surface = zwf_shell_manager_v1_get_wm_surface(display.wf_manager,
surface, ZWF_WM_SURFACE_V1_ROLE_DESKTOP_WIDGET, NULL);
zwf_wm_surface_v1_set_keyboard_mode(wm_surface,
ZWF_WM_SURFACE_V1_KEYBOARD_FOCUS_MODE_NO_FOCUS);
zwf_wm_surface_v1_configure(wm_surface, x, y);
WaylandWindow::WaylandWindow(int width, int height, std::string anchor)
: Gtk::Window()
{
// setup close button
close_button.get_style_context()->add_class("image-button");
close_button.set_image_from_icon_name("window-close-symbolic",
Gtk::ICON_SIZE_LARGE_TOOLBAR);
close_button.signal_clicked().connect_notify([=] () {
this->get_application()->quit();
});
// setup move gesture
headerbar_drag = Gtk::GestureDrag::create(drag_box);
headerbar_drag->signal_drag_begin().connect_notify([=] (double, double) {
if (this->wf_surface)
{
zwf_surface_v2_interactive_move(this->wf_surface);
/* Taken from GDK's Wayland impl of begin_move_drag() */
Gdk::Display::get_default()->get_default_seat()->ungrab();
headerbar_drag->reset();
}
});
Gtk::HeaderBar bar;
headerbar_box.override_background_color(bar.get_style_context()->get_background_color());
// setup headerbar layout
headerbar_box.set_size_request(-1, HEADERBAR_SIZE);
close_button.set_size_request(HEADERBAR_SIZE * 0.8, HEADERBAR_SIZE * 0.8);
close_button.set_margin_bottom(OSK_SPACING);
close_button.set_margin_top(OSK_SPACING);
close_button.set_margin_left(OSK_SPACING);
close_button.set_margin_right(OSK_SPACING);
headerbar_box.pack_end(close_button, false, false);
headerbar_box.pack_start(drag_box, true, true);
layout_box.pack_start(headerbar_box);
layout_box.set_spacing(OSK_SPACING);
this->add(layout_box);
// setup gtk layer shell
init(width, height, anchor);
}
void WaylandWindow::set_widget(Gtk::Widget& w)
{
if (current_widget)
this->layout_box.remove(*current_widget);
this->layout_box.pack_end(w);
current_widget = &w;
w.set_margin_bottom(OSK_SPACING);
w.set_margin_left(OSK_SPACING);
w.set_margin_right(OSK_SPACING);
this->show_all();
}
}

View File

@ -1,9 +1,16 @@
#pragma once
#include <gtkmm/hvbox.h>
#include <gtkmm/button.h>
#include <gtkmm/window.h>
#include <wayfire-shell-client-protocol.h>
#include <gtkmm/eventbox.h>
#include <gtkmm/gesturedrag.h>
#include <gtkmm/headerbar.h>
#include <wayfire-shell-unstable-v2-client-protocol.h>
#include <virtual-keyboard-unstable-v1-client-protocol.h>
#define OSK_SPACING 8
namespace wf
{
class WaylandDisplay
@ -13,15 +20,26 @@ namespace wf
public:
static WaylandDisplay& get();
wl_seat *seat = nullptr;
zwf_shell_manager_v1 *wf_manager = nullptr;
zwf_shell_manager_v2 *zwf_manager = nullptr;
zwp_virtual_keyboard_manager_v1 *vk_manager = nullptr;
};
class WaylandWindow : public Gtk::Window
{
zwf_wm_surface_v1 *wm_surface;
zwf_surface_v2 *wf_surface = nullptr;
Gtk::Widget* current_widget = nullptr;
Glib::RefPtr<Gtk::GestureDrag> headerbar_drag;
Gtk::EventBox drag_box;
Gtk::Button close_button;
Gtk::HBox headerbar_box;
Gtk::VBox layout_box;
int32_t check_anchor(std::string anchor);
void init(int width, int height, std::string anchor);
public:
WaylandWindow(int x, int y, int width, int height);
WaylandWindow(int width, int height, std::string anchor);
void set_widget(Gtk::Widget& w);
};
}