From 55c431505e86423dbb6fc8a84d53a2b79b923975 Mon Sep 17 00:00:00 2001 From: trigg Date: Thu, 11 Jun 2026 10:47:51 +0100 Subject: [PATCH 1/4] chore: uncrustify --- src/keymap.tpp | 413 +++--- src/layouts.tpp | 4 + src/main.cpp | 280 ++-- src/osk.hpp | 130 +- src/shared/os-compatibility.c | 208 ++- src/shared/os-compatibility.h | 14 +- src/util/clara.hpp | 2391 ++++++++++++++++++++------------- src/virtual-keyboard.cpp | 90 +- src/virtual-keyboard.hpp | 20 +- src/wayland-window.cpp | 353 ++--- src/wayland-window.hpp | 48 +- 11 files changed, 2218 insertions(+), 1733 deletions(-) diff --git a/src/keymap.tpp b/src/keymap.tpp index d97c6ca..eb47233 100644 --- a/src/keymap.tpp +++ b/src/keymap.tpp @@ -1,4 +1,5 @@ -static const char keymap[] = "xkb_keymap {\ +static const char keymap[] = + "xkb_keymap {\ xkb_keycodes \"(unnamed)\" {\ minimum = 8;\ maximum = 255;\ @@ -1168,67 +1169,67 @@ xkb_compatibility \"(unnamed)\" {\ xkb_symbols \"(unnamed)\" {\ name[group1]=\"English (US)\";\ \ - key { [ Escape ] };\ - key { [ 1, exclam ] };\ - key { [ 2, at ] };\ - key { [ 3, numbersign ] };\ - key { [ 4, dollar ] };\ - key { [ 5, percent ] };\ - key { [ 6, asciicircum ] };\ - key { [ 7, ampersand ] };\ - key { [ 8, asterisk ] };\ - key { [ 9, parenleft ] };\ - key { [ 0, parenright ] };\ - key { [ minus, underscore ] };\ - key { [ equal, plus ] };\ - key { [ BackSpace, BackSpace ] };\ - key { [ Tab, ISO_Left_Tab ] };\ - key { [ q, Q, 1 ] };\ - key { [ w, W, 2 ] };\ - key { [ e, E, 3 ] };\ - key { [ r, R, 4 ] };\ - key { [ t, T, 5 ] };\ - key { [ y, Y, 6 ] };\ - key { [ u, U, 7 ] };\ - key { [ i, I, 8 ] };\ - key { [ o, O, 9 ] };\ - key { [ p, P, 0 ] };\ - key { [ bracketleft, braceleft ] };\ - key { [ bracketright, braceright ] };\ - key { [ Return ] };\ - key { [ Control_L ] };\ - key { [ a, A, minus ] };\ - key { [ s, S, at ] };\ - key { [ d, D, asterisk ] };\ - key { [ f, F, asciicircum ] };\ - key { [ g, G, colon ] };\ - key { [ h, H, semicolon ] };\ - key { [ j, J, parenleft ] };\ - key { [ k, K, parenright ] };\ - key { [ l, L, asciitilde ] };\ - key { [ semicolon, colon ] };\ - key { [ apostrophe, quotedbl ] };\ - key { [ grave, asciitilde ] };\ - key { [ Shift_L ] };\ - key { [ backslash, bar ] };\ - key { [ z, Z, slash ] };\ - key { [ x, X, apostrophe ] };\ - key { [ c, C, quotedbl ] };\ - key { [ v, V, plus ] };\ - key { [ b, B, equal ] };\ - key { [ n, N, question ] };\ - key { [ m, M, exclam ] };\ - key { [ comma, less, backslash] };\ - key { [ period, greater, bar ] };\ - key { [ slash, question ] };\ - key { [ Shift_R ] };\ + key {\t[ Escape ] };\ + key {\t[ 1, exclam ] };\ + key {\t[ 2, at ] };\ + key {\t[ 3, numbersign ] };\ + key {\t[ 4, dollar ] };\ + key {\t[ 5, percent ] };\ + key {\t[ 6, asciicircum ] };\ + key {\t[ 7, ampersand ] };\ + key {\t[ 8, asterisk ] };\ + key {\t[ 9, parenleft ] };\ + key {\t[ 0, parenright ] };\ + key {\t[ minus, underscore ] };\ + key {\t[ equal, plus ] };\ + key {\t[ BackSpace, BackSpace ] };\ + key {\t[ Tab, ISO_Left_Tab ] };\ + key {\t[ q, Q, 1 ] };\ + key {\t[ w, W, 2 ] };\ + key {\t[ e, E, 3 ] };\ + key {\t[ r, R, 4 ] };\ + key {\t[ t, T, 5 ] };\ + key {\t[ y, Y, 6 ] };\ + key {\t[ u, U, 7 ] };\ + key {\t[ i, I, 8 ] };\ + key {\t[ o, O, 9 ] };\ + key {\t[ p, P, 0 ] };\ + key {\t[ bracketleft, braceleft ] };\ + key {\t[ bracketright, braceright ] };\ + key {\t[ Return ] };\ + key {\t[ Control_L ] };\ + key {\t[ a, A, minus ] };\ + key {\t[ s, S, at ] };\ + key {\t[ d, D, asterisk ] };\ + key {\t[ f, F, asciicircum ] };\ + key {\t[ g, G, colon ] };\ + key {\t[ h, H, semicolon ] };\ + key {\t[ j, J, parenleft ] };\ + key {\t[ k, K, parenright ] };\ + key {\t[ l, L, asciitilde ] };\ + key {\t[ semicolon, colon ] };\ + key {\t[ apostrophe, quotedbl ] };\ + key {\t[ grave, asciitilde ] };\ + key {\t[ Shift_L ] };\ + key {\t[ backslash, bar ] };\ + key {\t[ z, Z, slash ] };\ + key {\t[ x, X, apostrophe ] };\ + key {\t[ c, C, quotedbl ] };\ + key {\t[ v, V, plus ] };\ + key {\t[ b, B, equal ] };\ + key {\t[ n, N, question ] };\ + key {\t[ m, M, exclam ] };\ + key {\t[ comma, less, backslash] };\ + key {\t[ period, greater, bar ] };\ + key {\t[ slash, question ] };\ + key {\t[ Shift_R ] };\ key {\ type= \"CTRL+ALT\",\ symbols[Group1]= [ KP_Multiply, KP_Multiply, KP_Multiply, KP_Multiply, XF86ClearGrab ]\ };\ - key { [ Alt_L, Meta_L ] };\ - key { [ space ] };\ - key { [ Caps_Lock ] };\ + key {\t[ Alt_L, Meta_L ] };\ + key {\t[ space ] };\ + key {\t[ Caps_Lock ] };\ key {\ type= \"CTRL+ALT\",\ symbols[Group1]= [ F1, F1, F1, F1, XF86Switch_VT_1 ]\ @@ -1269,29 +1270,29 @@ xkb_symbols \"(unnamed)\" {\ type= \"CTRL+ALT\",\ symbols[Group1]= [ F10, F10, F10, F10, XF86Switch_VT_10 ]\ };\ - key { [ Num_Lock ] };\ - key { [ Scroll_Lock ] };\ - key { [ KP_Home, KP_7 ] };\ - key { [ KP_Up, KP_8 ] };\ - key { [ KP_Prior, KP_9 ] };\ + key {\t[ Num_Lock ] };\ + key {\t[ Scroll_Lock ] };\ + key {\t[ KP_Home, KP_7 ] };\ + key {\t[ KP_Up, KP_8 ] };\ + key {\t[ KP_Prior, KP_9 ] };\ key {\ type= \"CTRL+ALT\",\ symbols[Group1]= [ KP_Subtract, KP_Subtract, KP_Subtract, KP_Subtract, XF86Prev_VMode ]\ };\ - key { [ KP_Left, KP_4 ] };\ - key { [ KP_Begin, KP_5 ] };\ - key { [ KP_Right, KP_6 ] };\ + key {\t[ KP_Left, KP_4 ] };\ + key {\t[ KP_Begin, KP_5 ] };\ + key {\t[ KP_Right, KP_6 ] };\ key {\ type= \"CTRL+ALT\",\ symbols[Group1]= [ KP_Add, KP_Add, KP_Add, KP_Add, XF86Next_VMode ]\ };\ - key { [ KP_End, KP_1 ] };\ - key { [ KP_Down, KP_2 ] };\ - key { [ KP_Next, KP_3 ] };\ - key { [ KP_Insert, KP_0 ] };\ - key { [ KP_Delete, KP_Decimal ] };\ - key { [ ISO_Level3_Shift ] };\ - key { [ less, greater, bar, brokenbar ] };\ + key {\t[ KP_End, KP_1 ] };\ + key {\t[ KP_Down, KP_2 ] };\ + key {\t[ KP_Next, KP_3 ] };\ + key {\t[ KP_Insert, KP_0 ] };\ + key {\t[ KP_Delete, KP_Decimal ] };\ + key {\t[ ISO_Level3_Shift ] };\ + key {\t[ less, greater, bar, brokenbar ] };\ key {\ type= \"CTRL+ALT\",\ symbols[Group1]= [ F11, F11, F11, F11, XF86Switch_VT_11 ]\ @@ -1300,13 +1301,13 @@ xkb_symbols \"(unnamed)\" {\ type= \"CTRL+ALT\",\ symbols[Group1]= [ F12, F12, F12, F12, XF86Switch_VT_12 ]\ };\ - key { [ Katakana ] };\ - key { [ Hiragana ] };\ - key { [ Henkan_Mode ] };\ - key { [ Hiragana_Katakana ] };\ - key { [ Muhenkan ] };\ - key { [ KP_Enter ] };\ - key { [ Control_R ] };\ + key {\t[ Katakana ] };\ + key {\t[ Hiragana ] };\ + key {\t[ Henkan_Mode ] };\ + key {\t[ Hiragana_Katakana ] };\ + key {\t[ Muhenkan ] };\ + key {\t[ KP_Enter ] };\ + key {\t[ Control_R ] };\ key {\ type= \"CTRL+ALT\",\ symbols[Group1]= [ KP_Divide, KP_Divide, KP_Divide, KP_Divide, XF86Ungrab ]\ @@ -1319,136 +1320,136 @@ xkb_symbols \"(unnamed)\" {\ type= \"TWO_LEVEL\",\ symbols[Group1]= [ Alt_R, Meta_R ]\ };\ - key { [ Linefeed ] };\ - key { [ Home ] };\ - key { [ Up ] };\ - key { [ Prior ] };\ - key { [ Left ] };\ - key { [ Right ] };\ - key { [ End ] };\ - key { [ Down ] };\ - key { [ Next ] };\ - key { [ Insert ] };\ - key { [ Delete ] };\ - key { [ XF86AudioMute ] };\ - key { [ XF86AudioLowerVolume ] };\ - key { [ XF86AudioRaiseVolume ] };\ - key { [ XF86PowerOff ] };\ - key { [ KP_Equal ] };\ - key { [ plusminus ] };\ + key {\t[ Linefeed ] };\ + key {\t[ Home ] };\ + key {\t[ Up ] };\ + key {\t[ Prior ] };\ + key {\t[ Left ] };\ + key {\t[ Right ] };\ + key {\t[ End ] };\ + key {\t[ Down ] };\ + key {\t[ Next ] };\ + key {\t[ Insert ] };\ + key {\t[ Delete ] };\ + key {\t[ XF86AudioMute ] };\ + key {\t[ XF86AudioLowerVolume ] };\ + key {\t[ XF86AudioRaiseVolume ] };\ + key {\t[ XF86PowerOff ] };\ + key {\t[ KP_Equal ] };\ + key {\t[ plusminus ] };\ key {\ type= \"PC_CONTROL_LEVEL2\",\ symbols[Group1]= [ Pause, Break ]\ };\ - key { [ XF86LaunchA ] };\ - key { [ KP_Decimal, KP_Decimal ] };\ - key { [ Hangul ] };\ - key { [ Hangul_Hanja ] };\ - key { [ Super_L ] };\ - key { [ Super_R ] };\ - key { [ Menu ] };\ - key { [ Cancel ] };\ - key { [ Redo ] };\ - key { [ SunProps ] };\ - key { [ Undo ] };\ - key { [ SunFront ] };\ - key { [ XF86Copy ] };\ - key { [ XF86Open ] };\ - key { [ XF86Paste ] };\ - key { [ Find ] };\ - key { [ XF86Cut ] };\ - key { [ Help ] };\ - key { [ XF86MenuKB ] };\ - key { [ XF86Calculator ] };\ - key { [ XF86Sleep ] };\ - key { [ XF86WakeUp ] };\ - key { [ XF86Explorer ] };\ - key { [ XF86Send ] };\ - key { [ XF86Xfer ] };\ - key { [ XF86Launch1 ] };\ - key { [ XF86Launch2 ] };\ - key { [ XF86WWW ] };\ - key { [ XF86DOS ] };\ - key { [ XF86ScreenSaver ] };\ - key { [ XF86RotateWindows ] };\ - key { [ XF86TaskPane ] };\ - key { [ XF86Mail ] };\ - key { [ XF86Favorites ] };\ - key { [ XF86MyComputer ] };\ - key { [ XF86Back ] };\ - key { [ XF86Forward ] };\ - key { [ XF86Eject ] };\ - key { [ XF86Eject, XF86Eject ] };\ - key { [ XF86AudioNext ] };\ - key { [ XF86AudioPlay, XF86AudioPause ] };\ - key { [ XF86AudioPrev ] };\ - key { [ XF86AudioStop, XF86Eject ] };\ - key { [ XF86AudioRecord ] };\ - key { [ XF86AudioRewind ] };\ - key { [ XF86Phone ] };\ - key { [ XF86Tools ] };\ - key { [ XF86HomePage ] };\ - key { [ XF86Reload ] };\ - key { [ XF86Close ] };\ - key { [ XF86ScrollUp ] };\ - key { [ XF86ScrollDown ] };\ - key { [ parenleft ] };\ - key { [ parenright ] };\ - key { [ XF86New ] };\ - key { [ Redo ] };\ - key { [ XF86Tools ] };\ - key { [ XF86Launch5 ] };\ - key { [ XF86Launch6 ] };\ - key { [ XF86Launch7 ] };\ - key { [ XF86Launch8 ] };\ - key { [ XF86Launch9 ] };\ - key { [ XF86AudioMicMute ] };\ - key { [ XF86TouchpadToggle ] };\ - key { [ XF86TouchpadOn ] };\ - key { [ XF86TouchpadOff ] };\ - key { [ Mode_switch ] };\ - key { [ NoSymbol, Alt_L ] };\ - key { [ NoSymbol, Meta_L ] };\ - key { [ NoSymbol, Super_L ] };\ - key { [ NoSymbol, Hyper_L ] };\ - key { [ XF86AudioPlay ] };\ - key { [ XF86AudioPause ] };\ - key { [ XF86Launch3 ] };\ - key { [ XF86Launch4 ] };\ - key { [ XF86LaunchB ] };\ - key { [ XF86Suspend ] };\ - key { [ XF86Close ] };\ - key { [ XF86AudioPlay ] };\ - key { [ XF86AudioForward ] };\ - key { [ Print ] };\ - key { [ XF86WebCam ] };\ - key { [ XF86AudioPreset ] };\ - key { [ XF86Mail ] };\ - key { [ XF86Messenger ] };\ - key { [ XF86Search ] };\ - key { [ XF86Go ] };\ - key { [ XF86Finance ] };\ - key { [ XF86Game ] };\ - key { [ XF86Shop ] };\ - key { [ Cancel ] };\ - key { [ XF86MonBrightnessDown ] };\ - key { [ XF86MonBrightnessUp ] };\ - key { [ XF86AudioMedia ] };\ - key { [ XF86Display ] };\ - key { [ XF86KbdLightOnOff ] };\ - key { [ XF86KbdBrightnessDown ] };\ - key { [ XF86KbdBrightnessUp ] };\ - key { [ XF86Send ] };\ - key { [ XF86Reply ] };\ - key { [ XF86MailForward ] };\ - key { [ XF86Save ] };\ - key { [ XF86Documents ] };\ - key { [ XF86Battery ] };\ - key { [ XF86Bluetooth ] };\ - key { [ XF86WLAN ] };\ - key { [ XF86UWB ] };\ - key { [ XF86WWAN ] };\ - key { [ XF86RFKill ] };\ + key {\t[ XF86LaunchA ] };\ + key {\t[ KP_Decimal, KP_Decimal ] };\ + key {\t[ Hangul ] };\ + key {\t[ Hangul_Hanja ] };\ + key {\t[ Super_L ] };\ + key {\t[ Super_R ] };\ + key {\t[ Menu ] };\ + key {\t[ Cancel ] };\ + key {\t[ Redo ] };\ + key {\t[ SunProps ] };\ + key {\t[ Undo ] };\ + key {\t[ SunFront ] };\ + key {\t[ XF86Copy ] };\ + key {\t[ XF86Open ] };\ + key {\t[ XF86Paste ] };\ + key {\t[ Find ] };\ + key {\t[ XF86Cut ] };\ + key {\t[ Help ] };\ + key {\t[ XF86MenuKB ] };\ + key {\t[ XF86Calculator ] };\ + key {\t[ XF86Sleep ] };\ + key {\t[ XF86WakeUp ] };\ + key {\t[ XF86Explorer ] };\ + key {\t[ XF86Send ] };\ + key {\t[ XF86Xfer ] };\ + key {\t[ XF86Launch1 ] };\ + key {\t[ XF86Launch2 ] };\ + key {\t[ XF86WWW ] };\ + key {\t[ XF86DOS ] };\ + key {\t[ XF86ScreenSaver ] };\ + key {\t[ XF86RotateWindows ] };\ + key {\t[ XF86TaskPane ] };\ + key {\t[ XF86Mail ] };\ + key {\t[ XF86Favorites ] };\ + key {\t[ XF86MyComputer ] };\ + key {\t[ XF86Back ] };\ + key {\t[ XF86Forward ] };\ + key {\t[ XF86Eject ] };\ + key {\t[ XF86Eject, XF86Eject ] };\ + key {\t[ XF86AudioNext ] };\ + key {\t[ XF86AudioPlay, XF86AudioPause ] };\ + key {\t[ XF86AudioPrev ] };\ + key {\t[ XF86AudioStop, XF86Eject ] };\ + key {\t[ XF86AudioRecord ] };\ + key {\t[ XF86AudioRewind ] };\ + key {\t[ XF86Phone ] };\ + key {\t[ XF86Tools ] };\ + key {\t[ XF86HomePage ] };\ + key {\t[ XF86Reload ] };\ + key {\t[ XF86Close ] };\ + key {\t[ XF86ScrollUp ] };\ + key {\t[ XF86ScrollDown ] };\ + key {\t[ parenleft ] };\ + key {\t[ parenright ] };\ + key {\t[ XF86New ] };\ + key {\t[ Redo ] };\ + key {\t[ XF86Tools ] };\ + key {\t[ XF86Launch5 ] };\ + key {\t[ XF86Launch6 ] };\ + key {\t[ XF86Launch7 ] };\ + key {\t[ XF86Launch8 ] };\ + key {\t[ XF86Launch9 ] };\ + key {\t[ XF86AudioMicMute ] };\ + key {\t[ XF86TouchpadToggle ] };\ + key {\t[ XF86TouchpadOn ] };\ + key {\t[ XF86TouchpadOff ] };\ + key {\t[ Mode_switch ] };\ + key {\t[ NoSymbol, Alt_L ] };\ + key {\t[ NoSymbol, Meta_L ] };\ + key {\t[ NoSymbol, Super_L ] };\ + key {\t[ NoSymbol, Hyper_L ] };\ + key {\t[ XF86AudioPlay ] };\ + key {\t[ XF86AudioPause ] };\ + key {\t[ XF86Launch3 ] };\ + key {\t[ XF86Launch4 ] };\ + key {\t[ XF86LaunchB ] };\ + key {\t[ XF86Suspend ] };\ + key {\t[ XF86Close ] };\ + key {\t[ XF86AudioPlay ] };\ + key {\t[ XF86AudioForward ] };\ + key {\t[ Print ] };\ + key {\t[ XF86WebCam ] };\ + key {\t[ XF86AudioPreset ] };\ + key {\t[ XF86Mail ] };\ + key {\t[ XF86Messenger ] };\ + key {\t[ XF86Search ] };\ + key {\t[ XF86Go ] };\ + key {\t[ XF86Finance ] };\ + key {\t[ XF86Game ] };\ + key {\t[ XF86Shop ] };\ + key {\t[ Cancel ] };\ + key {\t[ XF86MonBrightnessDown ] };\ + key {\t[ XF86MonBrightnessUp ] };\ + key {\t[ XF86AudioMedia ] };\ + key {\t[ XF86Display ] };\ + key {\t[ XF86KbdLightOnOff ] };\ + key {\t[ XF86KbdBrightnessDown ] };\ + key {\t[ XF86KbdBrightnessUp ] };\ + key {\t[ XF86Send ] };\ + key {\t[ XF86Reply ] };\ + key {\t[ XF86MailForward ] };\ + key {\t[ XF86Save ] };\ + key {\t[ XF86Documents ] };\ + key {\t[ XF86Battery ] };\ + key {\t[ XF86Bluetooth ] };\ + key {\t[ XF86WLAN ] };\ + key {\t[ XF86UWB ] };\ + key {\t[ XF86WWAN ] };\ + key {\t[ XF86RFKill ] };\ modifier_map Shift { , };\ modifier_map Lock { };\ modifier_map Control { , };\ diff --git a/src/layouts.tpp b/src/layouts.tpp index b2b4201..b1b774d 100644 --- a/src/layouts.tpp +++ b/src/layouts.tpp @@ -54,10 +54,14 @@ for (auto& row : shift_keys) { key.text = key.text.uppercase(); if (key.code < USE_SHIFT) + { key.code |= USE_SHIFT; + } if (key.text == "ABC") + { key.text = "abc"; + } } } diff --git a/src/main.cpp b/src/main.cpp index 24a950e..55e3dd7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,163 +14,177 @@ namespace wf { - namespace osk +namespace osk +{ +int spacing = OSK_SPACING; +int default_width = 800; +int default_height = 400; +int headerbar_size = 60; + +std::string anchor; + +KeyButton::KeyButton(Key key, int width, int height) +{ + this->code = key.code; + + this->button.set_size_request(width, height); + this->button.set_label(key.text); + + this->button.signal_pressed().connect_notify( + sigc::mem_fun(this, &KeyButton::on_pressed)); + this->button.signal_released().connect_notify( + sigc::mem_fun(this, &KeyButton::on_released)); +} + +void KeyButton::on_pressed() +{ + auto& keyboard = Keyboard::get(); + if (IS_COMMAND(this->code)) { - int spacing = OSK_SPACING; - int default_width = 800; - int default_height = 400; - int headerbar_size = 60; + return; + } - std::string anchor; + if (this->code & USE_SHIFT) + { + keyboard.get_device().set_shift(true); + } - KeyButton::KeyButton(Key key, int width, int height) - { - this->code = key.code; + keyboard.get_device().send_key(this->code & ~(USE_SHIFT), + WL_KEYBOARD_KEY_STATE_PRESSED); +} - this->button.set_size_request(width, height); - this->button.set_label(key.text); +void KeyButton::on_released() +{ + auto& keyboard = Keyboard::get(); + if (IS_COMMAND(this->code)) + { + return keyboard.handle_action(this->code); + } - this->button.signal_pressed().connect_notify( - sigc::mem_fun(this, &KeyButton::on_pressed)); - this->button.signal_released().connect_notify( - sigc::mem_fun(this, &KeyButton::on_released)); - } + if (this->code & USE_SHIFT) + { + keyboard.get_device().set_shift(false); + } - void KeyButton::on_pressed() - { - auto& keyboard = Keyboard::get(); - if (IS_COMMAND(this->code)) - return; + keyboard.get_device().send_key(this->code & ~(USE_SHIFT), + WL_KEYBOARD_KEY_STATE_RELEASED); +} - if (this->code & USE_SHIFT) - keyboard.get_device().set_shift(true); +KeyboardRow::KeyboardRow(std::vector keys, + int width, int height) +{ + double sum = 0; + for (auto& key : keys) + { + sum += key.width; + } - keyboard.get_device().send_key(this->code & ~(USE_SHIFT), - WL_KEYBOARD_KEY_STATE_PRESSED); - } + box.set_spacing(spacing); + int total_spacing = std::min((int)keys.size() - 1, 0) * spacing; + int total_buttons = width - total_spacing; - void KeyButton::on_released() - { - auto& keyboard = Keyboard::get(); - if (IS_COMMAND(this->code)) - return keyboard.handle_action(this->code); + for (auto& key : keys) + { + this->keys.emplace_back(std::make_unique(key, int(key.width / sum * total_buttons), + height)); + this->box.pack_start(this->keys.back()->button); + } +} - if (this->code & USE_SHIFT) - keyboard.get_device().set_shift(false); +KeyboardLayout::KeyboardLayout(std::vector> keys, + int32_t width, int32_t height) +{ + box.set_spacing(spacing); + int total_spacing = std::min((int)keys.size() - 1, 0) * spacing; - keyboard.get_device().send_key(this->code & ~(USE_SHIFT), - WL_KEYBOARD_KEY_STATE_RELEASED); - } + int row_height = (height - total_spacing) / keys.size(); + for (auto& row : keys) + { + this->rows.emplace_back( + std::make_unique(row, width, row_height)); + this->box.pack_start(this->rows.back()->box); + } +} - KeyboardRow::KeyboardRow(std::vector keys, - int width, int height) - { - double sum = 0; - for (auto& key : keys) - sum += key.width; - - box.set_spacing(spacing); - int total_spacing = std::min((int)keys.size() - 1, 0) * spacing; - int total_buttons = width - total_spacing; - - for (auto& key : keys) - { - this->keys.emplace_back(std::make_unique - (key, int(key.width / sum * total_buttons), height)); - this->box.pack_start(this->keys.back()->button); - } - } +void Keyboard::init_layouts() +{ + /* Key layouts are defined in layouts.tpp, + * it defines default_keys, shift_keys, numeric_keys */ +#include "layouts.tpp" - KeyboardLayout::KeyboardLayout(std::vector> keys, - int32_t width, int32_t height) - { - box.set_spacing(spacing); - int total_spacing = std::min((int)keys.size() - 1, 0) * spacing; - - int row_height = (height - total_spacing) / keys.size(); - for (auto& row : keys) - { - this->rows.emplace_back( - std::make_unique (row, width, row_height)); - this->box.pack_start(this->rows.back()->box); - } - } + this->default_layout = std::make_unique(default_keys, default_width, default_height); - void Keyboard::init_layouts() - { - /* Key layouts are defined in layouts.tpp, - * it defines default_keys, shift_keys, numeric_keys */ - #include "layouts.tpp" + this->shift_layout = std::make_unique(shift_keys, default_width, default_height); - this->default_layout = std::make_unique - (default_keys, default_width, default_height); + this->numeric_layout = std::make_unique(numeric_keys, default_width, default_height); +} - this->shift_layout = std::make_unique - (shift_keys, default_width, default_height); +void Keyboard::set_layout(KeyboardLayout *new_layout) +{ + this->current_layout = new_layout; + window->set_widget(new_layout->box); +} - this->numeric_layout = std::make_unique - (numeric_keys, default_width, default_height); - } +Keyboard::Keyboard() +{ + window = std::make_unique(default_width, default_height, anchor, headerbar_size); + vk = std::make_unique(); - void Keyboard::set_layout(KeyboardLayout *new_layout) - { - this->current_layout = new_layout; - window->set_widget(new_layout->box); - } + init_layouts(); + set_layout(default_layout.get()); +} - Keyboard::Keyboard() - { - window = std::make_unique - (default_width, default_height, anchor, headerbar_size); - vk = std::make_unique (); +std::unique_ptr Keyboard::instance; +void Keyboard::create() +{ + if (instance) + { + throw std::logic_error("Creating keyboard twice!"); + } - init_layouts(); - set_layout(default_layout.get()); - } + instance = std::unique_ptr(new Keyboard()); +} - std::unique_ptr Keyboard::instance; - void Keyboard::create() - { - if (instance) - throw std::logic_error("Creating keyboard twice!"); +Keyboard& Keyboard::get() +{ + if (!instance) + { + throw std::logic_error("Getting keyboard before creating it!"); + } - instance = std::unique_ptr(new Keyboard()); - } + return *instance; +} - Keyboard& Keyboard::get() - { - if (!instance) - throw std::logic_error("Getting keyboard before creating it!"); +VirtualKeyboardDevice& Keyboard::get_device() +{ + return *vk; +} - return *instance; - } +Gtk::Window& Keyboard::get_window() +{ + return *window; +} - VirtualKeyboardDevice& Keyboard::get_device() +void Keyboard::handle_action(uint32_t action) +{ + if (action == ABC_TOGGLE) + { + if (current_layout == default_layout.get()) { - return *vk; - } - - Gtk::Window& Keyboard::get_window() + set_layout(shift_layout.get()); + } else { - return *window; + set_layout(default_layout.get()); } + } - void Keyboard::handle_action(uint32_t action) - { - if (action == ABC_TOGGLE) - { - if (current_layout == default_layout.get()) { - set_layout(shift_layout.get()); - } else { - set_layout(default_layout.get()); - } - } - - if (action == NUM_TOGGLE) - set_layout(numeric_layout.get()); - } + if (action == NUM_TOGGLE) + { + set_layout(numeric_layout.get()); } } +} +} int main(int argc, char **argv) { @@ -178,21 +192,23 @@ int main(int argc, char **argv) auto cli = clara::detail::Help(show_help) | clara::detail::Opt(wf::osk::default_width, "int")["-w"]["--width"] - ("keyboard width") | + ("keyboard width") | clara::detail::Opt(wf::osk::default_height, "int")["-h"]["--height"] - ("keyboard height") | + ("keyboard height") | clara::detail::Opt(wf::osk::headerbar_size, "int")["-b"]["--headerbar-height"] - ("headerbar height") | + ("headerbar height") | clara::detail::Opt(wf::osk::anchor, "top|left|bottom|right|pinned")["-a"] - ["--anchor"]("where the keyboard should anchor in the screen"); + ["--anchor"]("where the keyboard should anchor in the screen"); auto res = cli.parse(clara::detail::Args(argc, argv)); - if (!res) { + if (!res) + { std::cerr << "Error: " << res.errorMessage() << std::endl; return 1; } - if (show_help) { + if (show_help) + { std::cout << cli << std::endl; return 0; } diff --git a/src/osk.hpp b/src/osk.hpp index 07c38e6..91a48a1 100644 --- a/src/osk.hpp +++ b/src/osk.hpp @@ -11,69 +11,69 @@ namespace wf { - namespace osk - { - extern int spacing; - - struct Key - { - uint32_t code; - Glib::ustring text; - double width; - }; - - struct KeyButton - { - Gtk::Button button; - - /* keycode as in linux/input-event-codes.h */ - uint32_t code; - KeyButton(Key key, int width, int height); - - private: - void on_pressed(); - void on_released(); - }; - - struct KeyboardRow - { - Gtk::HBox box; - std::vector> keys; - - KeyboardRow(std::vector keys, - int width, int height); - }; - - struct KeyboardLayout - { - Gtk::VBox box; - std::vector> rows; - - KeyboardLayout(std::vector> keys, - int32_t width, int32_t height); - }; - - class Keyboard - { - std::unique_ptr default_layout, shift_layout, - numeric_layout; - KeyboardLayout *current_layout = nullptr; - void init_layouts(); - void set_layout(KeyboardLayout *new_layout); - - std::unique_ptr window; - std::unique_ptr vk; - Keyboard(); - - static std::unique_ptr instance; - - public: - static void create(); - static Keyboard& get(); - - void handle_action(uint32_t action); - VirtualKeyboardDevice& get_device(); - Gtk::Window& get_window(); - }; - } +namespace osk +{ +extern int spacing; + +struct Key +{ + uint32_t code; + Glib::ustring text; + double width; +}; + +struct KeyButton +{ + Gtk::Button button; + + /* keycode as in linux/input-event-codes.h */ + uint32_t code; + KeyButton(Key key, int width, int height); + + private: + void on_pressed(); + void on_released(); +}; + +struct KeyboardRow +{ + Gtk::HBox box; + std::vector> keys; + + KeyboardRow(std::vector keys, + int width, int height); +}; + +struct KeyboardLayout +{ + Gtk::VBox box; + std::vector> rows; + + KeyboardLayout(std::vector> keys, + int32_t width, int32_t height); +}; + +class Keyboard +{ + std::unique_ptr default_layout, shift_layout, + numeric_layout; + KeyboardLayout *current_layout = nullptr; + void init_layouts(); + void set_layout(KeyboardLayout *new_layout); + + std::unique_ptr window; + std::unique_ptr vk; + Keyboard(); + + static std::unique_ptr instance; + + public: + static void create(); + static Keyboard& get(); + + void handle_action(uint32_t action); + VirtualKeyboardDevice& get_device(); + Gtk::Window& get_window(); +}; +} } diff --git a/src/shared/os-compatibility.c b/src/shared/os-compatibility.c index 9980d45..867c024 100644 --- a/src/shared/os-compatibility.c +++ b/src/shared/os-compatibility.c @@ -25,106 +25,96 @@ #define _POSIX_C_SOURCE 200809L -#include -#include -#include -#include #include -#include -#include +#include #include +#include +#include +#include +#include +#include #include "os-compatibility.h" -int -os_fd_set_cloexec(int fd) -{ - long flags; +int os_fd_set_cloexec(int fd) { + long flags; - if (fd == -1) - return -1; + if (fd == -1) + return -1; - flags = fcntl(fd, F_GETFD); - if (flags == -1) - return -1; + flags = fcntl(fd, F_GETFD); + if (flags == -1) + return -1; - if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) - return -1; + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + return -1; - return 0; + return 0; } -static int -set_cloexec_or_close(int fd) -{ - if (os_fd_set_cloexec(fd) != 0) { - close(fd); - return -1; - } - return fd; +static int set_cloexec_or_close(int fd) { + if (os_fd_set_cloexec(fd) != 0) { + close(fd); + return -1; + } + return fd; } -int -os_socketpair_cloexec(int domain, int type, int protocol, int *sv) -{ - int ret; +int os_socketpair_cloexec(int domain, int type, int protocol, int *sv) { + int ret; #ifdef SOCK_CLOEXEC - ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv); - if (ret == 0 || errno != EINVAL) - return ret; + ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv); + if (ret == 0 || errno != EINVAL) + return ret; #endif - ret = socketpair(domain, type, protocol, sv); - if (ret < 0) - return ret; + ret = socketpair(domain, type, protocol, sv); + if (ret < 0) + return ret; - sv[0] = set_cloexec_or_close(sv[0]); - sv[1] = set_cloexec_or_close(sv[1]); + sv[0] = set_cloexec_or_close(sv[0]); + sv[1] = set_cloexec_or_close(sv[1]); - if (sv[0] != -1 && sv[1] != -1) - return 0; + if (sv[0] != -1 && sv[1] != -1) + return 0; - close(sv[0]); - close(sv[1]); - return -1; + close(sv[0]); + close(sv[1]); + return -1; } -int -os_epoll_create_cloexec(void) -{ - int fd; +int os_epoll_create_cloexec(void) { + int fd; #ifdef EPOLL_CLOEXEC - fd = epoll_create1(EPOLL_CLOEXEC); - if (fd >= 0) - return fd; - if (errno != EINVAL) - return -1; + fd = epoll_create1(EPOLL_CLOEXEC); + if (fd >= 0) + return fd; + if (errno != EINVAL) + return -1; #endif - fd = epoll_create(1); - return set_cloexec_or_close(fd); + fd = epoll_create(1); + return set_cloexec_or_close(fd); } -static int -create_tmpfile_cloexec(char *tmpname) -{ - int fd; +static int create_tmpfile_cloexec(char *tmpname) { + int fd; #ifdef HAVE_MKOSTEMP - fd = mkostemp(tmpname, O_CLOEXEC); - if (fd >= 0) - unlink(tmpname); + fd = mkostemp(tmpname, O_CLOEXEC); + if (fd >= 0) + unlink(tmpname); #else - fd = mkstemp(tmpname); - if (fd >= 0) { - fd = set_cloexec_or_close(fd); - unlink(tmpname); - } + fd = mkstemp(tmpname); + if (fd >= 0) { + fd = set_cloexec_or_close(fd); + unlink(tmpname); + } #endif - return fd; + return fd; } /* @@ -148,63 +138,59 @@ create_tmpfile_cloexec(char *tmpname) * If posix_fallocate() is not supported, program may receive * SIGBUS on accessing mmap()'ed file contents instead. */ -int -os_create_anonymous_file(off_t size) -{ - static const char template[] = "/weston-shared-XXXXXX"; - const char *path; - char *name; - int fd; - int ret; +int os_create_anonymous_file(off_t size) { + static const char template[] = "/weston-shared-XXXXXX"; + const char *path; + char *name; + int fd; + int ret; - path = getenv("XDG_RUNTIME_DIR"); - if (!path) { - errno = ENOENT; - return -1; - } + path = getenv("XDG_RUNTIME_DIR"); + if (!path) { + errno = ENOENT; + return -1; + } - name = malloc(strlen(path) + sizeof(template)); - if (!name) - return -1; + name = malloc(strlen(path) + sizeof(template)); + if (!name) + return -1; - strcpy(name, path); - strcat(name, template); + strcpy(name, path); + strcat(name, template); - fd = create_tmpfile_cloexec(name); + fd = create_tmpfile_cloexec(name); - free(name); + free(name); - if (fd < 0) - return -1; + if (fd < 0) + return -1; #ifdef HAVE_POSIX_FALLOCATE - do { - ret = posix_fallocate(fd, 0, size); - } while (ret == EINTR); - if (ret != 0) { - close(fd); - errno = ret; - return -1; - } + do { + ret = posix_fallocate(fd, 0, size); + } while (ret == EINTR); + if (ret != 0) { + close(fd); + errno = ret; + return -1; + } #else - do { - ret = ftruncate(fd, size); - } while (ret < 0 && errno == EINTR); - if (ret < 0) { - close(fd); - return -1; - } + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + close(fd); + return -1; + } #endif - return fd; + return fd; } #ifndef MISSING_STRCHRNUL -char * -strchrnul(const char *s, int c) -{ - while (*s && *s != c) - s++; - return (char *)s; +char *strchrnul(const char *s, int c) { + while (*s && *s != c) + s++; + return (char *)s; } #endif diff --git a/src/shared/os-compatibility.h b/src/shared/os-compatibility.h index 25ba1bb..f8bcf93 100644 --- a/src/shared/os-compatibility.h +++ b/src/shared/os-compatibility.h @@ -30,13 +30,13 @@ #ifdef __cplusplus extern "C" { -int os_fd_set_cloexec(int fd); -int os_socketpair_cloexec(int domain, int type, int protocol, int *sv); -int os_epoll_create_cloexec(void); -int os_create_anonymous_file(off_t size); -#ifdef MISSING_STRCHRNUL -char * strchrnul(const char *s, int c); -#endif + int os_fd_set_cloexec(int fd); + int os_socketpair_cloexec(int domain, int type, int protocol, int *sv); + int os_epoll_create_cloexec(void); + int os_create_anonymous_file(off_t size); + #ifdef MISSING_STRCHRNUL + char *strchrnul(const char *s, int c); + #endif } #endif diff --git a/src/util/clara.hpp b/src/util/clara.hpp index 7e32288..dbe6bd1 100644 --- a/src/util/clara.hpp +++ b/src/util/clara.hpp @@ -11,20 +11,20 @@ #define CLARA_HPP_INCLUDED #ifndef CLARA_CONFIG_CONSOLE_WIDTH -#define CLARA_CONFIG_CONSOLE_WIDTH 80 + #define CLARA_CONFIG_CONSOLE_WIDTH 80 #endif #ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH + #define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH #endif #ifndef CLARA_CONFIG_OPTIONAL_TYPE -#ifdef __has_include -#if __has_include() && __cplusplus >= 201703L -#include -#define CLARA_CONFIG_OPTIONAL_TYPE std::optional -#endif -#endif + #ifdef __has_include + #if __has_include() && __cplusplus >= 201703L + #include + #define CLARA_CONFIG_OPTIONAL_TYPE std::optional + #endif + #endif #endif @@ -40,331 +40,453 @@ // This project is hosted at https://github.com/philsquared/textflowcpp #ifndef CLARA_TEXTFLOW_HPP_INCLUDED -#define CLARA_TEXTFLOW_HPP_INCLUDED - -#include -#include -#include -#include - -#ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 -#endif - - -namespace clara { namespace TextFlow { - - inline auto isWhitespace( char c ) -> bool { - static std::string chars = " \t\n\r"; - return chars.find( c ) != std::string::npos; - } - inline auto isBreakableBefore( char c ) -> bool { - static std::string chars = "[({<|"; - return chars.find( c ) != std::string::npos; - } - inline auto isBreakableAfter( char c ) -> bool { - static std::string chars = "])}>.,:;*+-=&/\\"; - return chars.find( c ) != std::string::npos; - } - - class Columns; - - class Column { - std::vector m_strings; - size_t m_width = CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; - size_t m_indent = 0; - size_t m_initialIndent = std::string::npos; - - public: - class iterator { - friend Column; - - Column const& m_column; - size_t m_stringIndex = 0; - size_t m_pos = 0; + #define CLARA_TEXTFLOW_HPP_INCLUDED + + #include + #include + #include + #include + + #ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH + #define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 + #endif + + +namespace clara +{ +namespace TextFlow +{ +inline auto isWhitespace(char c) -> bool +{ + static std::string chars = " \t\n\r"; + return chars.find(c) != std::string::npos; +} + +inline auto isBreakableBefore(char c) -> bool +{ + static std::string chars = "[({<|"; + return chars.find(c) != std::string::npos; +} + +inline auto isBreakableAfter(char c) -> bool +{ + static std::string chars = "])}>.,:;*+-=&/\\"; + return chars.find(c) != std::string::npos; +} + +class Columns; + +class Column +{ + std::vector m_strings; + size_t m_width = CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; + size_t m_indent = 0; + size_t m_initialIndent = std::string::npos; + + public: + class iterator + { + friend Column; + + Column const& m_column; + size_t m_stringIndex = 0; + size_t m_pos = 0; + + size_t m_len = 0; + size_t m_end = 0; + bool m_suffix = false; + + iterator(Column const& column, size_t stringIndex) : + m_column(column), + m_stringIndex(stringIndex) + {} - size_t m_len = 0; - size_t m_end = 0; - bool m_suffix = false; + auto line() const -> std::string const& + { + return m_column.m_strings[m_stringIndex]; + } - iterator( Column const& column, size_t stringIndex ) - : m_column( column ), - m_stringIndex( stringIndex ) - {} + auto isBoundary(size_t at) const -> bool + { + assert(at > 0); + assert(at <= line().size()); - auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } + return at == line().size() || + (isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) || + isBreakableBefore(line()[at]) || + isBreakableAfter(line()[at - 1]); + } - auto isBoundary( size_t at ) const -> bool { - assert( at > 0 ); - assert( at <= line().size() ); + void calcLength() + { + assert(m_stringIndex < m_column.m_strings.size()); - return at == line().size() || - ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) || - isBreakableBefore( line()[at] ) || - isBreakableAfter( line()[at-1] ); + m_suffix = false; + auto width = m_column.m_width - indent(); + m_end = m_pos; + while (m_end < line().size() && line()[m_end] != '\n') + { + ++m_end; } - void calcLength() { - assert( m_stringIndex < m_column.m_strings.size() ); - - m_suffix = false; - auto width = m_column.m_width-indent(); - m_end = m_pos; - while( m_end < line().size() && line()[m_end] != '\n' ) - ++m_end; + if (m_end < m_pos + width) + { + m_len = m_end - m_pos; + } else + { + size_t len = width; + while (len > 0 && !isBoundary(m_pos + len)) + { + --len; + } - if( m_end < m_pos + width ) { - m_len = m_end - m_pos; + while (len > 0 && isWhitespace(line()[m_pos + len - 1])) + { + --len; } - else { - size_t len = width; - while (len > 0 && !isBoundary(m_pos + len)) - --len; - while (len > 0 && isWhitespace( line()[m_pos + len - 1] )) - --len; - - if (len > 0) { - m_len = len; - } else { - m_suffix = true; - m_len = width - 1; - } + + if (len > 0) + { + m_len = len; + } else + { + m_suffix = true; + m_len = width - 1; } } + } - auto indent() const -> size_t { - auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; - return initial == std::string::npos ? m_column.m_indent : initial; - } + auto indent() const -> size_t + { + auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } - auto addIndentAndSuffix(std::string const &plain) const -> std::string { - return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain); - } + auto addIndentAndSuffix(std::string const & plain) const -> std::string + { + return std::string(indent(), ' ') + (m_suffix ? plain + "-" : plain); + } - public: - using difference_type = std::ptrdiff_t; - using value_type = std::string; - using pointer = value_type*; - using reference = value_type&; - using iterator_category = std::forward_iterator_tag; + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::forward_iterator_tag; - explicit iterator( Column const& column ) : m_column( column ) { - assert( m_column.m_width > m_column.m_indent ); - assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); - calcLength(); - if( m_len == 0 ) - m_stringIndex++; // Empty string + explicit iterator(Column const& column) : m_column(column) + { + assert(m_column.m_width > m_column.m_indent); + assert( + m_column.m_initialIndent == std::string::npos || + m_column.m_width > m_column.m_initialIndent); + calcLength(); + if (m_len == 0) + { + m_stringIndex++; // Empty string } + } - auto operator *() const -> std::string { - assert( m_stringIndex < m_column.m_strings.size() ); - assert( m_pos <= m_end ); - return addIndentAndSuffix(line().substr(m_pos, m_len)); - } + auto operator *() const -> std::string + { + assert(m_stringIndex < m_column.m_strings.size()); + assert(m_pos <= m_end); + return addIndentAndSuffix(line().substr(m_pos, m_len)); + } - auto operator ++() -> iterator& { - m_pos += m_len; - if( m_pos < line().size() && line()[m_pos] == '\n' ) - m_pos += 1; - else - while( m_pos < line().size() && isWhitespace( line()[m_pos] ) ) - ++m_pos; - - if( m_pos == line().size() ) { - m_pos = 0; - ++m_stringIndex; + auto operator ++() -> iterator& + { + m_pos += m_len; + if ((m_pos < line().size()) && (line()[m_pos] == '\n')) + { + m_pos += 1; + } else + { + while (m_pos < line().size() && isWhitespace(line()[m_pos])) + { + ++m_pos; } - if( m_stringIndex < m_column.m_strings.size() ) - calcLength(); - return *this; - } - auto operator ++(int) -> iterator { - iterator prev( *this ); - operator++(); - return prev; } - auto operator ==( iterator const& other ) const -> bool { - return - m_pos == other.m_pos && - m_stringIndex == other.m_stringIndex && - &m_column == &other.m_column; - } - auto operator !=( iterator const& other ) const -> bool { - return !operator==( other ); + if (m_pos == line().size()) + { + m_pos = 0; + ++m_stringIndex; } - }; - using const_iterator = iterator; - explicit Column( std::string const& text ) { m_strings.push_back( text ); } + if (m_stringIndex < m_column.m_strings.size()) + { + calcLength(); + } - auto width( size_t newWidth ) -> Column& { - assert( newWidth > 0 ); - m_width = newWidth; return *this; } - auto indent( size_t newIndent ) -> Column& { - m_indent = newIndent; - return *this; + + auto operator ++(int) -> iterator + { + iterator prev(*this); + operator ++(); + return prev; } - auto initialIndent( size_t newIndent ) -> Column& { - m_initialIndent = newIndent; - return *this; + + auto operator ==(iterator const& other) const -> bool + { + return + m_pos == other.m_pos && + m_stringIndex == other.m_stringIndex && + &m_column == &other.m_column; } - auto width() const -> size_t { return m_width; } - auto begin() const -> iterator { return iterator( *this ); } - auto end() const -> iterator { return { *this, m_strings.size() }; } + auto operator !=(iterator const& other) const -> bool + { + return !operator ==(other); + } + }; - inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { - bool first = true; - for( auto line : col ) { - if( first ) - first = false; - else - os << "\n"; - os << line; + using const_iterator = iterator; + + explicit Column(std::string const& text) + { + m_strings.push_back(text); + } + + auto width(size_t newWidth) -> Column& + { + assert(newWidth > 0); + m_width = newWidth; + return *this; + } + + auto indent(size_t newIndent) -> Column& + { + m_indent = newIndent; + return *this; + } + + auto initialIndent(size_t newIndent) -> Column& + { + m_initialIndent = newIndent; + return *this; + } + + auto width() const -> size_t + { + return m_width; + } + + auto begin() const -> iterator + { + return iterator(*this); + } + + auto end() const -> iterator + { + return {*this, m_strings.size()}; + } + + inline friend std::ostream& operator <<(std::ostream& os, Column const& col) + { + bool first = true; + for (auto line : col) + { + if (first) + { + first = false; + } else + { + os << "\n"; } - return os; + + os << line; } - auto operator + ( Column const& other ) -> Columns; + return os; + } - auto toString() const -> std::string { - std::ostringstream oss; - oss << *this; - return oss.str(); - } - }; + auto operator +(Column const& other) -> Columns; - class Spacer : public Column { + auto toString() const -> std::string + { + std::ostringstream oss; + oss << *this; + return oss.str(); + } +}; + +class Spacer : public Column +{ + public: + explicit Spacer(size_t spaceWidth) : Column("") + { + width(spaceWidth); + } +}; - public: - explicit Spacer( size_t spaceWidth ) : Column( "" ) { - width( spaceWidth ); - } - }; +class Columns +{ + std::vector m_columns; - class Columns { - std::vector m_columns; + public: - public: + class iterator + { + friend Columns; + struct EndTag {}; - class iterator { - friend Columns; - struct EndTag {}; + std::vector const& m_columns; + std::vector m_iterators; + size_t m_activeIterators; - std::vector const& m_columns; - std::vector m_iterators; - size_t m_activeIterators; + iterator(Columns const& columns, EndTag) : + m_columns(columns.m_columns), + m_activeIterators(0) + { + m_iterators.reserve(m_columns.size()); - iterator( Columns const& columns, EndTag ) - : m_columns( columns.m_columns ), - m_activeIterators( 0 ) + for (auto const& col : m_columns) { - m_iterators.reserve( m_columns.size() ); - - for( auto const& col : m_columns ) - m_iterators.push_back( col.end() ); + m_iterators.push_back(col.end()); } + } - public: - using difference_type = std::ptrdiff_t; - using value_type = std::string; - using pointer = value_type*; - using reference = value_type&; - using iterator_category = std::forward_iterator_tag; + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::forward_iterator_tag; - explicit iterator( Columns const& columns ) - : m_columns( columns.m_columns ), - m_activeIterators( m_columns.size() ) - { - m_iterators.reserve( m_columns.size() ); + explicit iterator(Columns const& columns) : + m_columns(columns.m_columns), + m_activeIterators(m_columns.size()) + { + m_iterators.reserve(m_columns.size()); - for( auto const& col : m_columns ) - m_iterators.push_back( col.begin() ); + for (auto const& col : m_columns) + { + m_iterators.push_back(col.begin()); } + } - auto operator ==( iterator const& other ) const -> bool { - return m_iterators == other.m_iterators; - } - auto operator !=( iterator const& other ) const -> bool { - return m_iterators != other.m_iterators; - } - auto operator *() const -> std::string { - std::string row, padding; - - for( size_t i = 0; i < m_columns.size(); ++i ) { - auto width = m_columns[i].width(); - if( m_iterators[i] != m_columns[i].end() ) { - std::string col = *m_iterators[i]; - row += padding + col; - if( col.size() < width ) - padding = std::string( width - col.size(), ' ' ); - else - padding = ""; - } - else { - padding += std::string( width, ' ' ); + auto operator ==(iterator const& other) const -> bool + { + return m_iterators == other.m_iterators; + } + + auto operator !=(iterator const& other) const -> bool + { + return m_iterators != other.m_iterators; + } + + auto operator *() const -> std::string + { + std::string row, padding; + + for (size_t i = 0; i < m_columns.size(); ++i) + { + auto width = m_columns[i].width(); + if (m_iterators[i] != m_columns[i].end()) + { + std::string col = *m_iterators[i]; + row += padding + col; + if (col.size() < width) + { + padding = std::string(width - col.size(), ' '); + } else + { + padding = ""; } + } else + { + padding += std::string(width, ' '); } - return row; } - auto operator ++() -> iterator& { - for( size_t i = 0; i < m_columns.size(); ++i ) { - if (m_iterators[i] != m_columns[i].end()) - ++m_iterators[i]; + + return row; + } + + auto operator ++() -> iterator& + { + for (size_t i = 0; i < m_columns.size(); ++i) + { + if (m_iterators[i] != m_columns[i].end()) + { + ++m_iterators[i]; } - return *this; - } - auto operator ++(int) -> iterator { - iterator prev( *this ); - operator++(); - return prev; } - }; - using const_iterator = iterator; - - auto begin() const -> iterator { return iterator( *this ); } - auto end() const -> iterator { return { *this, iterator::EndTag() }; } - auto operator += ( Column const& col ) -> Columns& { - m_columns.push_back( col ); return *this; } - auto operator + ( Column const& col ) -> Columns { - Columns combined = *this; - combined += col; - return combined; + + auto operator ++(int) -> iterator + { + iterator prev(*this); + operator ++(); + return prev; } + }; - inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) { + using const_iterator = iterator; - bool first = true; - for( auto line : cols ) { - if( first ) - first = false; - else - os << "\n"; - os << line; + auto begin() const -> iterator + { + return iterator(*this); + } + + auto end() const -> iterator + { + return {*this, iterator::EndTag()}; + } + + auto operator +=(Column const& col) -> Columns& + { + m_columns.push_back(col); + return *this; + } + + auto operator +(Column const& col) -> Columns + { + Columns combined = *this; + combined += col; + return combined; + } + + inline friend std::ostream& operator <<(std::ostream& os, Columns const& cols) + { + bool first = true; + for (auto line : cols) + { + if (first) + { + first = false; + } else + { + os << "\n"; } - return os; - } - auto toString() const -> std::string { - std::ostringstream oss; - oss << *this; - return oss.str(); + os << line; } - }; - inline auto Column::operator + ( Column const& other ) -> Columns { - Columns cols; - cols += *this; - cols += other; - return cols; + return os; + } + + auto toString() const -> std::string + { + std::ostringstream oss; + oss << *this; + return oss.str(); } -}} +}; + +inline auto Column::operator +(Column const& other) -> Columns +{ + Columns cols; + cols += *this; + cols += other; + return cols; +} +} +} #endif // CLARA_TEXTFLOW_HPP_INCLUDED @@ -376,862 +498,1215 @@ namespace clara { namespace TextFlow { #include #include -#if !defined(CLARA_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) -#define CLARA_PLATFORM_WINDOWS +#if !defined (CLARA_PLATFORM_WINDOWS) && (defined (WIN32) || defined (__WIN32__) || defined (_WIN32) || \ + defined (_MSC_VER)) + #define CLARA_PLATFORM_WINDOWS #endif -namespace clara { -namespace detail { - - // Traits for extracting arg and return type of lambdas (for single argument lambdas) - template - struct UnaryLambdaTraits : UnaryLambdaTraits {}; - - template - struct UnaryLambdaTraits { - static const bool isValid = false; - }; - - template - struct UnaryLambdaTraits { - static const bool isValid = true; - using ArgType = typename std::remove_const::type>::type; - using ReturnType = ReturnT; - }; - - class TokenStream; - - // Transport for raw args (copied from main args, or supplied via init list for testing) - class Args { - friend TokenStream; - std::string m_exeName; - std::vector m_args; - - public: - Args( int argc, char const* const* argv ) - : m_exeName(argv[0]), - m_args(argv + 1, argv + argc) {} - - Args( std::initializer_list args ) - : m_exeName( *args.begin() ), - m_args( args.begin()+1, args.end() ) - {} - - auto exeName() const -> std::string { - return m_exeName; - } - }; - - // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string - // may encode an option + its argument if the : or = form is used - enum class TokenType { - Option, Argument - }; - struct Token { - TokenType type; - std::string token; - }; - - inline auto isOptPrefix( char c ) -> bool { - return c == '-' +namespace clara +{ +namespace detail +{ +// Traits for extracting arg and return type of lambdas (for single argument lambdas) +template +struct UnaryLambdaTraits : UnaryLambdaTraits {}; + +template +struct UnaryLambdaTraits +{ + static const bool isValid = false; +}; + +template +struct UnaryLambdaTraits +{ + static const bool isValid = true; + using ArgType = typename std::remove_const::type>::type; + using ReturnType = ReturnT; +}; + +class TokenStream; + +// Transport for raw args (copied from main args, or supplied via init list for testing) +class Args +{ + friend TokenStream; + std::string m_exeName; + std::vector m_args; + + public: + Args(int argc, char const*const *argv) : + m_exeName(argv[0]), + m_args(argv + 1, argv + argc) + {} + + Args(std::initializer_list args) : + m_exeName(*args.begin()), + m_args(args.begin() + 1, args.end()) + {} + + auto exeName() const -> std::string + { + return m_exeName; + } +}; + +// Wraps a token coming from a token stream. These may not directly correspond to strings as a single string +// may encode an option + its argument if the : or = form is used +enum class TokenType +{ + Option, + Argument, +}; + +struct Token +{ + TokenType type; + std::string token; +}; + +inline auto isOptPrefix(char c) -> bool +{ + return c == '-' #ifdef CLARA_PLATFORM_WINDOWS - || c == '/' + || c == '/' #endif - ; - } - - // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled - class TokenStream { - using Iterator = std::vector::const_iterator; - Iterator it; - Iterator itEnd; - std::vector m_tokenBuffer; - - void loadBuffer() { - m_tokenBuffer.resize( 0 ); - - // Skip any empty strings - while( it != itEnd && it->empty() ) - ++it; + ; +} + +// Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled +class TokenStream +{ + using Iterator = std::vector::const_iterator; + Iterator it; + Iterator itEnd; + std::vector m_tokenBuffer; + + void loadBuffer() + { + m_tokenBuffer.resize(0); + + // Skip any empty strings + while (it != itEnd && it->empty()) + { + ++it; + } - if( it != itEnd ) { - auto const &next = *it; - if( isOptPrefix( next[0] ) ) { - auto delimiterPos = next.find_first_of( " :=" ); - if( delimiterPos != std::string::npos ) { - m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); - m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); - } else { - if( next[1] != '-' && next.size() > 2 ) { - std::string opt = "- "; - for( size_t i = 1; i < next.size(); ++i ) { - opt[1] = next[i]; - m_tokenBuffer.push_back( { TokenType::Option, opt } ); - } - } else { - m_tokenBuffer.push_back( { TokenType::Option, next } ); + if (it != itEnd) + { + auto const & next = *it; + if (isOptPrefix(next[0])) + { + auto delimiterPos = next.find_first_of(" :="); + if (delimiterPos != std::string::npos) + { + m_tokenBuffer.push_back({TokenType::Option, next.substr(0, delimiterPos)}); + m_tokenBuffer.push_back({TokenType::Argument, next.substr(delimiterPos + 1)}); + } else + { + if ((next[1] != '-') && (next.size() > 2)) + { + std::string opt = "- "; + for (size_t i = 1; i < next.size(); ++i) + { + opt[1] = next[i]; + m_tokenBuffer.push_back({TokenType::Option, opt}); } + } else + { + m_tokenBuffer.push_back({TokenType::Option, next}); } - } else { - m_tokenBuffer.push_back( { TokenType::Argument, next } ); } + } else + { + m_tokenBuffer.push_back({TokenType::Argument, next}); } } + } - public: - explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} + public: + explicit TokenStream(Args const & args) : TokenStream(args.m_args.begin(), args.m_args.end()) + {} - TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { - loadBuffer(); - } + TokenStream(Iterator it, Iterator itEnd) : it(it), itEnd(itEnd) + { + loadBuffer(); + } - explicit operator bool() const { - return !m_tokenBuffer.empty() || it != itEnd; - } + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; + } - auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } + auto count() const -> size_t + { + return m_tokenBuffer.size() + (itEnd - it); + } - auto operator*() const -> Token { - assert( !m_tokenBuffer.empty() ); - return m_tokenBuffer.front(); - } + auto operator *() const -> Token + { + assert(!m_tokenBuffer.empty()); + return m_tokenBuffer.front(); + } - auto operator->() const -> Token const * { - assert( !m_tokenBuffer.empty() ); - return &m_tokenBuffer.front(); - } + auto operator ->() const -> Token const* + { + assert(!m_tokenBuffer.empty()); + return &m_tokenBuffer.front(); + } - auto operator++() -> TokenStream & { - if( m_tokenBuffer.size() >= 2 ) { - m_tokenBuffer.erase( m_tokenBuffer.begin() ); - } else { - if( it != itEnd ) - ++it; - loadBuffer(); + auto operator ++() -> TokenStream & + { + if (m_tokenBuffer.size() >= 2) + { + m_tokenBuffer.erase(m_tokenBuffer.begin()); + } else + { + if (it != itEnd) + { + ++it; } - return *this; - } - }; + loadBuffer(); + } - class ResultBase { - public: - enum Type { - Ok, LogicError, RuntimeError - }; - - protected: - ResultBase( Type type ) : m_type( type ) {} - virtual ~ResultBase() = default; - - virtual void enforceOk() const = 0; + return *this; + } +}; + + +class ResultBase +{ + public: + enum Type + { + Ok, + LogicError, + RuntimeError, +}; + +protected: +ResultBase(Type type) : m_type(type) +{} +virtual ~ResultBase() = default; + +virtual void enforceOk() const = 0; + +Type m_type; +}; + +template +class ResultValueBase : public ResultBase +{ + public: + auto value() const -> T const & + { + enforceOk(); + return m_value; + } - Type m_type; - }; + protected: + ResultValueBase(Type type) : ResultBase(type) + {} - template - class ResultValueBase : public ResultBase { - public: - auto value() const -> T const & { - enforceOk(); - return m_value; + ResultValueBase(ResultValueBase const & other) : ResultBase(other) + { + if (m_type == ResultBase::Ok) + { + new(&m_value) T(other.m_value); } + } - protected: - ResultValueBase( Type type ) : ResultBase( type ) {} + ResultValueBase(Type, T const & value) : ResultBase(Ok) + { + new(&m_value) T(value); + } - ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { - if( m_type == ResultBase::Ok ) - new( &m_value ) T( other.m_value ); + auto operator =(ResultValueBase const & other) -> ResultValueBase & + { + if (m_type == ResultBase::Ok) + { + m_value.~T(); } - ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { - new( &m_value ) T( value ); + ResultBase::operator =(other); + if (m_type == ResultBase::Ok) + { + new(&m_value) T(other.m_value); } - auto operator=( ResultValueBase const &other ) -> ResultValueBase & { - if( m_type == ResultBase::Ok ) - m_value.~T(); - ResultBase::operator=(other); - if( m_type == ResultBase::Ok ) - new( &m_value ) T( other.m_value ); - return *this; - } + return *this; + } - ~ResultValueBase() override { - if( m_type == Ok ) - m_value.~T(); + ~ResultValueBase() override + { + if (m_type == Ok) + { + m_value.~T(); } + } - union { - T m_value; - }; + union + { + T m_value; }; +}; + +template<> +class ResultValueBase : public ResultBase +{ + protected: + using ResultBase::ResultBase; +}; + +template +class BasicResult : public ResultValueBase +{ + public: + template + explicit BasicResult(BasicResult const & other) : + ResultValueBase(other.type()), + m_errorMessage(other.errorMessage()) + { + assert(type() != ResultBase::Ok); + } - template<> - class ResultValueBase : public ResultBase { - protected: - using ResultBase::ResultBase; - }; + template + static auto ok(U const & value) -> BasicResult + { + return {ResultBase::Ok, value}; + } - template - class BasicResult : public ResultValueBase { - public: - template - explicit BasicResult( BasicResult const &other ) - : ResultValueBase( other.type() ), - m_errorMessage( other.errorMessage() ) - { - assert( type() != ResultBase::Ok ); - } + static auto ok() -> BasicResult + { + return {ResultBase::Ok}; + } - template - static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } - static auto ok() -> BasicResult { return { ResultBase::Ok }; } - static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } - static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } - - explicit operator bool() const { return m_type == ResultBase::Ok; } - auto type() const -> ResultBase::Type { return m_type; } - auto errorMessage() const -> std::string { return m_errorMessage; } - - protected: - void enforceOk() const override { - - // Errors shouldn't reach this point, but if they do - // the actual error message will be in m_errorMessage - assert( m_type != ResultBase::LogicError ); - assert( m_type != ResultBase::RuntimeError ); - if( m_type != ResultBase::Ok ) - std::abort(); - } + static auto logicError(std::string const & message) -> BasicResult + { + return {ResultBase::LogicError, message}; + } - std::string m_errorMessage; // Only populated if resultType is an error + static auto runtimeError(std::string const & message) -> BasicResult + { + return {ResultBase::RuntimeError, message}; + } - BasicResult( ResultBase::Type type, std::string const &message ) - : ResultValueBase(type), - m_errorMessage(message) - { - assert( m_type != ResultBase::Ok ); - } + explicit operator bool() const { + return m_type == ResultBase::Ok; + } + auto type() const -> ResultBase::Type + { + return m_type; + } - using ResultValueBase::ResultValueBase; - using ResultBase::m_type; - }; + auto errorMessage() const -> std::string + { + return m_errorMessage; + } - enum class ParseResultType { - Matched, NoMatch, ShortCircuitAll, ShortCircuitSame - }; + protected: + void enforceOk() const override + { + // Errors shouldn't reach this point, but if they do + // the actual error message will be in m_errorMessage + assert(m_type != ResultBase::LogicError); + assert(m_type != ResultBase::RuntimeError); + if (m_type != ResultBase::Ok) + { + std::abort(); + } + } - class ParseState { - public: + std::string m_errorMessage; // Only populated if resultType is an error - ParseState( ParseResultType type, TokenStream const &remainingTokens ) - : m_type(type), - m_remainingTokens( remainingTokens ) - {} + BasicResult(ResultBase::Type type, std::string const & message) : + ResultValueBase(type), + m_errorMessage(message) + { + assert(m_type != ResultBase::Ok); + } - auto type() const -> ParseResultType { return m_type; } - auto remainingTokens() const -> TokenStream { return m_remainingTokens; } + using ResultValueBase::ResultValueBase; + using ResultBase::m_type; +}; + +enum class ParseResultType +{ + Matched, + NoMatch, + ShortCircuitAll, + ShortCircuitSame, +}; + +class ParseState +{ + public: + + ParseState(ParseResultType type, TokenStream const & remainingTokens) : + m_type(type), + m_remainingTokens(remainingTokens) + {} + + auto type() const -> ParseResultType + { + return m_type; + } - private: - ParseResultType m_type; - TokenStream m_remainingTokens; - }; + auto remainingTokens() const -> TokenStream + { + return m_remainingTokens; + } - using Result = BasicResult; - using ParserResult = BasicResult; - using InternalParseResult = BasicResult; + private: + ParseResultType m_type; + TokenStream m_remainingTokens; +}; + +using Result = BasicResult; +using ParserResult = BasicResult; +using InternalParseResult = BasicResult; + +struct HelpColumns +{ + std::string left; + std::string right; +}; + +template +inline auto convertInto(std::string const & source, T& target) -> ParserResult +{ + std::stringstream ss; + ss << source; + ss >> target; + if (ss.fail()) + { + return ParserResult::runtimeError("Unable to convert '" + source + "' to destination type"); + } else + { + return ParserResult::ok(ParseResultType::Matched); + } +} + +inline auto convertInto(std::string const & source, std::string& target) -> ParserResult +{ + target = source; + return ParserResult::ok(ParseResultType::Matched); +} + +inline auto convertInto(std::string const & source, bool & target) -> ParserResult +{ + std::string srcLC = source; + std::transform(srcLC.begin(), srcLC.end(), srcLC.begin(), [] (char c) + { + return static_cast(::tolower(c)); + }); + if ((srcLC == "y") || (srcLC == "1") || (srcLC == "true") || (srcLC == "yes") || (srcLC == "on")) + { + target = true; + } else if ((srcLC == "n") || (srcLC == "0") || (srcLC == "false") || (srcLC == "no") || (srcLC == "off")) + { + target = false; + } else + { + return ParserResult::runtimeError("Expected a boolean value but did not recognise: '" + source + "'"); + } - struct HelpColumns { - std::string left; - std::string right; - }; + return ParserResult::ok(ParseResultType::Matched); +} - template - inline auto convertInto( std::string const &source, T& target ) -> ParserResult { - std::stringstream ss; - ss << source; - ss >> target; - if( ss.fail() ) - return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); - else - return ParserResult::ok( ParseResultType::Matched ); - } - inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { - target = source; - return ParserResult::ok( ParseResultType::Matched ); - } - inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { - std::string srcLC = source; - std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast( ::tolower(c) ); } ); - if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") - target = true; - else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") - target = false; - else - return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); - return ParserResult::ok( ParseResultType::Matched ); - } #ifdef CLARA_CONFIG_OPTIONAL_TYPE - template - inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE& target ) -> ParserResult { - T temp; - auto result = convertInto( source, temp ); - if( result ) - target = std::move(temp); - return result; +template +inline auto convertInto(std::string const & source, CLARA_CONFIG_OPTIONAL_TYPE& target) -> ParserResult +{ + T temp; + auto result = convertInto(source, temp); + if (result) + { + target = std::move(temp); } + + return result; +} + #endif // CLARA_CONFIG_OPTIONAL_TYPE - struct NonCopyable { - NonCopyable() = default; - NonCopyable( NonCopyable const & ) = delete; - NonCopyable( NonCopyable && ) = delete; - NonCopyable &operator=( NonCopyable const & ) = delete; - NonCopyable &operator=( NonCopyable && ) = delete; - }; +struct NonCopyable +{ + NonCopyable() = default; + NonCopyable(NonCopyable const &) = delete; + NonCopyable(NonCopyable &&) = delete; + NonCopyable& operator =(NonCopyable const&) = delete; + NonCopyable& operator =(NonCopyable&&) = delete; +}; + +struct BoundRef : NonCopyable +{ + virtual ~BoundRef() = default; + virtual auto isContainer() const -> bool + { + return false; + } - struct BoundRef : NonCopyable { - virtual ~BoundRef() = default; - virtual auto isContainer() const -> bool { return false; } - virtual auto isFlag() const -> bool { return false; } - }; - struct BoundValueRefBase : BoundRef { - virtual auto setValue( std::string const &arg ) -> ParserResult = 0; - }; - struct BoundFlagRefBase : BoundRef { - virtual auto setFlag( bool flag ) -> ParserResult = 0; - virtual auto isFlag() const -> bool { return true; } - }; + virtual auto isFlag() const -> bool + { + return false; + } +}; + +struct BoundValueRefBase : BoundRef +{ + virtual auto setValue(std::string const & arg) -> ParserResult = 0; +}; + +struct BoundFlagRefBase : BoundRef +{ + virtual auto setFlag(bool flag) -> ParserResult = 0; + virtual auto isFlag() const -> bool + { + return true; + } +}; - template - struct BoundValueRef : BoundValueRefBase { - T &m_ref; +template +struct BoundValueRef : BoundValueRefBase +{ + T & m_ref; - explicit BoundValueRef( T &ref ) : m_ref( ref ) {} + explicit BoundValueRef(T & ref) : m_ref(ref) + {} - auto setValue( std::string const &arg ) -> ParserResult override { - return convertInto( arg, m_ref ); - } - }; + auto setValue(std::string const & arg) -> ParserResult override + { + return convertInto(arg, m_ref); + } +}; - template - struct BoundValueRef> : BoundValueRefBase { - std::vector &m_ref; +template +struct BoundValueRef> : BoundValueRefBase +{ + std::vector & m_ref; - explicit BoundValueRef( std::vector &ref ) : m_ref( ref ) {} + explicit BoundValueRef(std::vector & ref) : m_ref(ref) + {} - auto isContainer() const -> bool override { return true; } + auto isContainer() const -> bool override + { + return true; + } - auto setValue( std::string const &arg ) -> ParserResult override { - T temp; - auto result = convertInto( arg, temp ); - if( result ) - m_ref.push_back( temp ); - return result; + auto setValue(std::string const & arg) -> ParserResult override + { + T temp; + auto result = convertInto(arg, temp); + if (result) + { + m_ref.push_back(temp); } - }; - struct BoundFlagRef : BoundFlagRefBase { - bool &m_ref; + return result; + } +}; - explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} +struct BoundFlagRef : BoundFlagRefBase +{ + bool & m_ref; - auto setFlag( bool flag ) -> ParserResult override { - m_ref = flag; - return ParserResult::ok( ParseResultType::Matched ); - } - }; + explicit BoundFlagRef(bool & ref) : m_ref(ref) + {} + + auto setFlag(bool flag) -> ParserResult override + { + m_ref = flag; + return ParserResult::ok(ParseResultType::Matched); + } +}; + +template +struct LambdaInvoker +{ + static_assert(std::is_same::value, + "Lambda must return void or clara::ParserResult"); + + template + static auto invoke(L const & lambda, ArgType const & arg) -> ParserResult + { + return lambda(arg); + } +}; + +template<> +struct LambdaInvoker +{ + template + static auto invoke(L const & lambda, ArgType const & arg) -> ParserResult + { + lambda(arg); + return ParserResult::ok(ParseResultType::Matched); + } +}; + +template +inline auto invokeLambda(L const & lambda, std::string const & arg) -> ParserResult +{ + ArgType temp{}; + auto result = convertInto(arg, temp); + return !result ? + result : + LambdaInvoker::ReturnType>::invoke(lambda, temp); +} + +template +struct BoundLambda : BoundValueRefBase +{ + L m_lambda; + + static_assert(UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument"); + explicit BoundLambda(L const & lambda) : m_lambda(lambda) + {} + + auto setValue(std::string const & arg) -> ParserResult override + { + return invokeLambda::ArgType>(m_lambda, arg); + } +}; - template - struct LambdaInvoker { - static_assert( std::is_same::value, "Lambda must return void or clara::ParserResult" ); +template +struct BoundFlagLambda : BoundFlagRefBase +{ + L m_lambda; - template - static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { - return lambda( arg ); - } - }; + static_assert(UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument"); + static_assert(std::is_same::ArgType, bool>::value, "flags must be boolean"); - template<> - struct LambdaInvoker { - template - static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { - lambda( arg ); - return ParserResult::ok( ParseResultType::Matched ); - } - }; + explicit BoundFlagLambda(L const & lambda) : m_lambda(lambda) + {} - template - inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { - ArgType temp{}; - auto result = convertInto( arg, temp ); - return !result - ? result - : LambdaInvoker::ReturnType>::invoke( lambda, temp ); + auto setFlag(bool flag) -> ParserResult override + { + return LambdaInvoker::ReturnType>::invoke(m_lambda, flag); + } +}; + +enum class Optionality +{ + Optional, + Required, +}; + +struct Parser; + +class ParserBase +{ + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result + { + return Result::ok(); } + virtual auto parse(std::string const& exeName, + TokenStream const & tokens) const -> InternalParseResult = 0; + virtual auto cardinality() const -> size_t + { + return 1; + } - template - struct BoundLambda : BoundValueRefBase { - L m_lambda; + auto parse(Args const & args) const -> InternalParseResult + { + return parse(args.exeName(), TokenStream(args)); + } +}; - static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); - explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} +template +class ComposableParserImpl : public ParserBase +{ + public: + template + auto operator |(T const & other) const -> Parser; - auto setValue( std::string const &arg ) -> ParserResult override { - return invokeLambda::ArgType>( m_lambda, arg ); - } - }; + template + auto operator +(T const & other) const -> Parser; +}; + +// Common code and state for Args and Opts +template +class ParserRefImpl : public ComposableParserImpl +{ + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr m_ref; + std::string m_hint; + std::string m_description; + + explicit ParserRefImpl(std::shared_ptr const & ref) : m_ref(ref) + {} + + public: + template + ParserRefImpl(T & ref, std::string const & hint) : + m_ref(std::make_shared>(ref)), + m_hint(hint) + {} + + template + ParserRefImpl(LambdaT const & ref, std::string const & hint) : + m_ref(std::make_shared>(ref)), + m_hint(hint) + {} + + auto operator ()(std::string const & description) -> DerivedT & + { + m_description = description; + return static_cast(*this); + } - template - struct BoundFlagLambda : BoundFlagRefBase { - L m_lambda; + auto optional() -> DerivedT & + { + m_optionality = Optionality::Optional; + return static_cast(*this); + } - static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); - static_assert( std::is_same::ArgType, bool>::value, "flags must be boolean" ); + auto required() -> DerivedT & + { + m_optionality = Optionality::Required; + return static_cast(*this); + } - explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} + auto isOptional() const -> bool + { + return m_optionality == Optionality::Optional; + } - auto setFlag( bool flag ) -> ParserResult override { - return LambdaInvoker::ReturnType>::invoke( m_lambda, flag ); + auto cardinality() const -> size_t override + { + if (m_ref->isContainer()) + { + return 0; + } else + { + return 1; } - }; + } - enum class Optionality { Optional, Required }; + auto hint() const -> std::string + { + return m_hint; + } +}; - struct Parser; +class ExeName : public ComposableParserImpl +{ + std::shared_ptr m_name; + std::shared_ptr m_ref; - class ParserBase { - public: - virtual ~ParserBase() = default; - virtual auto validate() const -> Result { return Result::ok(); } - virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; - virtual auto cardinality() const -> size_t { return 1; } + template + static auto makeRef(LambdaT const & lambda) -> std::shared_ptr + { + return std::make_shared>(lambda); + } - auto parse( Args const &args ) const -> InternalParseResult { - return parse( args.exeName(), TokenStream( args ) ); - } - }; + public: + ExeName() : m_name(std::make_shared("")) + {} - template - class ComposableParserImpl : public ParserBase { - public: - template - auto operator|( T const &other ) const -> Parser; + explicit ExeName(std::string & ref) : ExeName() + { + m_ref = std::make_shared>(ref); + } - template - auto operator+( T const &other ) const -> Parser; - }; + template + explicit ExeName(LambdaT const& lambda) : ExeName() + { + m_ref = std::make_shared>(lambda); + } - // Common code and state for Args and Opts - template - class ParserRefImpl : public ComposableParserImpl { - protected: - Optionality m_optionality = Optionality::Optional; - std::shared_ptr m_ref; - std::string m_hint; - std::string m_description; - - explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {} - - public: - template - ParserRefImpl( T &ref, std::string const &hint ) - : m_ref( std::make_shared>( ref ) ), - m_hint( hint ) - {} + // The exe name is not parsed out of the normal tokens, but is handled specially + auto parse(std::string const&, TokenStream const & tokens) const -> InternalParseResult override + { + return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens)); + } - template - ParserRefImpl( LambdaT const &ref, std::string const &hint ) - : m_ref( std::make_shared>( ref ) ), - m_hint(hint) - {} + auto name() const -> std::string + { + return *m_name; + } - auto operator()( std::string const &description ) -> DerivedT & { - m_description = description; - return static_cast( *this ); - } + auto set(std::string const& newName) -> ParserResult + { + auto lastSlash = newName.find_last_of("\\/"); + auto filename = (lastSlash == std::string::npos) ? + newName : + newName.substr(lastSlash + 1); - auto optional() -> DerivedT & { - m_optionality = Optionality::Optional; - return static_cast( *this ); - }; + *m_name = filename; + if (m_ref) + { + return m_ref->setValue(filename); + } else + { + return ParserResult::ok(ParseResultType::Matched); + } + } +}; - auto required() -> DerivedT & { - m_optionality = Optionality::Required; - return static_cast( *this ); - }; +class Arg : public ParserRefImpl +{ + public: + using ParserRefImpl::ParserRefImpl; - auto isOptional() const -> bool { - return m_optionality == Optionality::Optional; + auto parse(std::string const &, TokenStream const & tokens) const -> InternalParseResult override + { + auto validationResult = validate(); + if (!validationResult) + { + return InternalParseResult(validationResult); } - auto cardinality() const -> size_t override { - if( m_ref->isContainer() ) - return 0; - else - return 1; + auto remainingTokens = tokens; + auto const & token = *remainingTokens; + if (token.type != TokenType::Argument) + { + return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens)); } - auto hint() const -> std::string { return m_hint; } - }; - - class ExeName : public ComposableParserImpl { - std::shared_ptr m_name; - std::shared_ptr m_ref; + assert(!m_ref->isFlag()); + auto valueRef = static_cast(m_ref.get()); - template - static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { - return std::make_shared>( lambda) ; + auto result = valueRef->setValue(remainingTokens->token); + if (!result) + { + return InternalParseResult(result); + } else + { + return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens)); } + } +}; - public: - ExeName() : m_name( std::make_shared( "" ) ) {} +inline auto normaliseOpt(std::string const & optName) -> std::string +{ +#ifdef CLARA_PLATFORM_WINDOWS + if (optName[0] == '/') + { + return "-" + optName.substr(1); + } else +#endif + return optName; +} - explicit ExeName( std::string &ref ) : ExeName() { - m_ref = std::make_shared>( ref ); - } +class Opt : public ParserRefImpl +{ + protected: + std::vector m_optNames; - template - explicit ExeName( LambdaT const& lambda ) : ExeName() { - m_ref = std::make_shared>( lambda ); - } + public: + template + explicit Opt(LambdaT const & ref) : ParserRefImpl(std::make_shared>(ref)) + {} - // The exe name is not parsed out of the normal tokens, but is handled specially - auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { - return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); - } + explicit Opt(bool & ref) : ParserRefImpl(std::make_shared(ref)) + {} - auto name() const -> std::string { return *m_name; } - auto set( std::string const& newName ) -> ParserResult { + template + Opt(LambdaT const & ref, std::string const & hint) : ParserRefImpl(ref, hint) + {} - auto lastSlash = newName.find_last_of( "\\/" ); - auto filename = ( lastSlash == std::string::npos ) - ? newName - : newName.substr( lastSlash+1 ); + template + Opt(T & ref, std::string const & hint) : ParserRefImpl(ref, hint) + {} - *m_name = filename; - if( m_ref ) - return m_ref->setValue( filename ); - else - return ParserResult::ok( ParseResultType::Matched ); - } - }; + auto operator [](std::string const & optName) -> Opt & + { + m_optNames.push_back(optName); + return *this; + } - class Arg : public ParserRefImpl { - public: - using ParserRefImpl::ParserRefImpl; + auto getHelpColumns() const -> std::vector + { + std::ostringstream oss; + bool first = true; + for (auto const & opt : m_optNames) + { + if (first) + { + first = false; + } else + { + oss << ", "; + } - auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { - auto validationResult = validate(); - if( !validationResult ) - return InternalParseResult( validationResult ); + oss << opt; + } - auto remainingTokens = tokens; - auto const &token = *remainingTokens; - if( token.type != TokenType::Argument ) - return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + if (!m_hint.empty()) + { + oss << " <" << m_hint << ">"; + } - assert( !m_ref->isFlag() ); - auto valueRef = static_cast( m_ref.get() ); + return {{oss.str(), m_description}}; + } - auto result = valueRef->setValue( remainingTokens->token ); - if( !result ) - return InternalParseResult( result ); - else - return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + auto isMatch(std::string const & optToken) const -> bool + { + auto normalisedToken = normaliseOpt(optToken); + for (auto const & name : m_optNames) + { + if (normaliseOpt(name) == normalisedToken) + { + return true; + } } - }; - inline auto normaliseOpt( std::string const &optName ) -> std::string { -#ifdef CLARA_PLATFORM_WINDOWS - if( optName[0] == '/' ) - return "-" + optName.substr( 1 ); - else -#endif - return optName; + return false; } - class Opt : public ParserRefImpl { - protected: - std::vector m_optNames; + using ParserBase::parse; + + auto parse(std::string const&, TokenStream const & tokens) const -> InternalParseResult override + { + auto validationResult = validate(); + if (!validationResult) + { + return InternalParseResult(validationResult); + } - public: - template - explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared>( ref ) ) {} + auto remainingTokens = tokens; + if (remainingTokens && (remainingTokens->type == TokenType::Option)) + { + auto const & token = *remainingTokens; + if (isMatch(token.token)) + { + if (m_ref->isFlag()) + { + auto flagRef = static_cast(m_ref.get()); + auto result = flagRef->setFlag(true); + if (!result) + { + return InternalParseResult(result); + } - explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared( ref ) ) {} + if (result.value() == ParseResultType::ShortCircuitAll) + { + return InternalParseResult::ok(ParseState(result.value(), remainingTokens)); + } + } else + { + auto valueRef = static_cast(m_ref.get()); + ++remainingTokens; + if (!remainingTokens) + { + return InternalParseResult::runtimeError("Expected argument following " + + token.token); + } - template - Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + auto const & argToken = *remainingTokens; + if (argToken.type != TokenType::Argument) + { + return InternalParseResult::runtimeError("Expected argument following " + + token.token); + } - template - Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + auto result = valueRef->setValue(argToken.token); + if (!result) + { + return InternalParseResult(result); + } - auto operator[]( std::string const &optName ) -> Opt & { - m_optNames.push_back( optName ); - return *this; - } + if (result.value() == ParseResultType::ShortCircuitAll) + { + return InternalParseResult::ok(ParseState(result.value(), remainingTokens)); + } + } - auto getHelpColumns() const -> std::vector { - std::ostringstream oss; - bool first = true; - for( auto const &opt : m_optNames ) { - if (first) - first = false; - else - oss << ", "; - oss << opt; + return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens)); } - if( !m_hint.empty() ) - oss << " <" << m_hint << ">"; - return { { oss.str(), m_description } }; } - auto isMatch( std::string const &optToken ) const -> bool { - auto normalisedToken = normaliseOpt( optToken ); - for( auto const &name : m_optNames ) { - if( normaliseOpt( name ) == normalisedToken ) - return true; - } - return false; + return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens)); + } + + auto validate() const -> Result override + { + if (m_optNames.empty()) + { + return Result::logicError("No options supplied to Opt"); } - using ParserBase::parse; - - auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { - auto validationResult = validate(); - if( !validationResult ) - return InternalParseResult( validationResult ); - - auto remainingTokens = tokens; - if( remainingTokens && remainingTokens->type == TokenType::Option ) { - auto const &token = *remainingTokens; - if( isMatch(token.token ) ) { - if( m_ref->isFlag() ) { - auto flagRef = static_cast( m_ref.get() ); - auto result = flagRef->setFlag( true ); - if( !result ) - return InternalParseResult( result ); - if( result.value() == ParseResultType::ShortCircuitAll ) - return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); - } else { - auto valueRef = static_cast( m_ref.get() ); - ++remainingTokens; - if( !remainingTokens ) - return InternalParseResult::runtimeError( "Expected argument following " + token.token ); - auto const &argToken = *remainingTokens; - if( argToken.type != TokenType::Argument ) - return InternalParseResult::runtimeError( "Expected argument following " + token.token ); - auto result = valueRef->setValue( argToken.token ); - if( !result ) - return InternalParseResult( result ); - if( result.value() == ParseResultType::ShortCircuitAll ) - return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); - } - return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); - } + for (auto const & name : m_optNames) + { + if (name.empty()) + { + return Result::logicError("Option name cannot be empty"); } - return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); - } - auto validate() const -> Result override { - if( m_optNames.empty() ) - return Result::logicError( "No options supplied to Opt" ); - for( auto const &name : m_optNames ) { - if( name.empty() ) - return Result::logicError( "Option name cannot be empty" ); #ifdef CLARA_PLATFORM_WINDOWS - if( name[0] != '-' && name[0] != '/' ) - return Result::logicError( "Option name must begin with '-' or '/'" ); + if ((name[0] != '-') && (name[0] != '/')) + { + return Result::logicError("Option name must begin with '-' or '/'"); + } + #else - if( name[0] != '-' ) - return Result::logicError( "Option name must begin with '-'" ); -#endif + if (name[0] != '-') + { + return Result::logicError("Option name must begin with '-'"); } - return ParserRefImpl::validate(); + +#endif } - }; - struct Help : Opt { - Help( bool &showHelpFlag ) - : Opt([&]( bool flag ) { + return ParserRefImpl::validate(); + } +}; + +struct Help : Opt +{ + Help(bool & showHelpFlag) : + Opt([&] (bool flag) + { showHelpFlag = flag; - return ParserResult::ok( ParseResultType::ShortCircuitAll ); + return ParserResult::ok(ParseResultType::ShortCircuitAll); }) + { + static_cast(*this)("display usage information") + ["-?"]["-h"]["--help"] + .optional(); + } +}; + + +struct Parser : ParserBase +{ + mutable ExeName m_exeName; + std::vector m_options; + std::vector m_args; + + auto operator |=(ExeName const & exeName) -> Parser & + { + m_exeName = exeName; + return *this; + } + + auto operator |=(Arg const & arg) -> Parser & + { + m_args.push_back(arg); + return *this; + } + + auto operator |=(Opt const & opt) -> Parser & + { + m_options.push_back(opt); + return *this; + } + + auto operator |=(Parser const & other) -> Parser & + { + m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); + m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); + return *this; + } + + template + auto operator |(T const & other) const -> Parser + { + return Parser(*this) |= other; + } + + // Forward deprecated interface with '+' instead of '|' + template + auto operator +=(T const & other) -> Parser & + { + return operator |=(other); + } + + template + auto operator +(T const & other) const -> Parser + { + return operator |(other); + } + + auto getHelpColumns() const -> std::vector + { + std::vector cols; + for (auto const & o : m_options) { - static_cast( *this ) - ("display usage information") - ["-?"]["-h"]["--help"] - .optional(); + auto childCols = o.getHelpColumns(); + cols.insert(cols.end(), childCols.begin(), childCols.end()); } - }; + return cols; + } + + void writeToStream(std::ostream & os) const + { + if (!m_exeName.name().empty()) + { + os << "usage:\n" << " " << m_exeName.name() << " "; + bool required = true, first = true; + for (auto const & arg : m_args) + { + if (first) + { + first = false; + } else + { + os << " "; + } + + if (arg.isOptional() && required) + { + os << "["; + required = false; + } - struct Parser : ParserBase { + os << "<" << arg.hint() << ">"; + if (arg.cardinality() == 0) + { + os << " ... "; + } + } - mutable ExeName m_exeName; - std::vector m_options; - std::vector m_args; + if (!required) + { + os << "]"; + } - auto operator|=( ExeName const &exeName ) -> Parser & { - m_exeName = exeName; - return *this; - } + if (!m_options.empty()) + { + os << " options"; + } - auto operator|=( Arg const &arg ) -> Parser & { - m_args.push_back(arg); - return *this; + os << "\n\nwhere options are:" << std::endl; } - auto operator|=( Opt const &opt ) -> Parser & { - m_options.push_back(opt); - return *this; + auto rows = getHelpColumns(); + size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; + size_t optWidth = 0; + for (auto const & cols : rows) + { + optWidth = (std::max)(optWidth, cols.left.size() + 2); } - auto operator|=( Parser const &other ) -> Parser & { - m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); - m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); - return *this; - } + optWidth = (std::min)(optWidth, consoleWidth / 2); - template - auto operator|( T const &other ) const -> Parser { - return Parser( *this ) |= other; + for (auto const & cols : rows) + { + auto row = + TextFlow::Column(cols.left).width(optWidth).indent(2) + + TextFlow::Spacer(4) + + TextFlow::Column(cols.right).width(consoleWidth - 7 - optWidth); + os << row << std::endl; } + } - // Forward deprecated interface with '+' instead of '|' - template - auto operator+=( T const &other ) -> Parser & { return operator|=( other ); } - template - auto operator+( T const &other ) const -> Parser { return operator|( other ); } - - auto getHelpColumns() const -> std::vector { - std::vector cols; - for (auto const &o : m_options) { - auto childCols = o.getHelpColumns(); - cols.insert( cols.end(), childCols.begin(), childCols.end() ); + friend auto operator <<(std::ostream & os, Parser const & parser) -> std::ostream& + { + parser.writeToStream(os); + return os; + } + + auto validate() const -> Result override + { + for (auto const & opt : m_options) + { + auto result = opt.validate(); + if (!result) + { + return result; } - return cols; } - void writeToStream( std::ostream &os ) const { - if (!m_exeName.name().empty()) { - os << "usage:\n" << " " << m_exeName.name() << " "; - bool required = true, first = true; - for( auto const &arg : m_args ) { - if (first) - first = false; - else - os << " "; - if( arg.isOptional() && required ) { - os << "["; - required = false; - } - os << "<" << arg.hint() << ">"; - if( arg.cardinality() == 0 ) - os << " ... "; - } - if( !required ) - os << "]"; - if( !m_options.empty() ) - os << " options"; - os << "\n\nwhere options are:" << std::endl; + for (auto const & arg : m_args) + { + auto result = arg.validate(); + if (!result) + { + return result; } + } - auto rows = getHelpColumns(); - size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; - size_t optWidth = 0; - for( auto const &cols : rows ) - optWidth = (std::max)(optWidth, cols.left.size() + 2); + return Result::ok(); + } - optWidth = (std::min)(optWidth, consoleWidth/2); + using ParserBase::parse; - for( auto const &cols : rows ) { - auto row = - TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + - TextFlow::Spacer(4) + - TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); - os << row << std::endl; - } - } + auto parse(std::string const& exeName, TokenStream const & tokens) const -> InternalParseResult override + { + struct ParserInfo + { + ParserBase const *parser = nullptr; + size_t count = 0; + }; - friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { - parser.writeToStream( os ); - return os; - } + const size_t totalParsers = m_options.size() + m_args.size(); + assert(totalParsers < 512); + // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do + ParserInfo parseInfos[512]; - auto validate() const -> Result override { - for( auto const &opt : m_options ) { - auto result = opt.validate(); - if( !result ) - return result; + { + size_t i = 0; + for (auto const & opt : m_options) + { + parseInfos[i++].parser = &opt; } - for( auto const &arg : m_args ) { - auto result = arg.validate(); - if( !result ) - return result; + + for (auto const & arg : m_args) + { + parseInfos[i++].parser = &arg; } - return Result::ok(); } - using ParserBase::parse; - - auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { + m_exeName.set(exeName); - struct ParserInfo { - ParserBase const* parser = nullptr; - size_t count = 0; - }; - const size_t totalParsers = m_options.size() + m_args.size(); - assert( totalParsers < 512 ); - // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do - ParserInfo parseInfos[512]; + auto result = InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens)); + while (result.value().remainingTokens()) + { + bool tokenParsed = false; + for (size_t i = 0; i < totalParsers; ++i) { - size_t i = 0; - for (auto const &opt : m_options) parseInfos[i++].parser = &opt; - for (auto const &arg : m_args) parseInfos[i++].parser = &arg; - } + auto& parseInfo = parseInfos[i]; + if ((parseInfo.parser->cardinality() == 0) || + (parseInfo.count < parseInfo.parser->cardinality())) + { + result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); + if (!result) + { + return result; + } - m_exeName.set( exeName ); - - auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); - while( result.value().remainingTokens() ) { - bool tokenParsed = false; - - for( size_t i = 0; i < totalParsers; ++i ) { - auto& parseInfo = parseInfos[i]; - if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { - result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); - if (!result) - return result; - if (result.value().type() != ParseResultType::NoMatch) { - tokenParsed = true; - ++parseInfo.count; - break; - } + if (result.value().type() != ParseResultType::NoMatch) + { + tokenParsed = true; + ++parseInfo.count; + break; } } + } + + if (result.value().type() == ParseResultType::ShortCircuitAll) + { + return result; + } - if( result.value().type() == ParseResultType::ShortCircuitAll ) - return result; - if( !tokenParsed ) - return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); + if (!tokenParsed) + { + return InternalParseResult::runtimeError( + "Unrecognised token: " + result.value().remainingTokens()->token); } - // !TBD Check missing required options - return result; } - }; - template - template - auto ComposableParserImpl::operator|( T const &other ) const -> Parser { - return Parser() | static_cast( *this ) | other; + // !TBD Check missing required options + return result; } -} // namespace detail +}; + +template +template +auto ComposableParserImpl::operator |(T const & other) const -> Parser +{ + return Parser() | static_cast(*this) | other; +} +}// namespace detail // A Combined parser @@ -1257,8 +1732,6 @@ using detail::ParseResultType; // Result type for parser operation using detail::ParserResult; - - -} // namespace clara +}// namespace clara #endif // CLARA_HPP_INCLUDED diff --git a/src/virtual-keyboard.cpp b/src/virtual-keyboard.cpp index d84fef5..d7df105 100644 --- a/src/virtual-keyboard.cpp +++ b/src/virtual-keyboard.cpp @@ -13,49 +13,49 @@ 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, gdk_wayland_seat_get_wl_seat(seat->gobj())); - - this->send_keymap(); - } - - void VirtualKeyboardDevice::send_keymap() - { - /* The keymap string is defined in keymap.tpp, it is keymap_normal */ - #include "keymap.tpp" - - size_t keymap_size = strlen(keymap) + 1; - int keymap_fd = os_create_anonymous_file(keymap_size); - void *ptr = mmap(NULL, keymap_size, PROT_READ | PROT_WRITE, MAP_SHARED, - keymap_fd, 0); - - std::strcpy((char*)ptr, keymap); - zwp_virtual_keyboard_v1_keymap(vk, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, - keymap_fd, keymap_size); - } - - uint32_t get_current_time() - { - timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return ts.tv_sec * 1000ll + ts.tv_nsec / 1000000ll; - } - - void VirtualKeyboardDevice::send_key(uint32_t key, uint32_t state) const - { - zwp_virtual_keyboard_v1_key(vk, get_current_time(), key, state); - } - - void VirtualKeyboardDevice::set_shift(bool shift_on) - { - shift_pressed_counter += (shift_on ? 1 : -1); - - const int modifier_shift_code = 1; - zwp_virtual_keyboard_v1_modifiers(vk, - shift_pressed_counter ? modifier_shift_code : 0, 0, 0, 0); - } +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, gdk_wayland_seat_get_wl_seat(seat->gobj())); + + this->send_keymap(); +} + +void VirtualKeyboardDevice::send_keymap() +{ + /* The keymap string is defined in keymap.tpp, it is keymap_normal */ +#include "keymap.tpp" + + size_t keymap_size = strlen(keymap) + 1; + int keymap_fd = os_create_anonymous_file(keymap_size); + void *ptr = mmap(NULL, keymap_size, PROT_READ | PROT_WRITE, MAP_SHARED, + keymap_fd, 0); + + std::strcpy((char*)ptr, keymap); + zwp_virtual_keyboard_v1_keymap(vk, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, + keymap_fd, keymap_size); +} + +uint32_t get_current_time() +{ + timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000ll + ts.tv_nsec / 1000000ll; +} + +void VirtualKeyboardDevice::send_key(uint32_t key, uint32_t state) const +{ + zwp_virtual_keyboard_v1_key(vk, get_current_time(), key, state); +} + +void VirtualKeyboardDevice::set_shift(bool shift_on) +{ + shift_pressed_counter += (shift_on ? 1 : -1); + + const int modifier_shift_code = 1; + zwp_virtual_keyboard_v1_modifiers(vk, + shift_pressed_counter ? modifier_shift_code : 0, 0, 0, 0); +} } diff --git a/src/virtual-keyboard.hpp b/src/virtual-keyboard.hpp index c67fee2..94d570b 100644 --- a/src/virtual-keyboard.hpp +++ b/src/virtual-keyboard.hpp @@ -5,17 +5,17 @@ namespace wf { - class VirtualKeyboardDevice - { - int shift_pressed_counter = 0; +class VirtualKeyboardDevice +{ + int shift_pressed_counter = 0; - void send_keymap(); - zwp_virtual_keyboard_v1 *vk; + void send_keymap(); + zwp_virtual_keyboard_v1 *vk; - public: - VirtualKeyboardDevice(); + public: + VirtualKeyboardDevice(); - void set_shift(bool shift_on); - void send_key(uint32_t key, uint32_t state) const; - }; + void set_shift(bool shift_on); + void send_key(uint32_t key, uint32_t state) const; +}; } diff --git a/src/wayland-window.cpp b/src/wayland-window.cpp index cffd159..0398768 100644 --- a/src/wayland-window.cpp +++ b/src/wayland-window.cpp @@ -13,214 +13,219 @@ namespace wf { - // listeners - static void registry_add_object(void *data, struct wl_registry *registry, - uint32_t name, const char *interface, uint32_t version) - { - auto display = static_cast (data); - - if (strcmp(interface, zwf_shell_manager_v2_interface.name) == 0) - { - 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) - { - display->vk_manager = (zwp_virtual_keyboard_manager_v1*) - wl_registry_bind(registry, name, - &zwp_virtual_keyboard_manager_v1_interface, 1u); - } - } +// listeners +static void registry_add_object(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) +{ + auto display = static_cast(data); - static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) + if (strcmp(interface, zwf_shell_manager_v2_interface.name) == 0) { - /* no-op */ + display->zwf_manager = + (zwf_shell_manager_v2*)wl_registry_bind(registry, name, + &zwf_shell_manager_v2_interface, std::min(version, 1u)); } - static struct wl_registry_listener registry_listener = + if (strcmp(interface, zwp_virtual_keyboard_manager_v1_interface.name) == 0) { - ®istry_add_object, - ®istry_remove_object - }; + display->vk_manager = (zwp_virtual_keyboard_manager_v1*) + wl_registry_bind(registry, name, + &zwp_virtual_keyboard_manager_v1_interface, 1u); + } +} - WaylandDisplay::WaylandDisplay() - { - auto gdk_display = gdk_display_get_default(); - auto display = gdk_wayland_display_get_wl_display(gdk_display); +static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) +{ + /* no-op */ +} - if (!display) - { - std::cerr << "Failed to connect to wayland display!" - << " Are you sure you are running a wayland compositor?" << std::endl; - std::exit(-1); - } +static struct wl_registry_listener registry_listener = +{ + ®istry_add_object, + ®istry_remove_object +}; - wl_registry *registry = wl_display_get_registry(display); - wl_registry_add_listener(registry, ®istry_listener, this); - wl_display_dispatch(display); - wl_display_roundtrip(display); +WaylandDisplay::WaylandDisplay() +{ + auto gdk_display = gdk_display_get_default(); + auto display = gdk_wayland_display_get_wl_display(gdk_display); - if (!vk_manager) - { - std::cerr << "Compositor doesn't support the virtual-keyboard-v1 " - << "protocol, exiting" << std::endl; - std::exit(-1); - } + if (!display) + { + std::cerr << "Failed to connect to wayland display!" << + " Are you sure you are running a wayland compositor?" << std::endl; + std::exit(-1); } - WaylandDisplay& WaylandDisplay::get() + wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, this); + wl_display_dispatch(display); + wl_display_roundtrip(display); + + if (!vk_manager) { - static WaylandDisplay instance; - return instance; + std::cerr << "Compositor doesn't support the virtual-keyboard-v1 " << + "protocol, exiting" << std::endl; + std::exit(-1); } +} - int32_t WaylandWindow::check_anchor(std::string anchor) - { - std::transform(anchor.begin(), anchor.end(), anchor.begin(), ::tolower); +WaylandDisplay& WaylandDisplay::get() +{ + static WaylandDisplay instance; + return instance; +} - 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; - } else if (anchor.compare("pinned") == 0) - { - parsed_anchor = ANCHOR_PINNED_BOTTOM; - } +int32_t WaylandWindow::check_anchor(std::string anchor) +{ + std::transform(anchor.begin(), anchor.end(), anchor.begin(), ::tolower); - return parsed_anchor; + 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; + } else if (anchor.compare("pinned") == 0) + { + parsed_anchor = ANCHOR_PINNED_BOTTOM; } - void WaylandWindow::init(int width, int height, std::string anchor) + 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"); + gtk_layer_set_exclusive_zone(this->gobj(), -1); + auto layer_anchor = check_anchor(anchor); + if (layer_anchor > -1) { - 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"); - gtk_layer_set_exclusive_zone(this->gobj(), -1); - auto layer_anchor = check_anchor(anchor); - if (layer_anchor > -1) - { - gtk_layer_set_anchor(this->gobj(), - (GtkLayerShellEdge)layer_anchor, true); - } else if (layer_anchor == ANCHOR_PINNED_BOTTOM) - { - gtk_layer_set_anchor(this->gobj(), - GTK_LAYER_SHELL_EDGE_BOTTOM, true); - gtk_layer_set_anchor(this->gobj(), - GTK_LAYER_SHELL_EDGE_LEFT, true); - gtk_layer_set_anchor(this->gobj(), - GTK_LAYER_SHELL_EDGE_RIGHT, true); - gtk_layer_auto_exclusive_zone_enable(this->gobj()); - } + gtk_layer_set_anchor(this->gobj(), + (GtkLayerShellEdge)layer_anchor, true); + } else if (layer_anchor == ANCHOR_PINNED_BOTTOM) + { + gtk_layer_set_anchor(this->gobj(), + GTK_LAYER_SHELL_EDGE_BOTTOM, true); + gtk_layer_set_anchor(this->gobj(), + GTK_LAYER_SHELL_EDGE_LEFT, true); + gtk_layer_set_anchor(this->gobj(), + GTK_LAYER_SHELL_EDGE_RIGHT, true); + gtk_layer_auto_exclusive_zone_enable(this->gobj()); + } - this->set_size_request(width, height); - this->show_all(); - auto gdk_window = this->get_window()->gobj(); - auto surface = gdk_wayland_window_get_wl_surface(gdk_window); + this->set_size_request(width, height); + this->show_all(); + auto gdk_window = this->get_window()->gobj(); + auto surface = gdk_wayland_window_get_wl_surface(gdk_window); - if (surface && WaylandDisplay::get().zwf_manager) - { - this->wf_surface = zwf_shell_manager_v2_get_wf_surface( - WaylandDisplay::get().zwf_manager, surface); - } + if (surface && WaylandDisplay::get().zwf_manager) + { + this->wf_surface = zwf_shell_manager_v2_get_wf_surface( + WaylandDisplay::get().zwf_manager, surface); } +} + +void WaylandWindow::init_headerbar(int headerbar_size) +{ + std::vector buttons = { + &top_button, &bottom_button, &close_button + }; - void WaylandWindow::init_headerbar(int headerbar_size) + const int button_size = 0.8 * headerbar_size; + for (auto& button : buttons) { - std::vector buttons = { - &top_button, &bottom_button, &close_button - }; + button->get_style_context()->add_class("image-button"); + button->set_size_request(button_size, button_size); + button->set_margin_bottom(OSK_SPACING); + button->set_margin_top(OSK_SPACING); + button->set_margin_left(OSK_SPACING); + button->set_margin_right(OSK_SPACING); + } - const int button_size = 0.8 * headerbar_size; - for (auto& button : buttons) - { - button->get_style_context()->add_class("image-button"); - button->set_size_request(button_size, button_size); - button->set_margin_bottom(OSK_SPACING); - button->set_margin_top(OSK_SPACING); - button->set_margin_left(OSK_SPACING); - button->set_margin_right(OSK_SPACING); - } + static const std::map gtk_size_map = { + {Gtk::ICON_SIZE_MENU, 16}, + {Gtk::ICON_SIZE_SMALL_TOOLBAR, 16}, + {Gtk::ICON_SIZE_LARGE_TOOLBAR, 24}, + {Gtk::ICON_SIZE_BUTTON, 16}, + {Gtk::ICON_SIZE_DND, 32}, + {Gtk::ICON_SIZE_DIALOG, 48} + }; - static const std::map gtk_size_map = { - {Gtk::ICON_SIZE_MENU, 16}, - {Gtk::ICON_SIZE_SMALL_TOOLBAR, 16}, - {Gtk::ICON_SIZE_LARGE_TOOLBAR, 24}, - {Gtk::ICON_SIZE_BUTTON, 16}, - {Gtk::ICON_SIZE_DND, 32}, - {Gtk::ICON_SIZE_DIALOG, 48} - }; - - Gtk::BuiltinIconSize desired_gtk_icon_size = Gtk::ICON_SIZE_MENU; - for (auto [gtk_size, pixel_size] : gtk_size_map) + Gtk::BuiltinIconSize desired_gtk_icon_size = Gtk::ICON_SIZE_MENU; + for (auto [gtk_size, pixel_size] : gtk_size_map) + { + if (pixel_size <= button_size) { - if (pixel_size <= button_size) - { - desired_gtk_icon_size = gtk_size; - } + desired_gtk_icon_size = gtk_size; } - - close_button.set_image_from_icon_name("window-close-symbolic", desired_gtk_icon_size); - close_button.signal_clicked().connect_notify([=] () { - this->get_application()->quit(); - }); - - top_button.set_image_from_icon_name("pan-up-symbolic", desired_gtk_icon_size); - top_button.signal_clicked().connect_notify([=] () { - gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_TOP, true); - gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, false); - }); - - bottom_button.set_image_from_icon_name("pan-down-symbolic", desired_gtk_icon_size); - bottom_button.signal_clicked().connect_notify([=] () { - gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_TOP, false); - gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, true); - }); - - // setup move gesture - 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); - headerbar_box.pack_end(close_button, false, false); - headerbar_box.pack_start(top_button, false, false); - headerbar_box.pack_start(bottom_button, false, false); - - layout_box.pack_start(headerbar_box); - layout_box.set_spacing(OSK_SPACING); - this->add(layout_box); } - WaylandWindow::WaylandWindow(int width, int height, std::string anchor, int headerbar_size) - : Gtk::Window() + close_button.set_image_from_icon_name("window-close-symbolic", desired_gtk_icon_size); + close_button.signal_clicked().connect_notify([=] () { - init_headerbar(headerbar_size); - // setup gtk layer shell - init(width, height, anchor); - } + this->get_application()->quit(); + }); - void WaylandWindow::set_widget(Gtk::Widget& w) + top_button.set_image_from_icon_name("pan-up-symbolic", desired_gtk_icon_size); + top_button.signal_clicked().connect_notify([=] () { - if (current_widget) - this->layout_box.remove(*current_widget); + gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_TOP, true); + gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, false); + }); - this->layout_box.pack_end(w); - current_widget = &w; + bottom_button.set_image_from_icon_name("pan-down-symbolic", desired_gtk_icon_size); + bottom_button.signal_clicked().connect_notify([=] () + { + gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_TOP, false); + gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, true); + }); + + // setup move gesture + 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); + headerbar_box.pack_end(close_button, false, false); + headerbar_box.pack_start(top_button, false, false); + headerbar_box.pack_start(bottom_button, false, false); + + layout_box.pack_start(headerbar_box); + layout_box.set_spacing(OSK_SPACING); + this->add(layout_box); +} - w.set_margin_bottom(OSK_SPACING); - w.set_margin_left(OSK_SPACING); - w.set_margin_right(OSK_SPACING); - this->show_all(); +WaylandWindow::WaylandWindow(int width, int height, std::string anchor, int headerbar_size) : + Gtk::Window() +{ + init_headerbar(headerbar_size); + // 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(); +} } diff --git a/src/wayland-window.hpp b/src/wayland-window.hpp index 9ee20ca..70f81c0 100644 --- a/src/wayland-window.hpp +++ b/src/wayland-window.hpp @@ -14,36 +14,36 @@ static constexpr int32_t ANCHOR_PINNED_BOTTOM = -2; namespace wf { - class WaylandDisplay - { - WaylandDisplay(); +class WaylandDisplay +{ + WaylandDisplay(); - public: - static WaylandDisplay& get(); + public: + static WaylandDisplay& get(); - zwf_shell_manager_v2 *zwf_manager = nullptr; - zwp_virtual_keyboard_manager_v1 *vk_manager = nullptr; - }; + zwf_shell_manager_v2 *zwf_manager = nullptr; + zwp_virtual_keyboard_manager_v1 *vk_manager = nullptr; +}; - class WaylandWindow : public Gtk::Window - { - zwf_surface_v2 *wf_surface = nullptr; +class WaylandWindow : public Gtk::Window +{ + zwf_surface_v2 *wf_surface = nullptr; - Gtk::Widget* current_widget = nullptr; - Gtk::Button close_button; - Gtk::Button top_button; - Gtk::Button bottom_button; + Gtk::Widget *current_widget = nullptr; + Gtk::Button close_button; + Gtk::Button top_button; + Gtk::Button bottom_button; - Gtk::HBox headerbar_box; - Gtk::VBox layout_box; + Gtk::HBox headerbar_box; + Gtk::VBox layout_box; - int32_t check_anchor(std::string anchor); - void init(int width, int height, std::string anchor); - void init_headerbar(int headerbar_size); + int32_t check_anchor(std::string anchor); + void init(int width, int height, std::string anchor); + void init_headerbar(int headerbar_size); - public: - WaylandWindow(int width, int height, std::string anchor, int headerbar_size); - void set_widget(Gtk::Widget& w); - }; + public: + WaylandWindow(int width, int height, std::string anchor, int headerbar_size); + void set_widget(Gtk::Widget& w); +}; } From 2292c96dc509fbc3e4cf40043df84b423e3e023c Mon Sep 17 00:00:00 2001 From: trigg Date: Thu, 11 Jun 2026 11:16:08 +0100 Subject: [PATCH 2/4] chore: upgrade to gtk4 --- meson.build | 16 ++++----- src/main.cpp | 28 ++++++++++----- src/osk.hpp | 9 ++--- src/virtual-keyboard.cpp | 2 +- src/wayland-window.cpp | 77 +++++++++++++++------------------------- src/wayland-window.hpp | 7 ++-- 6 files changed, 64 insertions(+), 75 deletions(-) diff --git a/meson.build b/meson.build index b9e368d..4f3e6ca 100644 --- a/meson.build +++ b/meson.build @@ -1,30 +1,30 @@ project( 'wf-simple-osk', - 'c', + 'c', 'cpp', version: '0.1', license: 'MIT', meson_version: '>=0.43.0', default_options: [ 'cpp_std=c++17', - 'c_std=c11', + 'c_std=c11', 'warning_level=2', 'werror=false', ], ) -gtkmm = dependency('gtkmm-3.0') +gtkmm = dependency('gtkmm-4.0') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols') -gtkls = dependency('gtk-layer-shell-0') +gtkls = dependency('gtk4-layer-shell-0', version: '>=1.3.0', required: false) -add_project_link_arguments(['-rdynamic'], language:'cpp') +add_project_link_arguments(['-rdynamic'], language: 'cpp') add_project_arguments(['-Wno-unused-parameter'], language: 'cpp') subdir('proto') subdir('src') install_data( - 'wf-osk.desktop', - install_dir: '@0@/share/applications'.format(get_option('prefix')) - ) + 'wf-osk.desktop', + install_dir: '@0@/share/applications'.format(get_option('prefix')), +) \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 55e3dd7..99411f4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include "gtkmm/gestureclick.h" #include "osk.hpp" #include #include @@ -30,13 +31,17 @@ KeyButton::KeyButton(Key key, int width, int height) this->button.set_size_request(width, height); this->button.set_label(key.text); - this->button.signal_pressed().connect_notify( - sigc::mem_fun(this, &KeyButton::on_pressed)); - this->button.signal_released().connect_notify( - sigc::mem_fun(this, &KeyButton::on_released)); + auto click_gesture = Gtk::GestureClick::create(); + + click_gesture->signal_pressed().connect( + sigc::mem_fun(*this, &KeyButton::on_pressed)); + click_gesture->signal_released().connect( + sigc::mem_fun(*this, &KeyButton::on_released)); + + this->button.add_controller(click_gesture); } -void KeyButton::on_pressed() +void KeyButton::on_pressed(int button, double x, double y) { auto& keyboard = Keyboard::get(); if (IS_COMMAND(this->code)) @@ -53,7 +58,7 @@ void KeyButton::on_pressed() WL_KEYBOARD_KEY_STATE_PRESSED); } -void KeyButton::on_released() +void KeyButton::on_released(int button, double x, double y) { auto& keyboard = Keyboard::get(); if (IS_COMMAND(this->code)) @@ -87,13 +92,14 @@ KeyboardRow::KeyboardRow(std::vector keys, { this->keys.emplace_back(std::make_unique(key, int(key.width / sum * total_buttons), height)); - this->box.pack_start(this->keys.back()->button); + this->box.append(this->keys.back()->button); } } KeyboardLayout::KeyboardLayout(std::vector> keys, int32_t width, int32_t height) { + box.set_orientation(Gtk::Orientation::VERTICAL); box.set_spacing(spacing); int total_spacing = std::min((int)keys.size() - 1, 0) * spacing; @@ -102,7 +108,7 @@ KeyboardLayout::KeyboardLayout(std::vector> keys, { this->rows.emplace_back( std::make_unique(row, width, row_height)); - this->box.pack_start(this->rows.back()->box); + this->box.append(this->rows.back()->box); } } @@ -215,5 +221,9 @@ int main(int argc, char **argv) auto app = Gtk::Application::create(); wf::osk::Keyboard::create(); - return app->run(wf::osk::Keyboard::get().get_window()); + app->signal_activate().connect([app] () + { + app->add_window(wf::osk::Keyboard::get().get_window()); + }); + return app->run(); } diff --git a/src/osk.hpp b/src/osk.hpp index 91a48a1..18d7b94 100644 --- a/src/osk.hpp +++ b/src/osk.hpp @@ -6,6 +6,7 @@ #include +#include "gtkmm/enums.h" #include "virtual-keyboard.hpp" #include "wayland-window.hpp" @@ -31,13 +32,13 @@ struct KeyButton KeyButton(Key key, int width, int height); private: - void on_pressed(); - void on_released(); + void on_pressed(int, double, double); + void on_released(int, double, double); }; struct KeyboardRow { - Gtk::HBox box; + Gtk::Box box; std::vector> keys; KeyboardRow(std::vector keys, @@ -46,7 +47,7 @@ struct KeyboardRow struct KeyboardLayout { - Gtk::VBox box; + Gtk::Box box; std::vector> rows; KeyboardLayout(std::vector> keys, diff --git a/src/virtual-keyboard.cpp b/src/virtual-keyboard.cpp index d7df105..545180a 100644 --- a/src/virtual-keyboard.cpp +++ b/src/virtual-keyboard.cpp @@ -1,4 +1,5 @@ #include "virtual-keyboard.hpp" +#include "gdk/wayland/gdkwayland.h" #include "wayland-window.hpp" #include "shared/os-compatibility.h" @@ -9,7 +10,6 @@ #include #include -#include namespace wf { diff --git a/src/wayland-window.cpp b/src/wayland-window.cpp index 0398768..0dac098 100644 --- a/src/wayland-window.cpp +++ b/src/wayland-window.cpp @@ -1,11 +1,11 @@ #include "wayland-window.hpp" +#include "gtkmm/enums.h" #include #include -#include -#include -#include +#include +#include #include -#include +#include #include #include @@ -124,9 +124,9 @@ void WaylandWindow::init(int width, int height, std::string anchor) } this->set_size_request(width, height); - this->show_all(); - auto gdk_window = this->get_window()->gobj(); - auto surface = gdk_wayland_window_get_wl_surface(gdk_window); + this->show(); + auto gdk_window = this->get_surface()->gobj(); + auto surface = gdk_wayland_surface_get_wl_surface(gdk_window); if (surface && WaylandDisplay::get().zwf_manager) { @@ -148,61 +148,40 @@ void WaylandWindow::init_headerbar(int headerbar_size) button->set_size_request(button_size, button_size); button->set_margin_bottom(OSK_SPACING); button->set_margin_top(OSK_SPACING); - button->set_margin_left(OSK_SPACING); - button->set_margin_right(OSK_SPACING); + button->set_margin_start(OSK_SPACING); + button->set_margin_end(OSK_SPACING); } - static const std::map gtk_size_map = { - {Gtk::ICON_SIZE_MENU, 16}, - {Gtk::ICON_SIZE_SMALL_TOOLBAR, 16}, - {Gtk::ICON_SIZE_LARGE_TOOLBAR, 24}, - {Gtk::ICON_SIZE_BUTTON, 16}, - {Gtk::ICON_SIZE_DND, 32}, - {Gtk::ICON_SIZE_DIALOG, 48} - }; - - Gtk::BuiltinIconSize desired_gtk_icon_size = Gtk::ICON_SIZE_MENU; - for (auto [gtk_size, pixel_size] : gtk_size_map) - { - if (pixel_size <= button_size) - { - desired_gtk_icon_size = gtk_size; - } - } - - close_button.set_image_from_icon_name("window-close-symbolic", desired_gtk_icon_size); - close_button.signal_clicked().connect_notify([=] () + close_button.set_image_from_icon_name("window-close-symbolic"); + close_button.signal_clicked().connect([=] () { this->get_application()->quit(); - }); + }, true); - top_button.set_image_from_icon_name("pan-up-symbolic", desired_gtk_icon_size); - top_button.signal_clicked().connect_notify([=] () + top_button.set_image_from_icon_name("pan-up-symbolic"); + top_button.signal_clicked().connect([=] () { gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_TOP, true); gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, false); - }); + }, true); - bottom_button.set_image_from_icon_name("pan-down-symbolic", desired_gtk_icon_size); - bottom_button.signal_clicked().connect_notify([=] () + bottom_button.set_image_from_icon_name("pan-down-symbolic"); + bottom_button.signal_clicked().connect([=] () { gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_TOP, false); gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, true); - }); - - // setup move gesture - Gtk::HeaderBar bar; - headerbar_box.override_background_color(bar.get_style_context()->get_background_color()); + }, true); // setup headerbar layout headerbar_box.set_size_request(-1, headerbar_size); - headerbar_box.pack_end(close_button, false, false); - headerbar_box.pack_start(top_button, false, false); - headerbar_box.pack_start(bottom_button, false, false); + headerbar_box.append(top_button); + headerbar_box.append(bottom_button); + headerbar_box.append(close_button); - layout_box.pack_start(headerbar_box); + layout_box.set_orientation(Gtk::Orientation::VERTICAL); + layout_box.append(headerbar_box); layout_box.set_spacing(OSK_SPACING); - this->add(layout_box); + this->set_child(layout_box); } WaylandWindow::WaylandWindow(int width, int height, std::string anchor, int headerbar_size) : @@ -220,12 +199,12 @@ void WaylandWindow::set_widget(Gtk::Widget& w) this->layout_box.remove(*current_widget); } - this->layout_box.pack_end(w); + this->layout_box.append(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(); + w.set_margin_start(OSK_SPACING); + w.set_margin_end(OSK_SPACING); + this->show(); } } diff --git a/src/wayland-window.hpp b/src/wayland-window.hpp index 70f81c0..ebee47a 100644 --- a/src/wayland-window.hpp +++ b/src/wayland-window.hpp @@ -1,9 +1,8 @@ #pragma once -#include +#include #include #include -#include #include #include #include @@ -35,8 +34,8 @@ class WaylandWindow : public Gtk::Window Gtk::Button bottom_button; - Gtk::HBox headerbar_box; - Gtk::VBox layout_box; + Gtk::Box headerbar_box; + Gtk::Box layout_box; int32_t check_anchor(std::string anchor); void init(int width, int height, std::string anchor); From 6536cbd7d2c8d31cee28d58720a4d716f7efd4ee Mon Sep 17 00:00:00 2001 From: trigg Date: Thu, 11 Jun 2026 11:28:20 +0100 Subject: [PATCH 3/4] release key fixed --- src/main.cpp | 7 ++++++- src/osk.hpp | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 99411f4..30108bf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include "gtkmm/gesture.h" #include "gtkmm/gestureclick.h" #include "osk.hpp" #include @@ -31,7 +32,7 @@ KeyButton::KeyButton(Key key, int width, int height) this->button.set_size_request(width, height); this->button.set_label(key.text); - auto click_gesture = Gtk::GestureClick::create(); + click_gesture = Gtk::GestureClick::create(); click_gesture->signal_pressed().connect( sigc::mem_fun(*this, &KeyButton::on_pressed)); @@ -43,12 +44,15 @@ KeyButton::KeyButton(Key key, int width, int height) void KeyButton::on_pressed(int button, double x, double y) { + std::cout << "Pressed" << std::endl; auto& keyboard = Keyboard::get(); if (IS_COMMAND(this->code)) { return; } + click_gesture->set_state(Gtk::EventSequenceState::CLAIMED); + if (this->code & USE_SHIFT) { keyboard.get_device().set_shift(true); @@ -60,6 +64,7 @@ void KeyButton::on_pressed(int button, double x, double y) void KeyButton::on_released(int button, double x, double y) { + std::cout << "Released" << std::endl; auto& keyboard = Keyboard::get(); if (IS_COMMAND(this->code)) { diff --git a/src/osk.hpp b/src/osk.hpp index 18d7b94..cb113fe 100644 --- a/src/osk.hpp +++ b/src/osk.hpp @@ -7,6 +7,7 @@ #include #include "gtkmm/enums.h" +#include "gtkmm/gestureclick.h" #include "virtual-keyboard.hpp" #include "wayland-window.hpp" @@ -34,6 +35,7 @@ struct KeyButton private: void on_pressed(int, double, double); void on_released(int, double, double); + std::shared_ptr click_gesture; }; struct KeyboardRow From 3ef35c44dfd66b3828011a5587790b2bdb965a0e Mon Sep 17 00:00:00 2001 From: trigg Date: Fri, 12 Jun 2026 17:54:49 +0100 Subject: [PATCH 4/4] osk: add layout switching via IPC --- .gitmodules | 3 + data/layouts/ansi.xml | 426 +++++++++++++++++++++++++ data/layouts/iso.xml | 440 +++++++++++++++++++++++++ data/meson.build | 4 + meson.build | 11 +- metadata/meson.build | 1 + metadata/osk.xml | 43 +++ proto/input-method-unstable-v2.xml | 494 +++++++++++++++++++++++++++++ proto/meson.build | 14 +- src/layouts.tpp | 122 ------- src/main.cpp | 384 +++++++++++++++++----- src/meson.build | 14 +- src/osk.hpp | 70 ++-- src/shared/os-compatibility.c | 196 ------------ src/shared/os-compatibility.h | 43 --- src/util/wf-ipc.cpp | 395 +++++++++++++++++++++++ src/util/wf-ipc.hpp | 82 +++++ src/virtual-keyboard.cpp | 407 +++++++++++++++++++++++- src/virtual-keyboard.hpp | 71 ++++- src/wayland-window.cpp | 37 ++- src/wayland-window.hpp | 5 + subprojects/.wraplock | 0 subprojects/wf-json | 1 + 23 files changed, 2729 insertions(+), 534 deletions(-) create mode 100644 .gitmodules create mode 100644 data/layouts/ansi.xml create mode 100644 data/layouts/iso.xml create mode 100644 data/meson.build create mode 100644 metadata/meson.build create mode 100644 metadata/osk.xml create mode 100644 proto/input-method-unstable-v2.xml delete mode 100644 src/layouts.tpp delete mode 100644 src/shared/os-compatibility.c delete mode 100644 src/shared/os-compatibility.h create mode 100644 src/util/wf-ipc.cpp create mode 100644 src/util/wf-ipc.hpp create mode 100644 subprojects/.wraplock create mode 160000 subprojects/wf-json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d3fadba --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "wf-json"] + path = subprojects/wf-json + url = git@github.com:WayfireWM/wf-json.git diff --git a/data/layouts/ansi.xml b/data/layouts/ansi.xml new file mode 100644 index 0000000..2f79878 --- /dev/null +++ b/data/layouts/ansi.xml @@ -0,0 +1,426 @@ + + + + + GTK_ORIENTATION_VERTICAL + 10 + true + true + + + GTK_ORIENTATION_HORIZONTAL + 10 + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 95 + 50 + + + + + + + GTK_ORIENTATION_HORIZONTAL + 10 + true + true + + + 80 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 80 + 50 + + + + + + + GTK_ORIENTATION_HORIZONTAL + 10 + true + true + + + 95 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 115 + 50 + + + + + + + GTK_ORIENTATION_HORIZONTAL + 10 + true + true + + + 125 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 65 + 50 + + + + + + + GTK_ORIENTATION_HORIZONTAL + true + true + 10 + + + 65 + 50 + + + + + 65 + 50 + + + + + 64 + 50 + + + + + 340 + 50 + + + + + 65 + 50 + + + + + 65 + 50 + + + + + 65 + 50 + + + + + 65 + 50 + + + + + 65 + 50 + + + + + 65 + 50 + + + + + + \ No newline at end of file diff --git a/data/layouts/iso.xml b/data/layouts/iso.xml new file mode 100644 index 0000000..de40aee --- /dev/null +++ b/data/layouts/iso.xml @@ -0,0 +1,440 @@ + + + + + GTK_ORIENTATION_VERTICAL + 10 + true + true + + + GTK_ORIENTATION_HORIZONTAL + true + true + 10 + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 110 + 50 + + + + + + + GTK_ORIENTATION_HORIZONTAL + true + true + 10 + + + 95 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + + + GTK_ORIENTATION_HORIZONTAL + true + true + 10 + + + 105 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 80 + 50 + + + + + + + GTK_ORIENTATION_HORIZONTAL + true + true + 10 + + + 65 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + 75 + 50 + + + + + 65 + 50 + + + + + + + GTK_ORIENTATION_HORIZONTAL + true + true + 10 + + + 65 + 50 + + + + + 65 + 50 + + + + + 64 + 50 + + + + + 440 + 50 + + + + + 65 + 50 + + + + + 65 + 50 + + + + + 65 + 50 + + + + + 65 + 50 + + + + + 65 + 50 + + + + + 65 + 50 + + + + + + \ No newline at end of file diff --git a/data/meson.build b/data/meson.build new file mode 100644 index 0000000..4b0915b --- /dev/null +++ b/data/meson.build @@ -0,0 +1,4 @@ +layout_dir = join_paths(get_option('prefix'), 'share', 'wf-osk', 'layouts') + +install_data(join_paths('layouts', 'ansi.xml'), install_dir: layout_dir) +install_data(join_paths('layouts', 'iso.xml'), install_dir: layout_dir) \ No newline at end of file diff --git a/meson.build b/meson.build index 4f3e6ca..447f353 100644 --- a/meson.build +++ b/meson.build @@ -13,14 +13,23 @@ project( ], ) +wayfire = dependency('wayfire') gtkmm = dependency('gtkmm-4.0') +xkb = dependency('xkbcommon') +xkbregistry = dependency('xkbregistry') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols') -gtkls = dependency('gtk4-layer-shell-0', version: '>=1.3.0', required: false) +gtkls = dependency('gtk4-layer-shell-0', version: '>=1.3.0') +json = subproject('wf-json').get_variable('wfjson') + +resource_dir = join_paths(get_option('prefix'), 'share', 'wf-osk') +metadata_dir = join_paths(resource_dir, 'metadata') add_project_link_arguments(['-rdynamic'], language: 'cpp') add_project_arguments(['-Wno-unused-parameter'], language: 'cpp') +subdir('metadata') +subdir('data') subdir('proto') subdir('src') diff --git a/metadata/meson.build b/metadata/meson.build new file mode 100644 index 0000000..9685aa0 --- /dev/null +++ b/metadata/meson.build @@ -0,0 +1 @@ +install_data('osk.xml', install_dir: metadata_dir) \ No newline at end of file diff --git a/metadata/osk.xml b/metadata/osk.xml new file mode 100644 index 0000000..ec1d17f --- /dev/null +++ b/metadata/osk.xml @@ -0,0 +1,43 @@ + + + + <_short>Onscreen Keyboard + Shell + + + + + + + \ No newline at end of file diff --git a/proto/input-method-unstable-v2.xml b/proto/input-method-unstable-v2.xml new file mode 100644 index 0000000..1853f69 --- /dev/null +++ b/proto/input-method-unstable-v2.xml @@ -0,0 +1,494 @@ + + + + + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2011 Intel Corporation + Copyright © 2012-2013 Collabora, Ltd. + Copyright © 2012, 2013 Intel Corporation + Copyright © 2015, 2016 Jan Arne Petersen + Copyright © 2017, 2018 Red Hat, Inc. + Copyright © 2018 Purism SPC + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows applications to act as input methods for compositors. + + An input method context is used to manage the state of the input method. + + Text strings are UTF-8 encoded, their indices and lengths are in bytes. + + This document adheres to the RFC 2119 when using words like "must", + "should", "may", etc. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + An input method object allows for clients to compose text. + + The objects connects the client to a text input in an application, and + lets the client to serve as an input method for a seat. + + The zwp_input_method_v2 object can occupy two distinct states: active and + inactive. In the active state, the object is associated to and + communicates with a text input. In the inactive state, there is no + associated text input, and the only communication is with the compositor. + Initially, the input method is in the inactive state. + + Requests issued in the inactive state must be accepted by the compositor. + Because of the serial mechanism, and the state reset on activate event, + they will not have any effect on the state of the next text input. + + There must be no more than one input method object per seat. + + + + + + + + + Notification that a text input focused on this seat requested the input + method to be activated. + + This event serves the purpose of providing the compositor with an + active input method. + + This event resets all state associated with previous enable, disable, + surrounding_text, text_change_cause, and content_type events, as well + as the state associated with set_preedit_string, commit_string, and + delete_surrounding_text requests. In addition, it marks the + zwp_input_method_v2 object as active, and makes any existing + zwp_input_popup_surface_v2 objects visible. + + The surrounding_text, and content_type events must follow before the + next done event if the text input supports the respective + functionality. + + State set with this event is double-buffered. It will get applied on + the next zwp_input_method_v2.done event, and stay valid until changed. + + + + + + Notification that no focused text input currently needs an active + input method on this seat. + + This event marks the zwp_input_method_v2 object as inactive. The + compositor must make all existing zwp_input_popup_surface_v2 objects + invisible until the next activate event. + + State set with this event is double-buffered. It will get applied on + the next zwp_input_method_v2.done event, and stay valid until changed. + + + + + + Updates the surrounding plain text around the cursor, excluding the + preedit text. + + If any preedit text is present, it is replaced with the cursor for the + purpose of this event. + + The argument text is a buffer containing the preedit string, and must + include the cursor position, and the complete selection. It should + contain additional characters before and after these. There is a + maximum length of wayland messages, so text can not be longer than 4000 + bytes. + + cursor is the byte offset of the cursor within the text buffer. + + anchor is the byte offset of the selection anchor within the text + buffer. If there is no selected text, anchor must be the same as + cursor. + + If this event does not arrive before the first done event, the input + method may assume that the text input does not support this + functionality and ignore following surrounding_text events. + + Values set with this event are double-buffered. They will get applied + and set to initial values on the next zwp_input_method_v2.done + event. + + The initial state for affected fields is empty, meaning that the text + input does not support sending surrounding text. If the empty values + get applied, subsequent attempts to change them may have no effect. + + + + + + + + + Tells the input method why the text surrounding the cursor changed. + + Whenever the client detects an external change in text, cursor, or + anchor position, it must issue this request to the compositor. This + request is intended to give the input method a chance to update the + preedit text in an appropriate way, e.g. by removing it when the user + starts typing with a keyboard. + + cause describes the source of the change. + + The value set with this event is double-buffered. It will get applied + and set to its initial value on the next zwp_input_method_v2.done + event. + + The initial value of cause is input_method. + + + + + + + Indicates the content type and hint for the current + zwp_input_method_v2 instance. + + Values set with this event are double-buffered. They will get applied + on the next zwp_input_method_v2.done event. + + The initial value for hint is none, and the initial value for purpose + is normal. + + + + + + + + Atomically applies state changes recently sent to the client. + + The done event establishes and updates the state of the client, and + must be issued after any changes to apply them. + + Text input state (content purpose, content hint, surrounding text, and + change cause) is conceptually double-buffered within an input method + context. + + Events modify the pending state, as opposed to the current state in use + by the input method. A done event atomically applies all pending state, + replacing the current state. After done, the new pending state is as + documented for each related request. + + Events must be applied in the order of arrival. + + Neither current nor pending state are modified unless noted otherwise. + + + + + + Send the commit string text for insertion to the application. + + Inserts a string at current cursor position (see commit event + sequence). The string to commit could be either just a single character + after a key press or the result of some composing. + + The argument text is a buffer containing the string to insert. There is + a maximum length of wayland messages, so text can not be longer than + 4000 bytes. + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.commit request. + + The initial value of text is an empty string. + + + + + + + Send the pre-edit string text to the application text input. + + Place a new composing text (pre-edit) at the current cursor position. + Any previously set composing text must be removed. Any previously + existing selected text must be removed. The cursor is moved to a new + position within the preedit string. + + The argument text is a buffer containing the preedit string. There is + a maximum length of wayland messages, so text can not be longer than + 4000 bytes. + + The arguments cursor_begin and cursor_end are counted in bytes relative + to the beginning of the submitted string buffer. Cursor should be + hidden by the text input when both are equal to -1. + + cursor_begin indicates the beginning of the cursor. cursor_end + indicates the end of the cursor. It may be equal or different than + cursor_begin. + + Values set with this event are double-buffered. They must be applied on + the next zwp_input_method_v2.commit event. + + The initial value of text is an empty string. The initial value of + cursor_begin, and cursor_end are both 0. + + + + + + + + + Remove the surrounding text. + + before_length and after_length are the number of bytes before and after + the current cursor index (excluding the preedit text) to delete. + + If any preedit text is present, it is replaced with the cursor for the + purpose of this event. In effect before_length is counted from the + beginning of preedit text, and after_length from its end (see commit + event sequence). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_input_method_v2.commit request. + + The initial values of both before_length and after_length are 0. + + + + + + + + Apply state changes from commit_string, set_preedit_string and + delete_surrounding_text requests. + + The state relating to these events is double-buffered, and each one + modifies the pending state. This request replaces the current state + with the pending state. + + The connected text input is expected to proceed by evaluating the + changes in the following order: + + 1. Replace existing preedit string with the cursor. + 2. Delete requested surrounding text. + 3. Insert commit string with the cursor at its end. + 4. Calculate surrounding text to send. + 5. Insert new preedit text in cursor position. + 6. Place cursor inside preedit text. + + The serial number reflects the last state of the zwp_input_method_v2 + object known to the client. The value of the serial argument must be + equal to the number of done events already issued by that object. When + the compositor receives a commit request with a serial different than + the number of past done events, it must proceed as normal, except it + should not change the current state of the zwp_input_method_v2 object. + + + + + + + Creates a new zwp_input_popup_surface_v2 object wrapping a given + surface. + + The surface gets assigned the "input_popup" role. If the surface + already has an assigned role, the compositor must issue a protocol + error. + + + + + + + + Allow an input method to receive hardware keyboard input and process + key events to generate text events (with pre-edit) over the wire. This + allows input methods which compose multiple key events for inputting + text like it is done for CJK languages. + + The compositor should send all keyboard events on the seat to the grab + holder via the returned wl_keyboard object. Nevertheless, the + compositor may decide not to forward any particular event. The + compositor must not further process any event after it has been + forwarded to the grab holder. + + Releasing the resulting wl_keyboard object releases the grab. + + + + + + + The input method ceased to be available. + + The compositor must issue this event as the only event on the object if + there was another input_method object associated with the same seat at + the time of its creation. + + The compositor must issue this request when the object is no longer + usable, e.g. due to seat removal. + + The input method context becomes inert and should be destroyed after + deactivation is handled. Any further requests and events except for the + destroy request must be ignored. + + + + + + Destroys the zwp_text_input_v2 object and any associated child + objects, i.e. zwp_input_popup_surface_v2 and + zwp_input_method_keyboard_grab_v2. + + + + + + + This interface marks a surface as a popup for interacting with an input + method. + + The compositor should place it near the active text input area. It must + be visible if and only if the input method is in the active state. + + The client must not destroy the underlying wl_surface while the + zwp_input_popup_surface_v2 object exists. + + + + + Notify about the position of the area of the text input expressed as a + rectangle in surface local coordinates. + + This is a hint to the input method telling it the relative position of + the text being entered. + + + + + + + + + + + + + + The zwp_input_method_keyboard_grab_v2 interface represents an exclusive + grab of the wl_keyboard interface associated with the seat. + + + + + This event provides a file descriptor to the client which can be + memory-mapped to provide a keyboard mapping description. + + + + + + + + + A key was pressed or released. + The time argument is a timestamp with millisecond granularity, with an + undefined base. + + + + + + + + + + Notifies clients that the modifier and/or group state has changed, and + it should update its local state. + + + + + + + + + + + + + + + Informs the client about the keyboard's repeat rate and delay. + + This event is sent as soon as the zwp_input_method_keyboard_grab_v2 + object has been created, and is guaranteed to be received by the + client before any key press event. + + Negative values for either rate or delay are illegal. A rate of zero + will disable any repeating (regardless of the value of delay). + + This event can be sent later on as well with a new value if necessary, + so clients should continue listening for the event past the creation + of zwp_input_method_keyboard_grab_v2. + + + + + + + + + The input method manager allows the client to become the input method on + a chosen seat. + + No more than one input method must be associated with any seat at any + given time. + + + + + Request a new input zwp_input_method_v2 object associated with a given + seat. + + + + + + + + Destroys the zwp_input_method_manager_v2 object. + + The zwp_input_method_v2 objects originating from it remain valid. + + + + \ No newline at end of file diff --git a/proto/meson.build b/proto/meson.build index eb07910..37465de 100644 --- a/proto/meson.build +++ b/proto/meson.build @@ -15,8 +15,9 @@ wayland_scanner_client = generator( ) client_protocols = [ - ['wayfire-shell-unstable-v2.xml'], - ['virtual-keyboard-unstable-v1.xml'] + ['wayfire-shell-unstable-v2.xml'], + ['virtual-keyboard-unstable-v1.xml'], + ['input-method-unstable-v2.xml'], ] wl_protos_src = [] @@ -28,10 +29,13 @@ foreach p : client_protocols wl_protos_src += wayland_scanner_code.process(xml) endforeach -lib_wl_protos = static_library('wl_protos', wl_protos_src + wl_protos_headers, - dependencies: [wayland_client]) # for the include directory +lib_wl_protos = static_library( + 'wl_protos', + wl_protos_src + wl_protos_headers, + dependencies: [wayland_client], +) # for the include directory wf_protos = declare_dependency( link_with: lib_wl_protos, sources: wl_protos_headers, -) +) \ No newline at end of file diff --git a/src/layouts.tpp b/src/layouts.tpp deleted file mode 100644 index b1b774d..0000000 --- a/src/layouts.tpp +++ /dev/null @@ -1,122 +0,0 @@ -std::vector> default_keys = { - { - {KEY_Q, "q", 1}, - {KEY_W, "w", 1}, - {KEY_E, "e", 1}, - {KEY_R, "r", 1}, - {KEY_T, "t", 1}, - {KEY_Y, "y", 1}, - {KEY_U, "u", 1}, - {KEY_I, "i", 1}, - {KEY_O, "o", 1}, - {KEY_P, "p", 1}, - {KEY_BACKSPACE, "⌫", 2} - }, - { - {KEY_TAB, "⇥", 0.5}, - {KEY_A, "a", 1}, - {KEY_S, "s", 1}, - {KEY_D, "d", 1}, - {KEY_F, "f", 1}, - {KEY_G, "g", 1}, - {KEY_H, "h", 1}, - {KEY_J, "j", 1}, - {KEY_K, "k", 1}, - {KEY_L, "l", 1}, - {KEY_ENTER, "↵", 2} - }, - { - {ABC_TOGGLE, "ABC", 1}, - {KEY_Z, "z", 1}, - {KEY_X, "x", 1}, - {KEY_C, "c", 1}, - {KEY_V, "v", 1}, - {KEY_B, "b", 1}, - {KEY_N, "n", 1}, - {KEY_M, "m", 1}, - {KEY_COMMA, ",", 1}, - {KEY_DOT, ".", 1} - }, - { - {NUM_TOGGLE, "123?", 1.5}, - {KEY_SPACE, "_", 9.5}, - {KEY_LEFT, "←", 0.5}, - {KEY_RIGHT, "→", 0.5}, - {KEY_UP, "↑", 0.5}, - {KEY_DOWN, "↓", 0.5} - } -}; - -auto shift_keys = default_keys; -for (auto& row : shift_keys) -{ - for (auto& key : row) - { - key.text = key.text.uppercase(); - if (key.code < USE_SHIFT) - { - key.code |= USE_SHIFT; - } - - if (key.text == "ABC") - { - key.text = "abc"; - } - } -} - -std::vector> numeric_keys = { - { - {KEY_1, "1", 1}, - {KEY_2, "2", 1}, - {KEY_3, "3", 1}, - {KEY_4, "4", 1}, - {KEY_5, "5", 1}, - {KEY_6, "6", 1}, - {KEY_7, "7", 1}, - {KEY_8, "8", 1}, - {KEY_9, "9", 1}, - {KEY_0, "0", 1}, - {KEY_MINUS, "-", 1}, - {KEY_EQUAL, "=", 1}, - {KEY_BACKSPACE, "⌫", 2} - }, - { - {KEY_1 | USE_SHIFT, "!", 1}, - {KEY_2 | USE_SHIFT, "@", 1}, - {KEY_3 | USE_SHIFT, "#", 1}, - {KEY_4 | USE_SHIFT, "$", 1}, - {KEY_5 | USE_SHIFT, "%", 1}, - {KEY_6 | USE_SHIFT, "^", 1}, - {KEY_7 | USE_SHIFT, "&", 1}, - {KEY_8 | USE_SHIFT, "*", 1}, - {KEY_9 | USE_SHIFT, "(", 1}, - {KEY_0 | USE_SHIFT, ")", 1}, - {KEY_SEMICOLON, ";", 1}, - {KEY_SEMICOLON | USE_SHIFT, ":", 1}, - {KEY_ENTER, "↵", 3} - }, - { - {KEY_LEFTBRACE, "[", 1}, - {KEY_RIGHTBRACE, "]", 1}, - {KEY_LEFTBRACE | USE_SHIFT, "{", 1}, - {KEY_RIGHTBRACE | USE_SHIFT, "}", 1}, - {KEY_COMMA | USE_SHIFT, "<", 1}, - {KEY_DOT | USE_SHIFT, ">", 1}, - {KEY_EQUAL | USE_SHIFT, "+", 1}, - {KEY_SLASH, "/", 1}, - {KEY_SLASH | USE_SHIFT, "?", 1}, - {KEY_APOSTROPHE, "\'", 1}, - {KEY_APOSTROPHE | USE_SHIFT, "\"", 1}, - {KEY_GRAVE, "`", 1}, - {KEY_GRAVE | USE_SHIFT, "~", 1}, - {KEY_COMMA, ",", 1}, - {KEY_DOT, ".", 1} - }, - { - {ABC_TOGGLE, "abc", 1}, - {KEY_SPACE, "_", 10}, - {KEY_BACKSLASH, "\\", 1}, - {KEY_BACKSLASH | USE_SHIFT, "|", 1} - } -}; diff --git a/src/main.cpp b/src/main.cpp index 30108bf..f607659 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,17 @@ +#include "gtk/gtk.h" +#include "gtkmm/cssprovider.h" #include "gtkmm/gesture.h" #include "gtkmm/gestureclick.h" +#include "gtkmm/stylecontext.h" #include "osk.hpp" +#include #include #include #include #include "util/clara.hpp" +#include +#include #define ABC_TOGGLE 0x12345678 #define NUM_TOGGLE 0x87654321 @@ -23,117 +29,306 @@ int default_width = 800; int default_height = 400; int headerbar_size = 60; -std::string anchor; +std::string anchor = "bottom"; -KeyButton::KeyButton(Key key, int width, int height) +std::string Keyboard::get_keycap_symbol(const xkb_keysym_t *sym) { - this->code = key.code; + switch (*sym) + { + case XKB_KEY_Left: + return "←"; + + case XKB_KEY_Right: + return "→"; + + case XKB_KEY_Up: + return "↑"; + + case XKB_KEY_Down: + return "↓"; + + case XKB_KEY_BackSpace: + return "⌫"; + + case XKB_KEY_Delete: + return "⌦"; + + case XKB_KEY_Return: + return "↵"; + + case XKB_KEY_Tab: + return "⇥"; + + case XKB_KEY_Shift_L: + case XKB_KEY_Shift_R: + return "⇧"; - this->button.set_size_request(width, height); - this->button.set_label(key.text); + case XKB_KEY_Control_L: + case XKB_KEY_Control_R: + return "⌃"; - click_gesture = Gtk::GestureClick::create(); + case XKB_KEY_Alt_L: + case XKB_KEY_Alt_R: + return "⌥"; - click_gesture->signal_pressed().connect( - sigc::mem_fun(*this, &KeyButton::on_pressed)); - click_gesture->signal_released().connect( - sigc::mem_fun(*this, &KeyButton::on_released)); + case XKB_KEY_Caps_Lock: + return "⇪"; - this->button.add_controller(click_gesture); + case XKB_KEY_Super_L: + case XKB_KEY_Super_R: + return "⌘"; + + case XKB_KEY_Escape: + return "ESC"; + + case XKB_KEY_space: + return "␣"; + + default: + return ""; + } } -void KeyButton::on_pressed(int button, double x, double y) +void Keyboard::bind_key(struct xkb_keymap *keymap, xkb_keycode_t kc, void *user_data) { - std::cout << "Pressed" << std::endl; - auto& keyboard = Keyboard::get(); - if (IS_COMMAND(this->code)) + if (kc < 8) { return; } - click_gesture->set_state(Gtk::EventSequenceState::CLAIMED); - - if (this->code & USE_SHIFT) + Keyboard *instance = static_cast(user_data); + const char *name = xkb_keymap_key_get_name(keymap, kc); + if (!name) { - keyboard.get_device().set_shift(true); + return; } - keyboard.get_device().send_key(this->code & ~(USE_SHIFT), - WL_KEYBOARD_KEY_STATE_PRESSED); + std::string target_id = "key_"; + target_id += name; + if (instance->builder->get_object(target_id) != nullptr) + { + auto button = instance->builder->get_widget(target_id); + + auto update_label = [=] () + { + struct xkb_state *temp_state = xkb_state_new(keymap); + if (!temp_state) + { + return; + } + + auto depressed = instance->vk->get_depressed_modifiers(); + auto layout_idx = instance->vk->get_current_layout(); + + xkb_state_update_mask( + temp_state, + depressed, + 0, + 0, + 0, + 0, + 0); + xkb_layout_index_t num_layouts = xkb_keymap_num_layouts(keymap); + if (layout_idx >= num_layouts) + { + std::cerr << "Invalid layout idx " << layout_idx << " : Max " << num_layouts << std::endl; + return; + } + + xkb_level_index_t level = xkb_state_key_get_level(temp_state, kc, layout_idx); + if (level == XKB_LEVEL_INVALID) + { + return; + } + + const char *layout_name = xkb_keymap_layout_get_name(keymap, layout_idx); + + const xkb_keysym_t *syms; + int num_syms = xkb_keymap_key_get_syms_by_level(keymap, kc, + layout_idx, level, &syms); + if (num_syms > 0) + { + auto symbol = instance->get_keycap_symbol(syms); + + if (!symbol.empty()) + { + if (symbol.compare("␣") == 0) + { + /* TODO Option*/ + const char *layout_name = xkb_keymap_layout_get_name(keymap, layout_idx); + button->set_label(symbol + " (" + layout_name + ")"); + } else + { + button->set_label(symbol); + } + + return; + } + } + + char buffer[7]; + + int bytes_written = xkb_keysym_to_utf8(*syms, buffer, sizeof(buffer)); + + if (bytes_written > 0) + { + std::string label = std::string(buffer, bytes_written); + button->set_label(label); + } + }; + /* Set a label and update every state change */ + update_label(); + instance->signals.push_back(instance->vk->signal_modifiers_changed().connect([=] () + { + update_label(); + })); + + if (!instance->bind_toggle_modifier(button, std::string(name))) + { + /* A non-modifier key */ + auto click_gesture = Gtk::GestureClick::create(); + + instance->signals.push_back(click_gesture->signal_pressed().connect( + [=] (int button, double x, double y) + { + auto& keyboard = Keyboard::get(); + auto code = kc - 8; + + if (IS_COMMAND(code)) + { + return; + } + + click_gesture->set_state(Gtk::EventSequenceState::CLAIMED); + + keyboard.get_device().send_key(code & ~(USE_SHIFT), + WL_KEYBOARD_KEY_STATE_PRESSED); + })); + instance->signals.push_back(click_gesture->signal_released().connect( + [=] (int button, double x, double y) + { + auto& keyboard = Keyboard::get(); + auto code = kc - 8; + + if (IS_COMMAND(code)) + { + return keyboard.handle_action(code); + } + + keyboard.get_device().send_key(code & ~(USE_SHIFT), + WL_KEYBOARD_KEY_STATE_RELEASED); + })); + + button->add_controller(click_gesture); + } + } } -void KeyButton::on_released(int button, double x, double y) +bool Keyboard::bind_toggle_modifier(Gtk::Button *button, std::string name) { - std::cout << "Released" << std::endl; - auto& keyboard = Keyboard::get(); - if (IS_COMMAND(this->code)) + /* Code path for modifier keys. For now we latch them Only. Awaiting bug reports from people who + * expect to send a CTRl alone etc */ + std::string modifier_name = ""; + if ((name == "RCTL") || (name == "LCTL")) + { + modifier_name = XKB_MOD_NAME_CTRL; + } else if ((name == "LFSH") || (name == "RFSH")) + { + modifier_name = XKB_MOD_NAME_SHIFT; + } else if ((name == "LALT") || (name == "RALT")) + { + modifier_name = XKB_MOD_NAME_ALT; + } else if ((name == "LWIN") || (name == "RWIN")) + { + modifier_name = XKB_MOD_NAME_LOGO; + } else if (name == "CAPS") { - return keyboard.handle_action(this->code); + modifier_name = XKB_MOD_NAME_CAPS; + } else if (name == "NMLK") + { + modifier_name = XKB_MOD_NAME_NUM; + } else + { + std::cout << name << " Not modifier " << std::endl; + return false; } - if (this->code & USE_SHIFT) + if (modifier_name.empty()) { - keyboard.get_device().set_shift(false); + return false; } - keyboard.get_device().send_key(this->code & ~(USE_SHIFT), - WL_KEYBOARD_KEY_STATE_RELEASED); -} + auto click_gesture = Gtk::GestureClick::create(); -KeyboardRow::KeyboardRow(std::vector keys, - int width, int height) -{ - double sum = 0; - for (auto& key : keys) + instance->signals.push_back(click_gesture->signal_pressed().connect( + [=] (int button, double x, double y) { - sum += key.width; - } - - box.set_spacing(spacing); - int total_spacing = std::min((int)keys.size() - 1, 0) * spacing; - int total_buttons = width - total_spacing; + click_gesture->set_state(Gtk::EventSequenceState::CLAIMED); + })); + instance->signals.push_back(click_gesture->signal_released().connect( + [=] (int button, double x, double y) + { + vk->toggle_modifier(modifier_name); + })); - for (auto& key : keys) + instance->signals.push_back(vk->signal_modifiers_changed().connect([=] () { - this->keys.emplace_back(std::make_unique(key, int(key.width / sum * total_buttons), - height)); - this->box.append(this->keys.back()->button); - } + if (vk->is_modifier_pressed(modifier_name)) + { + button->add_css_class("locked"); + } else + { + button->remove_css_class("locked"); + } + })); + button->add_controller(click_gesture); + return true; } -KeyboardLayout::KeyboardLayout(std::vector> keys, - int32_t width, int32_t height) +void Keyboard::init_layouts() { - box.set_orientation(Gtk::Orientation::VERTICAL); - box.set_spacing(spacing); - int total_spacing = std::min((int)keys.size() - 1, 0) * spacing; - - int row_height = (height - total_spacing) / keys.size(); - for (auto& row : keys) + std::cout << "init Layout" << std::endl; + /* TODO FIX PATH PREFIX */ + std::string xml_filepath = "/usr/share/wf-osk/layouts/" + layout_name + ".xml"; + if (m_current_layout) { - this->rows.emplace_back( - std::make_unique(row, width, row_height)); - this->box.append(this->rows.back()->box); + box.remove(*m_current_layout); + m_current_layout = nullptr; + m_instantiated_keys.clear(); + for (auto signal : signals) + { + signal.disconnect(); + } + + signals.clear(); } -} -void Keyboard::init_layouts() -{ - /* Key layouts are defined in layouts.tpp, - * it defines default_keys, shift_keys, numeric_keys */ -#include "layouts.tpp" + if (!vk->valid()) + { + std::cout << "Invalid virtual keyboard state. No layout" << std::endl; + return; + } - this->default_layout = std::make_unique(default_keys, default_width, default_height); + try { + builder = Gtk::Builder::create_from_file(xml_filepath); + m_current_layout = builder->get_widget("keyboard_main"); + if (!m_current_layout) + { + std::cerr << "CRITICAL: 'keyboard_grid' target is missing or not a Gtk::Fixed layout!" << + std::endl; + return; + } - this->shift_layout = std::make_unique(shift_keys, default_width, default_height); + box.append(*m_current_layout); - this->numeric_layout = std::make_unique(numeric_keys, default_width, default_height); -} + xkb_keymap_key_for_each(vk->get_keymap(), &Keyboard::bind_key, this); + m_active_layout_path = xml_filepath; + } catch (const Glib::Error& ex) + { + std::cerr << "XML Parse Exception Encountered: " << ex.what() << std::endl; + } -void Keyboard::set_layout(KeyboardLayout *new_layout) -{ - this->current_layout = new_layout; - window->set_widget(new_layout->box); + window->set_widget(box); } Keyboard::Keyboard() @@ -141,10 +336,15 @@ Keyboard::Keyboard() window = std::make_unique(default_width, default_height, anchor, headerbar_size); vk = std::make_unique(); - init_layouts(); - set_layout(default_layout.get()); + vk->signal_layer_changed().connect([=] () + { + init_layouts(); + }); } +Keyboard::~Keyboard() +{} + std::unique_ptr Keyboard::instance; void Keyboard::create() { @@ -178,21 +378,17 @@ Gtk::Window& Keyboard::get_window() void Keyboard::handle_action(uint32_t action) { - if (action == ABC_TOGGLE) - { - if (current_layout == default_layout.get()) - { - set_layout(shift_layout.get()); - } else - { - set_layout(default_layout.get()); - } - } + /* TODO Actions*/ +} - if (action == NUM_TOGGLE) - { - set_layout(numeric_layout.get()); - } +void Keyboard::activate() +{ + window->show(); +} + +void Keyboard::deactivate() +{ + window->hide(); } } } @@ -229,6 +425,18 @@ int main(int argc, char **argv) app->signal_activate().connect([app] () { app->add_window(wf::osk::Keyboard::get().get_window()); + app->hold(); + wf::osk::Keyboard::get().get_window().hide(); + + auto css_provider = Gtk::CssProvider::create(); + css_provider->load_from_data( + "button.depressed, button.depressed:hover { \ + background-color: alpha(currentColor, 0.12); \ + box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.25); \ + background-image: none; \ + }"); + Gtk::StyleContext::add_provider_for_display(wf::osk::Keyboard::get().get_window().get_display(), + css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); }); return app->run(); } diff --git a/src/meson.build b/src/meson.build index ebdbd3f..23716c3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,3 +1,11 @@ -executable('wf-osk', ['main.cpp', 'wayland-window.cpp', 'virtual-keyboard.cpp', 'shared/os-compatibility.c'], - dependencies: [gtkmm, wf_protos, gtkls], - install: true) +executable( + 'wf-osk', + [ + 'main.cpp', + 'wayland-window.cpp', + 'virtual-keyboard.cpp', + 'util/wf-ipc.cpp', + ], + dependencies: [gtkmm, wf_protos, gtkls, xkb, xkbregistry, json, wayfire], + install: true, +) \ No newline at end of file diff --git a/src/osk.hpp b/src/osk.hpp index cb113fe..3c3de1b 100644 --- a/src/osk.hpp +++ b/src/osk.hpp @@ -1,15 +1,14 @@ #pragma once #include -#include #include #include - -#include "gtkmm/enums.h" -#include "gtkmm/gestureclick.h" +#include "sigc++/connection.h" #include "virtual-keyboard.hpp" #include "wayland-window.hpp" +#include + namespace wf { @@ -17,66 +16,41 @@ namespace osk { extern int spacing; -struct Key -{ - uint32_t code; - Glib::ustring text; - double width; -}; - -struct KeyButton -{ - Gtk::Button button; - - /* keycode as in linux/input-event-codes.h */ - uint32_t code; - KeyButton(Key key, int width, int height); - - private: - void on_pressed(int, double, double); - void on_released(int, double, double); - std::shared_ptr click_gesture; -}; - -struct KeyboardRow -{ - Gtk::Box box; - std::vector> keys; - - KeyboardRow(std::vector keys, - int width, int height); -}; - -struct KeyboardLayout -{ - Gtk::Box box; - std::vector> rows; - - KeyboardLayout(std::vector> keys, - int32_t width, int32_t height); -}; - class Keyboard { - std::unique_ptr default_layout, shift_layout, - numeric_layout; - KeyboardLayout *current_layout = nullptr; void init_layouts(); - void set_layout(KeyboardLayout *new_layout); + + std::vector signals; std::unique_ptr window; - std::unique_ptr vk; + std::unique_ptr vk = nullptr; Keyboard(); + Gtk::Box *m_current_layout = nullptr; + Gtk::Box box; + std::map m_instantiated_keys; + std::string m_active_layout_path; static std::unique_ptr instance; + void refresh_labels_from_xkb(); + + std::string layout_name = "iso"; + + std::string get_keycap_symbol(const xkb_keysym_t *sym); public: + static void bind_key(struct xkb_keymap *keymap, xkb_keycode_t kc, void *user_data); + bool bind_toggle_modifier(Gtk::Button *button, std::string name); static void create(); static Keyboard& get(); + Glib::RefPtr builder; void handle_action(uint32_t action); VirtualKeyboardDevice& get_device(); Gtk::Window& get_window(); + ~Keyboard(); + + void activate(); + void deactivate(); }; } } diff --git a/src/shared/os-compatibility.c b/src/shared/os-compatibility.c deleted file mode 100644 index 867c024..0000000 --- a/src/shared/os-compatibility.c +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright © 2012 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#define _POSIX_C_SOURCE 200809L - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "os-compatibility.h" - -int os_fd_set_cloexec(int fd) { - long flags; - - if (fd == -1) - return -1; - - flags = fcntl(fd, F_GETFD); - if (flags == -1) - return -1; - - if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) - return -1; - - return 0; -} - -static int set_cloexec_or_close(int fd) { - if (os_fd_set_cloexec(fd) != 0) { - close(fd); - return -1; - } - return fd; -} - -int os_socketpair_cloexec(int domain, int type, int protocol, int *sv) { - int ret; - -#ifdef SOCK_CLOEXEC - ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv); - if (ret == 0 || errno != EINVAL) - return ret; -#endif - - ret = socketpair(domain, type, protocol, sv); - if (ret < 0) - return ret; - - sv[0] = set_cloexec_or_close(sv[0]); - sv[1] = set_cloexec_or_close(sv[1]); - - if (sv[0] != -1 && sv[1] != -1) - return 0; - - close(sv[0]); - close(sv[1]); - return -1; -} - -int os_epoll_create_cloexec(void) { - int fd; - -#ifdef EPOLL_CLOEXEC - fd = epoll_create1(EPOLL_CLOEXEC); - if (fd >= 0) - return fd; - if (errno != EINVAL) - return -1; -#endif - - fd = epoll_create(1); - return set_cloexec_or_close(fd); -} - -static int create_tmpfile_cloexec(char *tmpname) { - int fd; - -#ifdef HAVE_MKOSTEMP - fd = mkostemp(tmpname, O_CLOEXEC); - if (fd >= 0) - unlink(tmpname); -#else - fd = mkstemp(tmpname); - if (fd >= 0) { - fd = set_cloexec_or_close(fd); - unlink(tmpname); - } -#endif - - return fd; -} - -/* - * Create a new, unique, anonymous file of the given size, and - * return the file descriptor for it. The file descriptor is set - * CLOEXEC. The file is immediately suitable for mmap()'ing - * the given size at offset zero. - * - * The file should not have a permanent backing store like a disk, - * but may have if XDG_RUNTIME_DIR is not properly implemented in OS. - * - * The file name is deleted from the file system. - * - * The file is suitable for buffer sharing between processes by - * transmitting the file descriptor over Unix sockets using the - * SCM_RIGHTS methods. - * - * If the C library implements posix_fallocate(), it is used to - * guarantee that disk space is available for the file at the - * given size. If disk space is insufficient, errno is set to ENOSPC. - * If posix_fallocate() is not supported, program may receive - * SIGBUS on accessing mmap()'ed file contents instead. - */ -int os_create_anonymous_file(off_t size) { - static const char template[] = "/weston-shared-XXXXXX"; - const char *path; - char *name; - int fd; - int ret; - - path = getenv("XDG_RUNTIME_DIR"); - if (!path) { - errno = ENOENT; - return -1; - } - - name = malloc(strlen(path) + sizeof(template)); - if (!name) - return -1; - - strcpy(name, path); - strcat(name, template); - - fd = create_tmpfile_cloexec(name); - - free(name); - - if (fd < 0) - return -1; - -#ifdef HAVE_POSIX_FALLOCATE - do { - ret = posix_fallocate(fd, 0, size); - } while (ret == EINTR); - if (ret != 0) { - close(fd); - errno = ret; - return -1; - } -#else - do { - ret = ftruncate(fd, size); - } while (ret < 0 && errno == EINTR); - if (ret < 0) { - close(fd); - return -1; - } -#endif - - return fd; -} - -#ifndef MISSING_STRCHRNUL -char *strchrnul(const char *s, int c) { - while (*s && *s != c) - s++; - return (char *)s; -} -#endif diff --git a/src/shared/os-compatibility.h b/src/shared/os-compatibility.h deleted file mode 100644 index f8bcf93..0000000 --- a/src/shared/os-compatibility.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright © 2012 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef OS_COMPATIBILITY_H -#define OS_COMPATIBILITY_H - -#include -#ifdef __cplusplus -extern "C" -{ - int os_fd_set_cloexec(int fd); - int os_socketpair_cloexec(int domain, int type, int protocol, int *sv); - int os_epoll_create_cloexec(void); - int os_create_anonymous_file(off_t size); - #ifdef MISSING_STRCHRNUL - char *strchrnul(const char *s, int c); - #endif -} -#endif - -#endif /* OS_COMPATIBILITY_H */ diff --git a/src/util/wf-ipc.cpp b/src/util/wf-ipc.cpp new file mode 100644 index 0000000..e392f72 --- /dev/null +++ b/src/util/wf-ipc.cpp @@ -0,0 +1,395 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "wf-ipc.hpp" + +WayfireIPC::WayfireIPC() +{ + if (connect()) + { + sig_connection = Glib::signal_io().connect( + sigc::mem_fun(*this, &WayfireIPC::receive), + connection->get_socket()->get_fd(), + Glib::IOCondition::IO_IN); + + connected = true; + } else + { + std::cerr << "Failed to connect to WAYFIRE_SOCKET. Is wayfire ipc plugin enabled?" << std::endl; + } +} + +WayfireIPC::~WayfireIPC() +{ + if (connected) + { + disconnect(); + } +} + +bool WayfireIPC::connect() +{ + const char *socket_path = getenv("WAYFIRE_SOCKET"); + if (!socket_path || std::string(socket_path).empty()) + { + std::cerr << "Wayfire socket not found" << std::endl; + return false; + } + + try { + auto client = Gio::SocketClient::create(); + auto address = Gio::UnixSocketAddress::create(socket_path); + connection = client->connect(address); + connection->get_socket()->set_blocking(false); + output = connection->get_output_stream(); + input = connection->get_input_stream(); + cancel = Gio::Cancellable::create(); + + return true; + } catch (const Glib::Error& ex) + { + std::cerr << "Error connecting to WAYFIRE_SOCKET at path \"" << socket_path << "\": " << ex.what(); + return false; + } + + return false; +} + +void WayfireIPC::disconnect() +{ + cancel->cancel(); + sig_connection.disconnect(); + connection->close(); +} + +void WayfireIPC::send(const std::string& message) +{ + send_message(message); + response_handlers.push(0); +} + +void WayfireIPC::send(const std::string& message, int response_handler) +{ + if (!connected) + { + return; + } + + send_message(message); + response_handlers.push(response_handler); +} + +void WayfireIPC::send_message(const std::string& message) +{ + if (output->has_pending() || writing) + { + write_queue.push(message); + write_next(); + return; + } + + // Shortcut: stream is not busy, no queue needed + write_stream(message); +} + +void WayfireIPC::write_next() +{ + if (writing || cancel->is_cancelled()) + { + return; + } + + writing = true; + sig_connection = Glib::signal_io().connect( + sigc::mem_fun(*this, &WayfireIPC::send_queue), + connection->get_socket()->get_fd(), + Glib::IOCondition::IO_OUT); +} + +void WayfireIPC::write_stream(const std::string& message) +{ + try { + writing = true; + uint32_t length = message.size(); + // Pointer to data must be valid until completely wrote and + // slot is called, as documented for write_all_async. + // So we pin it with a shared pointer, destroyed *after* slot is called. + auto all_data = std::make_shared((char*)&length, 4); + *all_data += message; + output->write_all_async(all_data->data(), all_data->size(), + [this, all_data] (Glib::RefPtr& result) + { + try { + gsize written; + auto success = output->write_all_finish(result, written); + if (!success) + { + LOGE("IPC error: write failed. Bytes written: ", written); + } + + this->writing = false; + if (!cancel->is_cancelled()) + { + write_next(); + } + } catch (const Glib::Error& e) + { + this->writing = false; + if (e.code() == G_IO_ERROR_CANCELLED) + { + // Intended behavior + return; + } else + { + LOGE("IPC error: write failed: ", e.what()); + } + } + }, cancel); + } catch (const Gio::Error& e) + { + LOGE("IPC error: ", e.what()); + } +} + +bool WayfireIPC::send_queue(Glib::IOCondition cond) +{ + if (write_queue.empty()) + { + writing = false; + return false; + } + + auto message = write_queue.front(); + write_queue.pop(); + + write_stream(message); + return false; +} + +bool WayfireIPC::receive(Glib::IOCondition cond) +{ + try { + ssize_t received = 0; + uint32_t length; + + // TODO: Input buffer can(?) contain incomplete message + while (connection->get_socket()->get_available_bytes() > 0) + { + received = input->read(&length, sizeof(length)); + if (received == -1) + { + LOGE("IPC error: Receive message length failed"); + return false; + } + + if (received == 0) + { + LOGE("IPC error: Disconnected"); + return false; + } + + if (received != sizeof(length)) + { + LOGE("IPC error: failed to read message. Expected (bytes): ", + sizeof(length), + ", was read (bytes)", + received); + return false; + } + + std::string buf(length, 0); + received = input->read(&buf[0], length); + if (received == -1) + { + LOGE("IPC error: receive message body failed"); + return false; + } + + if (received == 0) + { + LOGE("IPC error: Disconnected"); + return false; + } + + if (received != length) + { + LOGE("IPC error: failed to read message. Expected (bytes): ", + length, + ", was read (bytes)", + received); + return false; + } + + wf::json_t message; + auto err = wf::json_t::parse_string(buf, message); + if (err.has_value()) + { + LOGE("IPC error: JSON parse: ", err.value(), " message: ", buf, " length: ", buf.length()); + return false; + } + + if (message.has_member("event")) + { + for (auto subscriber : subscribers) + { + subscriber->on_event(message); + } + + if (subscriptions.find(message["event"]) != subscriptions.end()) + { + for (auto sub : subscriptions[message["event"]]) + { + sub->on_event(message); + } + } + } else + { + auto handler = response_handlers.front(); + response_handlers.pop(); + auto client = clients.find(handler); + if (client != clients.end()) + { + client->second->handle_response(message); + } + } + } + } catch (const Gio::Error& e) + { + LOGE("IPC error: ", e.what()); + return false; + } + + return true; +} + +void WayfireIPC::subscribe_all(IIPCSubscriber *subscriber) +{ + subscribers.insert(subscriber); + + wf::json_t new_subs; + new_subs["method"] = "window-rules/events/watch"; + send(new_subs.serialize()); +} + +void WayfireIPC::subscribe(IIPCSubscriber *subscriber, const std::vector& events) +{ + wf::json_t new_subs; + new_subs["method"] = "window-rules/events/watch"; + new_subs["events"] = wf::json_t::array(); + + for (auto event : events) + { + if (subscriptions.find(event) == subscriptions.end()) + { + new_subs["events"].append(event); + subscriptions[event] = std::set(); + } + + subscriptions[event].insert(subscriber); + } + + if (new_subs["events"].size() > 0) + { + send(new_subs.serialize()); + } +} + +void WayfireIPC::unsubscribe(IIPCSubscriber *subscriber) +{ + subscribers.erase(subscriber); + + for (auto& [_, subs] : subscriptions) + { + subs.erase(subscriber); + } +} + +std::shared_ptr WayfireIPC::create_client() +{ + if (!connected) + { + std::cerr << "Failed to create ipc client" << std::endl; + return std::shared_ptr(new IPCClient(0, shared_from_this())); + } + + auto client = new IPCClient(next_client_id, shared_from_this()); + clients[next_client_id++] = client; + + // Zero is reserved for NO CLIENT id, so just in case :) + if (next_client_id == 0) + { + next_client_id++; + } + + return std::shared_ptr(client); +} + +void WayfireIPC::client_destroyed(int id) +{ + clients.erase(id); +} + +std::shared_ptr WayfireIPC::get_instance() +{ + static std::weak_ptr ipc; + + auto instance = ipc.lock(); + if (!instance) + { + instance = std::shared_ptr(new WayfireIPC()); + ipc = instance; + } + + return instance; +} + +// IPCClient +IPCClient::~IPCClient() +{ + ipc->client_destroyed(id); +} + +void IPCClient::send(const std::string& message) +{ + ipc->send(message); +} + +void IPCClient::send(const std::string& message, response_handler cb) +{ + response_handlers.push(cb); + ipc->send(message, id); +} + +void IPCClient::handle_response(wf::json_t response) +{ + auto handler = response_handlers.front(); + response_handlers.pop(); + handler(response); +} + +void IPCClient::subscribe(IIPCSubscriber *subscriber, const std::vector& events) +{ + ipc->subscribe(subscriber, events); +} + +void IPCClient::subscribe_all(IIPCSubscriber *subscriber) +{ + ipc->subscribe_all(subscriber); +} + +void IPCClient::unsubscribe(IIPCSubscriber *subscriber) +{ + ipc->unsubscribe(subscriber); +} diff --git a/src/util/wf-ipc.hpp b/src/util/wf-ipc.hpp new file mode 100644 index 0000000..d9496a1 --- /dev/null +++ b/src/util/wf-ipc.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class IIPCSubscriber +{ + public: + virtual void on_event(wf::json_t) = 0; +}; + +using response_handler = std::function; + +class WayfireIPC; +class IPCClient +{ + private: + int id; + std::shared_ptr ipc; + std::queue response_handlers; + + public: + IPCClient(int id, std::shared_ptr ipc) : id(id), ipc(ipc) + {} + ~IPCClient(); + void handle_response(wf::json_t response); + void send(const std::string& message); + void send(const std::string& message, response_handler cb); + void subscribe(IIPCSubscriber *subscriber, const std::vector& events); + void subscribe_all(IIPCSubscriber *subscriber); + void unsubscribe(IIPCSubscriber *subscriber); +}; + +class WayfireIPC : public std::enable_shared_from_this +{ + private: + std::queue response_handlers; + std::set subscribers; + std::unordered_map> subscriptions; + int next_client_id{1}; + std::unordered_map clients; + sigc::connection sig_connection; + Glib::RefPtr connection; + Glib::RefPtr input; + Glib::RefPtr output; + Glib::RefPtr cancel; + std::queue write_queue; + bool writing = false; + + bool connect(); + void disconnect(); + void send_message(const std::string& message); + bool send_queue(Glib::IOCondition cond); + bool receive(Glib::IOCondition cond); + void write_stream(const std::string& message); + void write_next(); + + public: + void send(const std::string& message); + void send(const std::string& message, int response_handler); + void subscribe(IIPCSubscriber *subscriber, const std::vector& events); + void subscribe_all(IIPCSubscriber *subscriber); + void unsubscribe(IIPCSubscriber *subscriber); + std::shared_ptr create_client(); + void client_destroyed(int id); + + static std::shared_ptr get_instance(); + bool connected = false; + WayfireIPC(); + ~WayfireIPC(); +}; diff --git a/src/virtual-keyboard.cpp b/src/virtual-keyboard.cpp index 545180a..ea1b49d 100644 --- a/src/virtual-keyboard.cpp +++ b/src/virtual-keyboard.cpp @@ -1,7 +1,9 @@ #include "virtual-keyboard.hpp" #include "gdk/wayland/gdkwayland.h" +#include "input-method-unstable-v2-client-protocol.h" +#include "osk.hpp" +#include "util/wf-ipc.hpp" #include "wayland-window.hpp" -#include "shared/os-compatibility.h" #include #include @@ -10,32 +12,331 @@ #include #include +#include +#include +#include +#include namespace wf { +/* Keyboard callbacks */ +static void kbd_keymap(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t format, + int32_t fd, + uint32_t size) +{ + auto instance = static_cast(data); + instance->handle_keymap(data, format, fd, size); +} + +static void kbd_enter(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + struct wl_surface *surface, + struct wl_array *keys) +{ + /* never called */ +} + +static void kbd_leave(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + struct wl_surface *surface) +{ + /* never called */ +} + +static void kbd_key(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + uint32_t time, + uint32_t key, + uint32_t state) +{ + /* never called */ +} + +static void kbd_modifiers(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group) +{ + /* never called */ +} + +static void kbd_repeat_info(void *data, + struct wl_keyboard *wl_keyboard, + int32_t rate, + int32_t delay) +{ + /* Don't care */ +} + +static const wl_keyboard_listener kbd_listener = { + kbd_keymap, + kbd_enter, + kbd_leave, + kbd_key, + kbd_modifiers, + kbd_repeat_info, +}; + +/* Input method callbacks */ + +static void im_activate(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) +{ + std::cout << "Activate " << std::endl; + auto instance = static_cast(data); + instance->handle_activate(); +} + +static void im_deactivate(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) +{ + std::cout << "Deactivate " << std::endl; + auto instance = static_cast(data); + instance->handle_deactivate(); +} + +static void im_surrounding_text(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2, + const char *text, + uint32_t cursor, + uint32_t anchor) +{} + +static void im_text_change_cause(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2, + uint32_t cause) +{} + +static void im_content_type(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2, + uint32_t hint, + uint32_t purpose) +{} + +static void im_done(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) +{} + +static void im_unavailable(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) +{ + std::cout << "IM Unavailable" << std::endl; +} + +static const zwp_input_method_v2_listener im_listener = { + im_activate, + im_deactivate, + im_surrounding_text, + im_text_change_cause, + im_content_type, + im_done, + im_unavailable, +}; + + + VirtualKeyboardDevice::VirtualKeyboardDevice() { auto& display = WaylandDisplay::get(); auto seat = Gdk::Display::get_default()->get_default_seat(); + auto wl_seat = gdk_wayland_seat_get_wl_seat(seat->gobj()); + + auto wl_keyboard = wl_seat_get_keyboard(wl_seat); + wl_keyboard_add_listener(wl_keyboard, &kbd_listener, this); vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard( - display.vk_manager, gdk_wayland_seat_get_wl_seat(seat->gobj())); + display.vk_manager, wl_seat); + m_xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + im = zwp_input_method_manager_v2_get_input_method(display.im_manager, wl_seat); + + zwp_input_method_v2_add_listener(im, &im_listener, this); + + ipc_server = WayfireIPC::get_instance(); + if (!ipc_server) + { + std::cerr << "Failed to connect to Wayfire IPC. exiting" << std::endl; + std::exit(-1); + } - this->send_keymap(); + ipc_client = ipc_server->create_client(); + + if (!ipc_client) + { + std::cerr << "Failed to connect to Wayfire IPC client. exiting" << std::endl; + std::exit(-1); + } + + ipc_client->subscribe(this, {"keyboard-modifier-state-changed"}); +} + +VirtualKeyboardDevice::~VirtualKeyboardDevice() +{ + if (m_xkb_keymap) + { + xkb_keymap_unref(m_xkb_keymap); + } + + if (m_xkb_context) + { + xkb_context_unref(m_xkb_context); + } +} + +void VirtualKeyboardDevice::on_event(wf::json_t data) +{ + std::cout << data.serialize() << std::endl; + if (data["event"].as_string() == "keyboard-modifier-state-changed") + { + if (available_layouts.size() == 0) + { + set_available(data["state"]["possible-layouts"]); + } + + auto state_layout = data["state"]["layout-index"].as_uint(); + if (state_layout != current_layout) + { + current_layout = state_layout; + set_current(state_layout); + } + } +} + +void VirtualKeyboardDevice::set_available(wf::json_t layouts) +{ + std::vector layouts_available; + std::map names; + + for (size_t i = 0; i < layouts.size(); i++) + { + auto elem = layouts[i]; + names[elem] = i; + layouts_available.push_back(Layout{ + .Name = (std::string)elem, + .ID = "", + }); + } + + auto context = rxkb_context_new(RXKB_CONTEXT_NO_FLAGS); + rxkb_context_parse_default_ruleset(context); + auto rlayout = rxkb_layout_first(context); + for (; rlayout != NULL; rlayout = rxkb_layout_next(rlayout)) + { + auto descr = rxkb_layout_get_description(rlayout); + auto name = names.find(descr); + if (name != names.end()) + { + layouts_available[name->second].ID = rxkb_layout_get_brief(rlayout); + } + } + + available_layouts = layouts_available; +} + +void VirtualKeyboardDevice::set_current(uint32_t index) +{ + current_layout = index; + + layer_changed.emit(); +} + +void VirtualKeyboardDevice::handle_keymap(void *data, uint32_t format, + int32_t fd, uint32_t size) +{ + std::cout << "HANDLE KEYMAP" << std::endl; + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) + { + close(fd); + return; + } + + bool req_send_keymap = false; + + char *map_str = static_cast(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0)); + if (map_str != MAP_FAILED) + { + auto next_keymap = std::string(map_str, size); + if (keymap != next_keymap) + { + req_send_keymap = true; + } + + keymap = next_keymap; + if (m_xkb_keymap) + { + xkb_keymap_unref(m_xkb_keymap); + } + + m_xkb_keymap = xkb_keymap_new_from_string(m_xkb_context, map_str, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(map_str, size); + } + + close(fd); + if (req_send_keymap) + { + send_keymap(); + } + + keymap_changed.emit(); +} + +int VirtualKeyboardDevice::create_shm_file(size_t size) +{ + int fd = memfd_create("vk_keymap", MFD_CLOEXEC); + if (fd < 0) + { + return -1; + } + + if (ftruncate(fd, size) < 0) + { + close(fd); + return -1; + } + + return fd; } void VirtualKeyboardDevice::send_keymap() { - /* The keymap string is defined in keymap.tpp, it is keymap_normal */ -#include "keymap.tpp" + if (!vk || keymap.empty()) + { + return; + } + + auto chars = keymap.c_str(); + size_t size = strlen(chars) + 1; + int fd = create_shm_file(size); + if (fd < 0) + { + return; + } + + void *data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) + { + close(fd); + return; + } - size_t keymap_size = strlen(keymap) + 1; - int keymap_fd = os_create_anonymous_file(keymap_size); - void *ptr = mmap(NULL, keymap_size, PROT_READ | PROT_WRITE, MAP_SHARED, - keymap_fd, 0); + std::memcpy(data, chars, size); + munmap(data, size); - std::strcpy((char*)ptr, keymap); - zwp_virtual_keyboard_v1_keymap(vk, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, - keymap_fd, keymap_size); + zwp_virtual_keyboard_v1_keymap(vk, + WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, + fd, size); + close(fd); + + keymap_changed.emit(); } uint32_t get_current_time() @@ -47,15 +348,87 @@ uint32_t get_current_time() void VirtualKeyboardDevice::send_key(uint32_t key, uint32_t state) const { + /* TODO When we can globally track state, don't modify it here */ + if ((state == 1) && (mods_depressed != 0)) + { + zwp_virtual_keyboard_v1_modifiers(vk, + mods_depressed, + 0, + 0, + current_layout); + } + zwp_virtual_keyboard_v1_key(vk, get_current_time(), key, state); + if ((state == 0) && (mods_depressed != 0)) + { + zwp_virtual_keyboard_v1_modifiers(vk, 0, 0, 0, current_layout); + } +} + +void VirtualKeyboardDevice::toggle_modifier(std::string mod_name) +{ + /* TODO When we can globally track state, modify it directly */ + xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(m_xkb_keymap, mod_name.c_str()); + if (mod_index == XKB_MOD_INVALID) + { + return; + } + + mods_depressed = mods_depressed ^ (1 << mod_index); + modifiers_changed.emit(); +} + +bool VirtualKeyboardDevice::is_modifier_pressed(const std::string mod_name) +{ + /* TODO When we can globally track state, read it in here */ + if (!m_xkb_keymap) + { + return false; + } + + xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(m_xkb_keymap, mod_name.c_str()); + + if (mod_index == XKB_MOD_INVALID) + { + return false; + } + + return (mods_depressed & (1U << mod_index)) != 0; +} + +xkb_keymap*VirtualKeyboardDevice::get_keymap() +{ + return m_xkb_keymap; +} + +void VirtualKeyboardDevice::handle_activate() +{ + wf::osk::Keyboard::get().activate(); } -void VirtualKeyboardDevice::set_shift(bool shift_on) +void VirtualKeyboardDevice::handle_deactivate() { - shift_pressed_counter += (shift_on ? 1 : -1); + wf::osk::Keyboard::get().deactivate(); +} - const int modifier_shift_code = 1; - zwp_virtual_keyboard_v1_modifiers(vk, - shift_pressed_counter ? modifier_shift_code : 0, 0, 0, 0); +void VirtualKeyboardDevice::handle_modifiers(uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) +{ + modifiers_changed.emit(); +} + +bool VirtualKeyboardDevice::valid() +{ + return m_xkb_keymap && m_xkb_context; +} + +uint32_t VirtualKeyboardDevice::get_depressed_modifiers() +{ + return mods_depressed; +} + +uint32_t VirtualKeyboardDevice::get_current_layout() +{ + return current_layout; } } diff --git a/src/virtual-keyboard.hpp b/src/virtual-keyboard.hpp index 94d570b..b4f144f 100644 --- a/src/virtual-keyboard.hpp +++ b/src/virtual-keyboard.hpp @@ -1,21 +1,86 @@ #pragma once +#include "input-method-unstable-v2-client-protocol.h" +#include "sigc++/signal.h" +#include "util/wf-ipc.hpp" #include +#include #include +#include +#include + namespace wf { -class VirtualKeyboardDevice +struct Layout { + std::string Name; + std::string ID; +}; + +class VirtualKeyboardDevice : public IIPCSubscriber +{ + sigc::signal keymap_changed, layer_changed, modifiers_changed; int shift_pressed_counter = 0; void send_keymap(); - zwp_virtual_keyboard_v1 *vk; + zwp_virtual_keyboard_v1 *vk = nullptr; + zwp_input_method_v2 *im = nullptr; + std::shared_ptr ipc_client; + std::shared_ptr ipc_server; + + uint32_t mods_depressed = 0; + + struct xkb_context *m_xkb_context = nullptr; + struct xkb_keymap *m_xkb_keymap = nullptr; + + std::string keymap; + uint32_t current_layout = 0; + std::vector available_layouts; + + void set_current(uint32_t index); + void set_available(wf::json_t layouts); + + int create_shm_file(size_t size); public: VirtualKeyboardDevice(); + ~VirtualKeyboardDevice(); + + void on_event(wf::json_t data) override; - void set_shift(bool shift_on); void send_key(uint32_t key, uint32_t state) const; + struct xkb_keymap *get_keymap(); + void set_keymap(std::string map); + void toggle_modifier(std::string mod_name); + bool is_modifier_pressed(std::string mod_name); + bool valid(); + + void handle_keymap(void *data, uint32_t format, + int32_t fd, uint32_t size); + + void handle_modifiers(uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group); + + void handle_activate(); + void handle_deactivate(); + + uint32_t get_depressed_modifiers(); + uint32_t get_current_layout(); + + sigc::signal signal_keymap_changed() + { + return keymap_changed; + } + + sigc::signal signal_modifiers_changed() + { + return modifiers_changed; + } + + sigc::signal signal_layer_changed() + { + return layer_changed; + } }; } diff --git a/src/wayland-window.cpp b/src/wayland-window.cpp index 0dac098..564b3d3 100644 --- a/src/wayland-window.cpp +++ b/src/wayland-window.cpp @@ -1,5 +1,7 @@ #include "wayland-window.hpp" #include "gtkmm/enums.h" +#include "virtual-keyboard-unstable-v1-client-protocol.h" +#include "input-method-unstable-v2-client-protocol.h" #include #include #include @@ -18,7 +20,6 @@ static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { auto display = static_cast(data); - if (strcmp(interface, zwf_shell_manager_v2_interface.name) == 0) { display->zwf_manager = @@ -32,6 +33,12 @@ static void registry_add_object(void *data, struct wl_registry *registry, wl_registry_bind(registry, name, &zwp_virtual_keyboard_manager_v1_interface, 1u); } + + if (strcmp(interface, zwp_input_method_manager_v2_interface.name) == 0) + { + display->im_manager = (zwp_input_method_manager_v2*)wl_registry_bind( + registry, name, &zwp_input_method_manager_v2_interface, 1u); + } } static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) @@ -68,6 +75,12 @@ WaylandDisplay::WaylandDisplay() "protocol, exiting" << std::endl; std::exit(-1); } + + if (!im_manager) + { + std::cerr << "Compositor doesn't support the input-method-v2 protocol, exiting" << std::endl; + std::exit(-1); + } } WaylandDisplay& WaylandDisplay::get() @@ -153,24 +166,24 @@ void WaylandWindow::init_headerbar(int headerbar_size) } close_button.set_image_from_icon_name("window-close-symbolic"); - close_button.signal_clicked().connect([=] () + signals.push_back(close_button.signal_clicked().connect([=] () { - this->get_application()->quit(); - }, true); + std::exit(0); + }, true)); top_button.set_image_from_icon_name("pan-up-symbolic"); - top_button.signal_clicked().connect([=] () + signals.push_back(top_button.signal_clicked().connect([=] () { gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_TOP, true); gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, false); - }, true); + }, true)); bottom_button.set_image_from_icon_name("pan-down-symbolic"); - bottom_button.signal_clicked().connect([=] () + signals.push_back(bottom_button.signal_clicked().connect([=] () { gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_TOP, false); gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, true); - }, true); + }, true)); // setup headerbar layout headerbar_box.set_size_request(-1, headerbar_size); @@ -192,6 +205,14 @@ WaylandWindow::WaylandWindow(int width, int height, std::string anchor, int head init(width, height, anchor); } +WaylandWindow::~WaylandWindow() +{ + for (auto signal : signals) + { + signal.disconnect(); + } +} + void WaylandWindow::set_widget(Gtk::Widget& w) { if (current_widget) diff --git a/src/wayland-window.hpp b/src/wayland-window.hpp index ebee47a..80a0aff 100644 --- a/src/wayland-window.hpp +++ b/src/wayland-window.hpp @@ -5,8 +5,10 @@ #include #include #include +#include #include #include +#include #define OSK_SPACING 8 static constexpr int32_t ANCHOR_PINNED_BOTTOM = -2; @@ -22,10 +24,12 @@ class WaylandDisplay zwf_shell_manager_v2 *zwf_manager = nullptr; zwp_virtual_keyboard_manager_v1 *vk_manager = nullptr; + zwp_input_method_manager_v2 *im_manager = nullptr; }; class WaylandWindow : public Gtk::Window { + std::vector signals; zwf_surface_v2 *wf_surface = nullptr; Gtk::Widget *current_widget = nullptr; @@ -43,6 +47,7 @@ class WaylandWindow : public Gtk::Window public: WaylandWindow(int width, int height, std::string anchor, int headerbar_size); + ~WaylandWindow(); void set_widget(Gtk::Widget& w); }; } diff --git a/subprojects/.wraplock b/subprojects/.wraplock new file mode 100644 index 0000000..e69de29 diff --git a/subprojects/wf-json b/subprojects/wf-json new file mode 160000 index 0000000..70039e1 --- /dev/null +++ b/subprojects/wf-json @@ -0,0 +1 @@ +Subproject commit 70039e13cdeaebd8ec498ed30bf5ab91c2e313ec