From 608519363d1be9f88fb2b83d2da24a118e10b00e Mon Sep 17 00:00:00 2001 From: Michael Carlberg Date: Wed, 30 Nov 2016 10:06:16 +0100 Subject: [PATCH] feat(xkeyboard): New module New module that uses the X keyboard extension to show keyboard layout and indicators. Ref #84, #200 --- .gitmodules | 2 +- cmake/build/options.cmake | 1 + cmake/build/summary.cmake | 1 + cmake/modules/FindXCB.cmake | 8 +- include/components/types.hpp | 19 ++-- include/config.hpp.cmake | 2 + include/modules/xkeyboard.hpp | 53 +++++++++++ include/x11/connection.hpp | 48 +--------- include/x11/extensions.hpp | 22 +++++ include/x11/extensions_fwd.hpp | 39 ++++++++ include/x11/registry.hpp | 28 ++++++ include/x11/types.hpp | 66 ++----------- include/x11/xkb.hpp | 82 +++++++++++++++++ lib/xpp | 2 +- src/CMakeLists.txt | 33 +++++-- src/components/bar.cpp | 4 +- src/components/controller.cpp | 3 + src/modules/xbacklight.cpp | 4 +- src/modules/xkeyboard.cpp | 164 +++++++++++++++++++++++++++++++++ src/x11/connection.cpp | 9 +- src/x11/registry.cpp | 9 ++ src/x11/xkb.cpp | 150 ++++++++++++++++++++++++++++++ 22 files changed, 617 insertions(+), 132 deletions(-) create mode 100644 include/modules/xkeyboard.hpp create mode 100644 include/x11/extensions.hpp create mode 100644 include/x11/extensions_fwd.hpp create mode 100644 include/x11/registry.hpp create mode 100644 include/x11/xkb.hpp create mode 100644 src/modules/xkeyboard.cpp create mode 100644 src/x11/registry.cpp create mode 100644 src/x11/xkb.cpp diff --git a/.gitmodules b/.gitmodules index 37df6270..f4d7f5ee 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,4 +5,4 @@ [submodule "lib/xpp"] path = lib/xpp url = https://github.com/jaagr/xpp - branch = 1.3.1 + branch = 1.3.3 diff --git a/cmake/build/options.cmake b/cmake/build/options.cmake index 32ade002..511d3d52 100644 --- a/cmake/build/options.cmake +++ b/cmake/build/options.cmake @@ -56,6 +56,7 @@ option(ENABLE_RENDER_EXT "Enable Render X extension" OFF) option(ENABLE_DAMAGE_EXT "Enable Damage X extension" OFF) option(ENABLE_SYNC_EXT "Enable Sync X extension" OFF) option(ENABLE_COMPOSITE_EXT "Enable Sync X extension" OFF) +option(ENABLE_XKB_EXT "Enable Sync X extension" ON) # }}} # Set cache vars {{{ diff --git a/cmake/build/summary.cmake b/cmake/build/summary.cmake index 965d247b..a6a8b8f7 100644 --- a/cmake/build/summary.cmake +++ b/cmake/build/summary.cmake @@ -60,4 +60,5 @@ colored_option(STATUS " Enable X Render ${ENABLE_RENDER_EXT}" ENABLE_RENDER colored_option(STATUS " Enable X Damage ${ENABLE_DAMAGE_EXT}" ENABLE_DAMAGE_EXT "32;1" "37;2") colored_option(STATUS " Enable X Sync ${ENABLE_SYNC_EXT}" ENABLE_SYNC_EXT "32;1" "37;2") colored_option(STATUS " Enable X Composite ${ENABLE_COMPOSITE_EXT}" ENABLE_COMPOSITE_EXT "32;1" "37;2") +colored_option(STATUS " Enable X Xkb ${ENABLE_XKB_EXT}" ENABLE_XKB_EXT "32;1" "37;2") message(STATUS "--------------------------") diff --git a/cmake/modules/FindXCB.cmake b/cmake/modules/FindXCB.cmake index efeea58a..55a1205d 100644 --- a/cmake/modules/FindXCB.cmake +++ b/cmake/modules/FindXCB.cmake @@ -54,7 +54,8 @@ set(knownComponents XCB XFIXES XTEST XV - XINERAMA) + XINERAMA + XKB) unset(unknownComponents) @@ -114,6 +115,8 @@ foreach(comp ${comps}) list(APPEND pkgConfigModules "xcb-xv") elseif("${comp}" STREQUAL "XINERAMA") list(APPEND pkgConfigModules "xcb-xinerama") + elseif("${comp}" STREQUAL "XKB") + list(APPEND pkgConfigModules "xcb-xkb") endif() endif() endforeach() @@ -193,6 +196,9 @@ macro(_XCB_HANDLE_COMPONENT _comp) elseif("${_comp}" STREQUAL "XINERAMA") set(_header "xcb/xinerama.h") set(_lib "xcb-xinerama") + elseif("${_comp}" STREQUAL "XKB") + set(_header "xcb/xkb.h") + set(_lib "xcb-xkb") endif() find_path(XCB_${_comp}_INCLUDE_DIR NAMES ${_header} HINTS ${PKG_XCB_INCLUDE_DIRS}) diff --git a/include/components/types.hpp b/include/components/types.hpp index e9e94008..f4820c27 100644 --- a/include/components/types.hpp +++ b/include/components/types.hpp @@ -135,16 +135,17 @@ struct action_block { }; struct event_timer { - xcb_timestamp_t event{0}; - uint32_t delay_ms{0U}; + xcb_timestamp_t event{0L}; + xcb_timestamp_t offset{1L}; - bool throttle(xcb_timestamp_t evt) { - if (evt > event + delay_ms) { - event = evt; - return false; - } else { - return true; - } + bool allow(xcb_timestamp_t time) { + bool pass = time >= event + offset; + event = time; + return pass; + }; + + bool deny(xcb_timestamp_t time) { + return !allow(time); }; }; diff --git a/include/config.hpp.cmake b/include/config.hpp.cmake index 22a61da7..c7d11ccc 100644 --- a/include/config.hpp.cmake +++ b/include/config.hpp.cmake @@ -22,6 +22,8 @@ #cmakedefine01 ENABLE_DAMAGE_EXT #cmakedefine01 ENABLE_SYNC_EXT #cmakedefine01 ENABLE_COMPOSITE_EXT +#cmakedefine01 ENABLE_XKB_EXT +#cmakedefine XPP_EXTENSION_LIST @XPP_EXTENSION_LIST@ #cmakedefine DEBUG_LOGGER #cmakedefine VERBOSE_TRACELOG diff --git a/include/modules/xkeyboard.hpp b/include/modules/xkeyboard.hpp new file mode 100644 index 00000000..4a7daf12 --- /dev/null +++ b/include/modules/xkeyboard.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "common.hpp" +#include "components/config.hpp" +#include "components/types.hpp" +#include "modules/meta/static_module.hpp" +#include "x11/events.hpp" +#include "x11/window.hpp" +#include "x11/xkb.hpp" + +POLYBAR_NS + +class connection; + +namespace modules { + /** + * Keyboard module using the X keyboard extension + */ + class xkeyboard_module : public static_module, + public xpp::event::sink { + public: + xkeyboard_module(const bar_settings& bar, const logger& logger, const config& config, string name); + + void setup(); + void teardown(); + void update(); + bool build(builder* builder, const string& tag) const; + + protected: + bool query_keyboard(); + bool blacklisted(const string& indicator_name); + + void handle(const evt::xkb_new_keyboard_notify& evt); + void handle(const evt::xkb_indicator_state_notify& evt); + + private: + static constexpr const char* TAG_LABEL_LAYOUT{""}; + static constexpr const char* TAG_LABEL_INDICATOR{""}; + static constexpr const char* FORMAT_DEFAULT{" "}; + + connection& m_connection; + event_timer m_xkbnotify{}; + unique_ptr m_keyboard; + + label_t m_layout; + label_t m_indicator; + map m_indicators; + + vector m_blacklist; + }; +} + +POLYBAR_NS_END diff --git a/include/x11/connection.hpp b/include/x11/connection.hpp index 3d87e3b5..f67e81ee 100644 --- a/include/x11/connection.hpp +++ b/include/x11/connection.hpp @@ -8,55 +8,13 @@ #include #include "common.hpp" +#include "x11/extensions.hpp" +#include "x11/registry.hpp" #include "x11/types.hpp" -#if ENABLE_DAMAGE_EXT -#include "x11/damage.hpp" -#endif -#if ENABLE_RENDER_EXT -#include "x11/render.hpp" -#endif -#if ENABLE_RANDR_EXT -#include "x11/randr.hpp" -#endif -#if ENABLE_SYNC_EXT -#include "x11/sync.hpp" -#endif -#if ENABLE_COMPOSITE_EXT -#include "x11/composite.hpp" -#endif - POLYBAR_NS -using xpp_connection = xpp::connection< -#if ENABLE_DAMAGE_EXT - xpp::damage::extension -#endif -#if ENABLE_RANDR_EXT -#if ENABLE_DAMAGE_EXT - , -#endif - xpp::randr::extension -#endif -#if ENABLE_RENDER_EXT -#if (ENABLE_RANDR_EXT || ENABLE_DAMAGE_EXT) - , -#endif - xpp::render::extension -#endif -#if ENABLE_SYNC_EXT -#if (ENABLE_RANDR_EXT || ENABLE_DAMAGE_EXT || ENABLE_RENDER_EXT) - , -#endif - xpp::sync::extension -#endif -#if ENABLE_COMPOSITE_EXT -#if (ENABLE_RANDR_EXT || ENABLE_DAMAGE_EXT || ENABLE_RENDER_EXT || ENABLE_SYNC_EXT) - , -#endif - xpp::composite::extension -#endif - >; +using xpp_connection = xpp::connection; class connection : public xpp_connection { public: diff --git a/include/x11/extensions.hpp b/include/x11/extensions.hpp new file mode 100644 index 00000000..ce7ce3d6 --- /dev/null +++ b/include/x11/extensions.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "config.hpp" + +#if ENABLE_DAMAGE_EXT +#include "x11/damage.hpp" +#endif +#if ENABLE_RENDER_EXT +#include "x11/render.hpp" +#endif +#if ENABLE_RANDR_EXT +#include "x11/randr.hpp" +#endif +#if ENABLE_SYNC_EXT +#include "x11/sync.hpp" +#endif +#if ENABLE_COMPOSITE_EXT +#include "x11/composite.hpp" +#endif +#if ENABLE_XKB_EXT +#include "x11/xkb.hpp" +#endif diff --git a/include/x11/extensions_fwd.hpp b/include/x11/extensions_fwd.hpp new file mode 100644 index 00000000..f1098168 --- /dev/null +++ b/include/x11/extensions_fwd.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "common.hpp" + +namespace xpp { +#if ENABLE_DAMAGE_EXT + namespace damage { + class extension; + } +#endif +#if ENABLE_RANDR_EXT + namespace randr { + class extension; + } +#endif +#if ENABLE_SYNC_EXT + namespace sync { + class extension; + } +#endif +#if ENABLE_RENDER_EXT + namespace render { + class extension; + } +#endif +#if ENABLE_COMPOSITE_EXT + namespace composite { + class extension; + } +#endif +} + +POLYBAR_NS + +#if ENABLE_XKB_EXT + class xkb_extension; +#endif + +POLYBAR_NS_END diff --git a/include/x11/registry.hpp b/include/x11/registry.hpp new file mode 100644 index 00000000..92a49107 --- /dev/null +++ b/include/x11/registry.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "common.hpp" +#include "x11/extensions_fwd.hpp" + +// fwd +namespace xpp { + namespace event { + template + class registry; + } +} + +POLYBAR_NS + +// fwd +class connection; + +using xpp_registry = xpp::event::registry; + +class registry : public xpp_registry { + public: + explicit registry(connection& conn); +}; + +POLYBAR_NS_END diff --git a/include/x11/types.hpp b/include/x11/types.hpp index 9c7a8489..d4575628 100644 --- a/include/x11/types.hpp +++ b/include/x11/types.hpp @@ -1,36 +1,5 @@ #pragma once -#include "config.hpp" - -// fwd -namespace xpp { -#if ENABLE_DAMAGE_EXT - namespace damage { - class extension; - } -#endif -#if ENABLE_RANDR_EXT - namespace randr { - class extension; - } -#endif -#if ENABLE_SYNC_EXT - namespace sync { - class extension; - } -#endif -#if ENABLE_RENDER_EXT - namespace render { - class extension; - } -#endif -#if ENABLE_COMPOSITE_EXT - namespace composite { - class extension; - } -#endif -} - #include #include "common.hpp" @@ -38,6 +7,7 @@ namespace xpp { POLYBAR_NS class connection; +class registry; using gcontext = xpp::gcontext; using pixmap = xpp::pixmap; @@ -47,34 +17,10 @@ using atom = xpp::atom; using font = xpp::font; using cursor = xpp::cursor; -using registry = xpp::event::registry; +namespace reply_checked = xpp::x::reply::checked; +namespace reply_unchecked = xpp::x::reply::unchecked; +namespace reply { + using get_atom_name = reply_checked::get_atom_name; +} POLYBAR_NS_END diff --git a/include/x11/xkb.hpp b/include/x11/xkb.hpp new file mode 100644 index 00000000..eacbb56b --- /dev/null +++ b/include/x11/xkb.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include "config.hpp" + +#if not ENABLE_XKB_EXT +#error "X xkb extension is disabled..." +#endif + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wkeyword-macro" +#endif +#define explicit mask_cxx_explicit_keyword +#include +#include +#undef explicit +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#include "common.hpp" + +POLYBAR_NS + +// fwd +class connection; + +namespace evt { + using xkb_new_keyboard_notify = xpp::xkb::event::new_keyboard_notify; + using xkb_map_notify = xpp::xkb::event::map_notify; + using xkb_state_notify = xpp::xkb::event::state_notify; + using xkb_controls_notify = xpp::xkb::event::controls_notify; + using xkb_indicator_state_notify = xpp::xkb::event::indicator_state_notify; + using xkb_indicator_map_notify = xpp::xkb::event::indicator_map_notify; + using xkb_names_notify = xpp::xkb::event::names_notify; + using xkb_compat_map_notify = xpp::xkb::event::compat_map_notify; + using xkb_bell_notify = xpp::xkb::event::bell_notify; + using xkb_action_message = xpp::xkb::event::action_message; + using xkb_access_x_notify = xpp::xkb::event::access_x_notify; + using xkb_extension_device_notify = xpp::xkb::event::extension_device_notify; +} + +class keyboard { + public: + struct indicator { + enum class type { NONE = 0U, CAPS_LOCK, NUM_LOCK }; + xcb_atom_t atom{}; + uint8_t mask{0}; + string name; + bool enabled{false}; + }; + + struct layout { + string group_name; + vector symbols; + }; + + explicit keyboard(vector&& layouts_, map&& indicators_) + : layouts(forward(layouts_)), indicators(forward(indicators_)) {} + + const indicator& get(const indicator::type& i) const; + void set(uint32_t indicator_state); + bool on(const indicator::type&) const; + + const string group_name(size_t index = 0) const; + const string layout_name(size_t index = 0) const; + const string indicator_name(const indicator::type&) const; + + private: + vector layouts; + map indicators; +}; + +namespace xkb_util { + static constexpr const char* LAYOUT_SYMBOL_BLACKLIST{";group;inet;pc;"}; + + string parse_layout_symbol(string&& name); + vector get_layouts(connection& conn, xcb_xkb_device_spec_t device); + map get_indicators(connection& conn, xcb_xkb_device_spec_t device); +} + +POLYBAR_NS_END diff --git a/lib/xpp b/lib/xpp index 3d6925ad..34d0cf14 160000 --- a/lib/xpp +++ b/lib/xpp @@ -1 +1 @@ -Subproject commit 3d6925ad2e1ec7cd2c98ecdc0667c3e6e2d0baaf +Subproject commit 34d0cf14e928270e3dd2ff3aab97f2208d409bff diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fc0fdd01..bd536e77 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,16 +2,6 @@ # Create executable # -execute_process(COMMAND git describe --tags --dirty=-git - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - OUTPUT_VARIABLE APP_VERSION - OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) - -configure_file( - ${PROJECT_SOURCE_DIR}/include/config.hpp.cmake - ${CMAKE_SOURCE_DIR}/include/config.hpp - ESCAPE_QUOTES @ONLY) - # Generate source tree {{{ file(GLOB_RECURSE SOURCES RELATIVE ${PROJECT_SOURCE_DIR}/src *.c[p]*) @@ -55,20 +45,33 @@ set(APP_INCLUDE_DIRS ${APP_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) set(XCB_PROTOS xproto) if(ENABLE_RANDR_EXT) set(XCB_PROTOS "${XCB_PROTOS}" randr) + set(XPP_EXTENSION_LIST ${XPP_EXTENSION_LIST} xpp::randr::extension) endif() if(ENABLE_RENDER_EXT) set(XCB_PROTOS "${XCB_PROTOS}" render) + set(XPP_EXTENSION_LIST ${XPP_EXTENSION_LIST} xpp::render::extension) endif() if(ENABLE_DAMAGE_EXT) set(XCB_PROTOS "${XCB_PROTOS}" damage) + set(XPP_EXTENSION_LIST ${XPP_EXTENSION_LIST} xpp::damage::extension) endif() if(ENABLE_SYNC_EXT) set(XCB_PROTOS "${XCB_PROTOS}" sync) + set(XPP_EXTENSION_LIST ${XPP_EXTENSION_LIST} xpp::sync::extension) endif() if(ENABLE_COMPOSITE_EXT) set(XCB_PROTOS "${XCB_PROTOS}" composite) + set(XPP_EXTENSION_LIST ${XPP_EXTENSION_LIST} xpp::composite::extension) endif() +if(ENABLE_XKB_EXT) + set(XCB_PROTOS "${XCB_PROTOS}" xkb) + set(XPP_EXTENSION_LIST ${XPP_EXTENSION_LIST} xpp::xkb::extension) +endif() + +string(REPLACE ";" ", " XPP_EXTENSION_LIST "${XPP_EXTENSION_LIST}") + add_subdirectory(${PROJECT_SOURCE_DIR}/lib/xpp ${PROJECT_BINARY_DIR}/lib/xpp) + set(APP_LIBRARIES ${APP_LIBRARIES} ${XPP_LIBRARIES}) set(APP_INCLUDE_DIRS ${APP_INCLUDE_DIRS} ${XPP_INCLUDE_DIRS}) @@ -132,3 +135,13 @@ set(APP_LIBRARIES ${APP_LIBRARIES} PARENT_SCOPE) set(APP_INCLUDE_DIRS ${APP_INCLUDE_DIRS} PARENT_SCOPE) # }}} + +execute_process(COMMAND git describe --tags --dirty=-git + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_VARIABLE APP_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) + +configure_file( + ${PROJECT_SOURCE_DIR}/include/config.hpp.cmake + ${CMAKE_SOURCE_DIR}/include/config.hpp + ESCAPE_QUOTES @ONLY) diff --git a/src/components/bar.cpp b/src/components/bar.cpp index ad041e4c..1a83135c 100644 --- a/src/components/bar.cpp +++ b/src/components/bar.cpp @@ -88,7 +88,7 @@ void bar::bootstrap(bool nodraw) { m_opts.strut.top = m_conf.get("global/wm", "margin-top", 0); m_opts.strut.bottom = m_conf.get("global/wm", "margin-bottom", 0); - m_buttonpress.delay_ms = xutils::event_timer_ms(m_conf, xcb_button_press_event_t{}); + m_buttonpress.offset = xutils::event_timer_ms(m_conf, xcb_button_press_event_t{}); } m_log.trace("bar: Load color values"); @@ -584,7 +584,7 @@ void bar::handle(const evt::button_press& evt) { std::lock_guard guard(m_mutex, std::adopt_lock); - if (m_buttonpress.throttle(evt->time)) { + if (m_buttonpress.deny(evt->time)) { return m_log.trace_x("bar: Ignoring button press (throttled)..."); } diff --git a/src/components/controller.cpp b/src/components/controller.cpp index b89d91c9..fa856893 100644 --- a/src/components/controller.cpp +++ b/src/components/controller.cpp @@ -23,6 +23,7 @@ #include "modules/temperature.hpp" #include "modules/text.hpp" #include "modules/xbacklight.hpp" +#include "modules/xkeyboard.hpp" #include "modules/xwindow.hpp" #include "modules/xworkspaces.hpp" #include "utils/process.hpp" @@ -400,6 +401,8 @@ void controller::bootstrap_modules() { module.reset(new temperature_module(bar, m_log, m_conf, module_name)); } else if (type == "internal/xbacklight") { module.reset(new xbacklight_module(bar, m_log, m_conf, module_name)); + } else if (type == "internal/xkeyboard") { + module.reset(new xkeyboard_module(bar, m_log, m_conf, module_name)); } else if (type == "internal/xwindow") { module.reset(new xwindow_module(bar, m_log, m_conf, module_name)); } else if (type == "internal/xworkspaces") { diff --git a/src/modules/xbacklight.cpp b/src/modules/xbacklight.cpp index 25efd222..62a2837a 100644 --- a/src/modules/xbacklight.cpp +++ b/src/modules/xbacklight.cpp @@ -61,7 +61,7 @@ namespace modules { } // Get the throttle time - m_randrnotify.delay_ms = xutils::event_timer_ms(m_conf, xcb_randr_notify_event_t{}); + m_randrnotify.offset = xutils::event_timer_ms(m_conf, xcb_randr_notify_event_t{}); // Connect with the event registry and make sure we get // notified when a RandR output property gets modified @@ -106,7 +106,7 @@ namespace modules { return; } else if (evt->u.op.atom != m_output->backlight.atom) { return; - } else if (m_randrnotify.throttle(evt->u.op.timestamp)) { + } else if (m_randrnotify.deny(evt->u.op.timestamp)) { return m_log.trace_x("%s: Ignoring randr notify (throttled)...", name()); } else { update(); diff --git a/src/modules/xkeyboard.cpp b/src/modules/xkeyboard.cpp new file mode 100644 index 00000000..ac2675c3 --- /dev/null +++ b/src/modules/xkeyboard.cpp @@ -0,0 +1,164 @@ +#include "modules/xkeyboard.hpp" +#include "drawtypes/iconset.hpp" +#include "drawtypes/label.hpp" +#include "x11/atoms.hpp" +#include "x11/connection.hpp" + +#include "modules/meta/base.inl" +#include "modules/meta/static_module.inl" + +POLYBAR_NS + +namespace modules { + template class module; + template class static_module; + + /** + * Construct module + */ + xkeyboard_module::xkeyboard_module(const bar_settings& bar, const logger& logger, const config& config, string name) + : static_module(bar, logger, config, name) + , m_connection(configure_connection().create()) {} + + /** + * Bootstrap the module + */ + void xkeyboard_module::setup() { + // Load config values + m_blacklist = m_conf.get_list(name(), "blacklist", {}); + + // Add formats and elements + m_formatter->add(DEFAULT_FORMAT, FORMAT_DEFAULT, {TAG_LABEL_LAYOUT, TAG_LABEL_INDICATOR}); + + if (m_formatter->has(TAG_LABEL_LAYOUT)) { + m_layout = load_optional_label(m_conf, name(), TAG_LABEL_LAYOUT, "%layout%"); + } + if (m_formatter->has(TAG_LABEL_INDICATOR)) { + m_indicator = load_optional_label(m_conf, name(), TAG_LABEL_INDICATOR, "%name%"); + } + + // Connect to the event registry + m_connection.attach_sink(this, 3); + + // Setup extension + m_connection.xkb().select_events_checked(XCB_XKB_ID_USE_CORE_KBD, + XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | XCB_XKB_EVENT_TYPE_INDICATOR_STATE_NOTIFY, 0, + XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | XCB_XKB_EVENT_TYPE_INDICATOR_STATE_NOTIFY, 0, 0, nullptr); + + // Create keyboard object + query_keyboard(); + + update(); + } + + /** + * Disconnect from the event registry + */ + void xkeyboard_module::teardown() { + m_connection.detach_sink(this, 3); + } + + /** + * Update labels with extension data + */ + void xkeyboard_module::update() { + if (m_layout) { + m_layout->reset_tokens(); + m_layout->replace_token("%name%", m_keyboard->group_name()); + m_layout->replace_token("%layout%", m_keyboard->layout_name()); + } + + if (m_indicator) { + m_indicators.clear(); + + const auto& caps = keyboard::indicator::type::CAPS_LOCK; + const auto& caps_str = m_keyboard->indicator_name(caps); + + if (!blacklisted(caps_str) && m_keyboard->on(caps)) { + m_indicators[caps] = m_indicator->clone(); + m_indicators[caps]->replace_token("%name%", m_keyboard->indicator_name(caps)); + } + + const auto& num = keyboard::indicator::type::NUM_LOCK; + const auto& num_str = m_keyboard->indicator_name(num); + + if (!blacklisted(num_str) && m_keyboard->on(num)) { + m_indicators[num] = m_indicator->clone(); + m_indicators[num]->replace_token("%name%", num_str); + } + } + + // Trigger redraw + broadcast(); + } + + /** + * Map format tags to content + */ + bool xkeyboard_module::build(builder* builder, const string& tag) const { + if (tag == TAG_LABEL_LAYOUT) { + builder->node(m_layout); + } else if (tag == TAG_LABEL_INDICATOR) { + for (auto&& indicator : m_indicators) { + if (indicator != *m_indicators.begin()) { + builder->space(m_formatter->get(DEFAULT_FORMAT)->spacing); + } + builder->node(indicator.second); + } + } else { + return false; + } + + return true; + } + + /** + * Create keyboard object by querying current extension data + */ + bool xkeyboard_module::query_keyboard() { + try { + auto layouts = xkb_util::get_layouts(m_connection, XCB_XKB_ID_USE_CORE_KBD); + auto indicators = xkb_util::get_indicators(m_connection, XCB_XKB_ID_USE_CORE_KBD); + m_keyboard = make_unique(move(layouts), move(indicators)); + return true; + } catch (const exception& err) { + throw module_error("Failed to query keyboard, err: " + string{err.what()}); + } + + return false; + } + + /** + * Check if the indicator has been blacklisted by the user + */ + bool xkeyboard_module::blacklisted(const string& indicator_name) { + for (auto&& i : m_blacklist) { + if (string_util::compare(i, indicator_name)) { + return true; + } + } + return false; + } + + /** + * Handler for XCB_XKB_NEW_KEYBOARD_NOTIFY events + */ + void xkeyboard_module::handle(const evt::xkb_new_keyboard_notify& evt) { + if (evt->changed & XCB_XKB_NKN_DETAIL_KEYCODES && m_xkbnotify.allow(evt->time)) { + query_keyboard(); + update(); + } + } + + /** + * Handler for XCB_XKB_INDICATOR_STATE_NOTIFY events + */ + void xkeyboard_module::handle(const evt::xkb_indicator_state_notify& evt) { + if (m_xkbnotify.allow(evt->time)) { + m_keyboard->set(m_connection.xkb().get_state(XCB_XKB_ID_USE_CORE_KBD)->lockedMods); + update(); + } + } +} + +POLYBAR_NS_END diff --git a/src/x11/connection.cpp b/src/x11/connection.cpp index 57d0d217..d9001f53 100644 --- a/src/x11/connection.cpp +++ b/src/x11/connection.cpp @@ -70,6 +70,7 @@ void connection::query_extensions() { } #endif #if ENABLE_SYNC_EXT + sync().initialize(XCB_SYNC_MAJOR_VERSION, XCB_SYNC_MINOR_VERSION); if (!extension()->present) { throw application_error("Missing X extension: Sync"); } @@ -77,7 +78,13 @@ void connection::query_extensions() { #if ENABLE_COMPOSITE_EXT composite().query_version(XCB_COMPOSITE_MAJOR_VERSION, XCB_COMPOSITE_MINOR_VERSION); if (!extension()->present) { - throw application_error("Missing X extension: RandR"); + throw application_error("Missing X extension: Composite"); + } +#endif +#if ENABLE_XKB_EXT + xkb().use_extension(XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION); + if (!extension()->present) { + throw application_error("Missing X extension: Xkb"); } #endif diff --git a/src/x11/registry.cpp b/src/x11/registry.cpp new file mode 100644 index 00000000..329b0993 --- /dev/null +++ b/src/x11/registry.cpp @@ -0,0 +1,9 @@ +#include "x11/connection.hpp" +#include "x11/extensions.hpp" +#include "x11/registry.hpp" + +POLYBAR_NS + +registry::registry(connection& conn) : xpp_registry(conn) {} + +POLYBAR_NS_END diff --git a/src/x11/xkb.cpp b/src/x11/xkb.cpp new file mode 100644 index 00000000..da2ab1ac --- /dev/null +++ b/src/x11/xkb.cpp @@ -0,0 +1,150 @@ +#include "x11/connection.hpp" + +#include "errors.hpp" +#include "utils/string.hpp" +#include "x11/xkb.hpp" + +POLYBAR_NS + +/** + * Get indicator name + */ +const keyboard::indicator& keyboard::get(const indicator::type& i) const { + return indicators.at(i); +} + +/** + * Update indicator states + */ +void keyboard::set(uint32_t state) { + for (auto& i : indicators) { + i.second.enabled = state & i.second.mask; + } +} + +/** + * Get state for the given class + */ +bool keyboard::on(const indicator::type& i) const { + return indicators.at(i).enabled; +} + +/** + * Get current group name + */ +const string keyboard::group_name(size_t index) const { + if (!layouts.empty() && index < layouts.size()) { + return layouts[index].group_name; + } + return ""; +} + +/** + * Get current layout name + */ +const string keyboard::layout_name(size_t index) const { + if (!layouts.empty() && index < layouts.size() && !layouts[index].symbols.empty()) { + return layouts[index].symbols[0]; + } + return ""; +} + +/** + * Get indicator name + */ +const string keyboard::indicator_name(const indicator::type& i) const { + return indicators.at(i).name; +} + +namespace xkb_util { + /** + * Get keyboard layouts + */ + vector get_layouts(connection& conn, xcb_xkb_device_spec_t device) { + auto mask = XCB_XKB_NAME_DETAIL_GROUP_NAMES | XCB_XKB_NAME_DETAIL_SYMBOLS; + auto reply = xcb_xkb_get_names_reply(conn, xcb_xkb_get_names(conn, device, mask), nullptr); + xcb_xkb_get_names_value_list_t values; + xcb_xkb_get_names_value_list_unpack(xcb_xkb_get_names_value_list(reply), reply->nTypes, reply->indicators, + reply->virtualMods, reply->groupNames, reply->nKeys, reply->nKeyAliases, reply->nRadioGroups, reply->which, + &values); + auto len = xcb_xkb_get_names_value_list_groups_length(reply, &values); + free(reply); + + vector replies; + for (int i = 0; i < len; i++) { + replies.emplace_back(xpp::x::get_atom_name(conn, values.groups[i])); + } + + vector results; + for (const auto& reply : replies) { + vector sym_names; + + for (auto&& sym : string_util::split(conn.get_atom_name(values.symbolsName).name(), '+')) { + if (!(sym = parse_layout_symbol(move(sym))).empty()) { + sym_names.emplace_back(move(sym)); + } + } + + results.emplace_back(keyboard::layout{static_cast(reply).name(), sym_names}); + } + + return results; + } + + /** + * Get keyboard indicators + */ + map get_indicators(connection& conn, xcb_xkb_device_spec_t device) { + auto mask = XCB_XKB_NAME_DETAIL_INDICATOR_NAMES; + auto reply = xcb_xkb_get_names_reply(conn, xcb_xkb_get_names(conn, device, mask), nullptr); + xcb_xkb_get_names_value_list_t values; + xcb_xkb_get_names_value_list_unpack(xcb_xkb_get_names_value_list(reply), reply->nTypes, reply->indicators, + reply->virtualMods, reply->groupNames, reply->nKeys, reply->nKeyAliases, reply->nRadioGroups, reply->which, + &values); + auto len = xcb_xkb_get_names_value_list_indicator_names_length(reply, &values); + free(reply); + + map entries; + for (int i = 0; i < len; i++) { + entries.emplace(values.indicatorNames[i], xpp::x::get_atom_name(conn, values.indicatorNames[i])); + } + + map results; + for (const auto& entry : entries) { + auto name = static_cast(entry.second).name(); + auto type = keyboard::indicator::type::NONE; + + if (string_util::compare(name, "caps lock")) { + type = keyboard::indicator::type::CAPS_LOCK; + } else if (string_util::compare(name, "num lock")) { + type = keyboard::indicator::type::NUM_LOCK; + } else { + continue; + } + + auto data = conn.xkb().get_named_indicator(device, 0, 0, entry.first); + auto mask = (*conn.xkb().get_indicator_map(device, 1 << data->ndx).maps().begin()).mods; + auto enabled = static_cast(data->on); + + results.emplace(type, keyboard::indicator{entry.first, mask, name, enabled}); + } + + return results; + } + + /** + * Parse symbol name and exclude entries blacklisted entries + */ + string parse_layout_symbol(string&& name) { + auto pos = name.find('('); + if (pos != string::npos) { + name.erase(pos); + } + if (string_util::contains(LAYOUT_SYMBOL_BLACKLIST, ";" + name + ";")) { + return ""; + } + return name; + } +} + +POLYBAR_NS_END