/*
 * Copyright © 2013 Ran Benita
 * SPDX-License-Identifier: MIT
 */

#ifndef _XKBCOMMON_X11_H
#define _XKBCOMMON_X11_H

#include <xcb/xcb.h>
#include <xkbcommon/xkbcommon.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @file
 * libxkbcommon-x11 API - Additional X11 support for xkbcommon.
 */

/**
 * @defgroup x11 X11 support
 * Additional X11 support for xkbcommon.
 * @since 0.4.0
 *
 * @{
 */

/**
 * @page x11-overview Overview
 * @parblock
 *
 * The xkbcommon-x11 module provides a means for creating an xkb_keymap
 * corresponding to the currently active keymap on the X server.  To do
 * so, it queries the XKB X11 extension using the xcb-xkb library.  It
 * can be used as a replacement for Xlib's keyboard handling.
 *
 * Following is an example workflow using xkbcommon-x11.  A complete
 * example may be found in the tools/interactive-x11.c file in the
 * xkbcommon source repository.  On startup:
 *
 * 1. Connect to the X server using xcb_connect().
 * 2. Setup the XKB X11 extension.  You can do this either by using the
 *    xcb_xkb_use_extension() request directly, or by using the
 *    xkb_x11_setup_xkb_extension() helper function.
 *
 * The XKB extension supports using separate keymaps and states for
 * different keyboard devices.  The devices are identified by an integer
 * device ID and are managed by another X11 extension, XInput. The
 * original X11 protocol only had one keyboard device, called the "core
 * keyboard", which is still supported as a "virtual device".
 *
 * 3. We will use the core keyboard as an example.  To get its device ID,
 *    use either the xcb_xkb_get_device_info() request directly, or the
 *    xkb_x11_get_core_keyboard_device_id() helper function.
 * 4. Create an initial xkb_keymap for this device, using the
 *    xkb_x11_keymap_new_from_device() function.
 * 5. Create an initial xkb_state for this device, using the
 *    xkb_x11_state_new_from_device() function.
 *
 * @note At this point, you may consider setting various XKB controls and
 * XKB per-client flags.  For example, enabling detectable autorepeat: \n
 * https://www.x.org/releases/current/doc/kbproto/xkbproto.html#Detectable_Autorepeat
 *
 * Next, you need to react to state changes (e.g. a modifier was pressed,
 * the layout was changed) and to keymap changes (e.g. a tool like xkbcomp,
 * setxkbmap or xmodmap was used):
 *
 * 6. Select to listen to at least the following XKB events:
 *    NewKeyboardNotify, MapNotify, StateNotify; using the
 *    xcb_xkb_select_events_aux() request.
 * 7. When NewKeyboardNotify or MapNotify are received, recreate the
 *    xkb_keymap and xkb_state as described above.
 * 8. When StateNotify is received, update the xkb_state accordingly
 *    using the xkb_state_update_mask() function.
 *
 * @note It is also possible to use the KeyPress/KeyRelease @p state
 * field to find the effective modifier and layout state, instead of
 * using XkbStateNotify: \n
 * https://www.x.org/releases/current/doc/kbproto/xkbproto.html#Computing_A_State_Field_from_an_XKB_State
 * \n However, XkbStateNotify is more accurate.
 *
 * @note There is no need to call xkb_state_update_key(); the state is
 * already synchronized.
 *
 * Finally, when a key event is received, you can use ordinary xkbcommon
 * functions, like xkb_state_key_get_one_sym() and xkb_state_key_get_utf8(),
 * as you normally would.
 *
 * @endparblock
 */

/**
 * The minimal compatible major version of the XKB X11 extension which
 * this library can use.
 */
#define XKB_X11_MIN_MAJOR_XKB_VERSION 1
/**
 * The minimal compatible minor version of the XKB X11 extension which
 * this library can use (for the minimal major version).
 */
#define XKB_X11_MIN_MINOR_XKB_VERSION 0

/** Flags for the xkb_x11_setup_xkb_extension() function. */
enum xkb_x11_setup_xkb_extension_flags {
    /** Do not apply any flags. */
    XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS = 0
};

/**
 * Setup the XKB X11 extension for this X client.
 *
 * The xkbcommon-x11 library uses various XKB requests.  Before doing so,
 * an X client must notify the server that it will be using the extension.
 * This function (or an XCB equivalent) must be called before any other
 * function in this library is used.
 *
 * Some X servers may not support or disable the XKB extension.  If you
 * want to support such servers, you need to use a different fallback.
 *
 * You may call this function several times; it is idempotent.
 *
 * @param connection
 *     An XCB connection to the X server.
 * @param major_xkb_version
 *     See @p minor_xkb_version.
 * @param minor_xkb_version
 *     The XKB extension version to request.  To operate correctly, you
 *     must have (major_xkb_version, minor_xkb_version) >=
 *     (XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION),
 *     though this is not enforced.
 * @param flags
 *     Optional flags, or 0.
 * @param[out] major_xkb_version_out
 *     See @p minor_xkb_version_out.
 * @param[out] minor_xkb_version_out
 *     Backfilled with the compatible XKB extension version numbers picked
 *     by the server.  Can be NULL.
 * @param[out] base_event_out
 *     Backfilled with the XKB base (also known as first) event code, needed
 *     to distinguish XKB events.  Can be NULL.
 * @param[out] base_error_out
 *     Backfilled with the XKB base (also known as first) error code, needed
 *     to distinguish XKB errors.  Can be NULL.
 *
 * @returns 1 on success, or 0 on failure.
 */
XKB_EXPORT int
xkb_x11_setup_xkb_extension(xcb_connection_t *connection,
                            uint16_t major_xkb_version,
                            uint16_t minor_xkb_version,
                            enum xkb_x11_setup_xkb_extension_flags flags,
                            uint16_t *major_xkb_version_out,
                            uint16_t *minor_xkb_version_out,
                            uint8_t *base_event_out,
                            uint8_t *base_error_out);

/**
 * Get the keyboard device ID of the core X11 keyboard.
 *
 * @param connection An XCB connection to the X server.
 *
 * @returns A device ID which may be used with other xkb_x11_* functions,
 *          or -1 on failure.
 */
XKB_EXPORT int32_t
xkb_x11_get_core_keyboard_device_id(xcb_connection_t *connection);

/**
 * Create a keymap from an X11 keyboard device.
 *
 * This function queries the X server with various requests, fetches the
 * details of the active keymap on a keyboard device, and creates an
 * xkb_keymap from these details.
 *
 * @param context
 *     The context in which to create the keymap.
 * @param connection
 *     An XCB connection to the X server.
 * @param device_id
 *     An XInput device ID (in the range 0-127) with input class KEY.
 *     Passing values outside of this range is an error (the XKB protocol
 *     predates the XInput2 protocol, which first allowed IDs > 127).
 * @param flags
 *     Optional flags for the keymap, or 0.
 *
 * @returns A keymap retrieved from the X server, or NULL on failure.
 *
 * @memberof xkb_keymap
 */
XKB_EXPORT struct xkb_keymap *
xkb_x11_keymap_new_from_device(struct xkb_context *context,
                               xcb_connection_t *connection,
                               int32_t device_id,
                               enum xkb_keymap_compile_flags flags);

/**
 * Create a new keyboard state object from an X11 keyboard device.
 *
 * This function is the same as xkb_state_new(), only pre-initialized
 * with the state of the device at the time this function is called.
 *
 * @param keymap
 *     The keymap for which to create the state.
 * @param connection
 *     An XCB connection to the X server.
 * @param device_id
 *     An XInput 1 device ID (in the range 0-255) with input class KEY.
 *     Passing values outside of this range is an error.
 *
 * @returns A new keyboard state object, or NULL on failure.
 *
 * @memberof xkb_state
 */
XKB_EXPORT struct xkb_state *
xkb_x11_state_new_from_device(struct xkb_keymap *keymap,
                              xcb_connection_t *connection,
                              int32_t device_id);

/** @} */

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif /* _XKBCOMMON_X11_H */
