Skip to content

Instantly share code, notes, and snippets.

@bluetech
Created July 23, 2013 10:17
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bluetech/6061368 to your computer and use it in GitHub Desktop.
Save bluetech/6061368 to your computer and use it in GitHub Desktop.
get_map.c
#include <assert.h>
#include <err.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <xcb/xcb.h>
#include <xcb/xkb.h>
#include <xkbcommon/xkbcommon.h>
static void cleanup_free_fn(void *p) { free(*(void **)p); }
#define _cleanup_free_ __attribute__((cleanup(cleanup_free_fn)))
struct ctx {
xcb_connection_t *conn;
};
static void
setup_xkb(struct ctx *ctx)
{
{
const xcb_query_extension_reply_t *reply;
reply = xcb_get_extension_data(ctx->conn, &xcb_xkb_id);
if (!reply)
errx(1, "no xkb in x server");
}
{
xcb_xkb_use_extension_cookie_t cookie;
_cleanup_free_ xcb_xkb_use_extension_reply_t *reply = null;
cookie = xcb_xkb_use_extension(ctx->conn,
xcb_xkb_major_version,
xcb_xkb_minor_version);
reply = xcb_xkb_use_extension_reply(ctx->conn, cookie, null);
if (!reply)
errx(1, "couldn't use xkb extension");
if (!reply->supported)
errx(1, "the xkb extension is not supported in x server");
}
}
static const char *
get_atom_name(struct ctx *ctx, xcb_atom_t atom)
{
_cleanup_free_ xcb_get_atom_name_reply_t *reply = null;
char *name;
int length;
static char buf[1024]; /* fixme */
if (atom == 0)
return "<empty>";
{
xcb_get_atom_name_cookie_t cookie;
_cleanup_free_ xcb_generic_error_t *error = null;
cookie = xcb_get_atom_name(ctx->conn, atom);
reply = xcb_get_atom_name_reply(ctx->conn, cookie, &error);
if (!reply || error)
return "<invalid>";
}
length = xcb_get_atom_name_name_length(reply);
name = xcb_get_atom_name_name(reply);
snprintf(buf, sizeof(buf), "%.*s", length, name);
return buf;
}
static const char *
get_keysym_name(xcb_keysym_t keysym)
{
static char name[64];
xkb_keysym_get_name(keysym, name, sizeof(name));
return name;
}
#define all_components_mask \
(xcb_xkb_map_part_key_types | \
xcb_xkb_map_part_key_syms | \
xcb_xkb_map_part_modifier_map | \
xcb_xkb_map_part_explicit_components | \
xcb_xkb_map_part_key_actions | \
xcb_xkb_map_part_key_behaviors | \
xcb_xkb_map_part_virtual_mods | \
xcb_xkb_map_part_virtual_mod_map)
static void
get_map(struct ctx *ctx)
{
_cleanup_free_ xcb_xkb_get_map_reply_t *reply = null;
{
xcb_xkb_get_map_cookie_t cookie;
_cleanup_free_ xcb_generic_error_t *error = null;
/* send the xkbgetmap request. */
cookie = xcb_xkb_get_map(ctx->conn,
xcb_xkb_id_use_core_kbd,
all_components_mask,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0);
reply = xcb_xkb_get_map_reply(ctx->conn, cookie, &error);
if (!reply || error)
errx(1, "couldn't get get_map reply: error_code %d", error->error_code);
if ((reply->present & all_components_mask) != all_components_mask)
warnx("didn't get all components: want %x got %x",
all_components_mask, reply->present);
}
printf("minkeycode: %d\n", reply->minkeycode);
printf("maxkeycode: %d\n", reply->maxkeycode);
/* get the map itself, with all the details. */
xcb_xkb_get_map_map_t map;
{
void *buffer;
buffer = xcb_xkb_get_map_map(reply);
xcb_xkb_get_map_map_unpack(buffer,
reply->ntypes,
reply->nkeysyms,
reply->nkeyactions,
reply->totalactions,
reply->totalkeybehaviors,
reply->virtualmods,
reply->totalkeyexplicit,
reply->totalmodmapkeys,
reply->totalvmodmapkeys,
reply->present,
&map);
}
/* dump types. */
{
int length;
xcb_xkb_key_type_iterator_t iter;
printf("firsttype: %d\n", reply->firsttype);
printf("totaltype: %d\n", reply->totaltypes);
printf("ntypes: %d\n", reply->ntypes);
length = xcb_xkb_get_map_map_types_rtrn_length(reply, &map);
iter = xcb_xkb_get_map_map_types_rtrn_iterator(reply, &map);
for (int i = 0; i < length; i++) {
xcb_xkb_key_type_t *type = iter.data;
printf("type %d:\n", reply->firsttype + i);
printf("\tmods_mask: %d\n", type->mods_mask);
printf("\tmods_mods: %d\n", type->mods_mods);
printf("\tmods_vmods: %d\n", type->mods_vmods);
printf("\tnumlevels: %d\n", type->numlevels);
printf("\thaspreserve: %d\n", type->haspreserve);
/* dump type's entries. */
{
int length2;
xcb_xkb_kt_map_entry_iterator_t iter2;
length2 = xcb_xkb_key_type_map_length(type);
iter2 = xcb_xkb_key_type_map_iterator(type);
for (int j = 0; j < length2; j++) {
xcb_xkb_kt_map_entry_t *entry = iter2.data;
printf("\tentry %d:\n", j);
printf("\t\tactive: %d\n", entry->active);
printf("\t\tlevel: %d\n", entry->level);
printf("\t\tmods_mask: %d\n", entry->mods_mask);
printf("\t\tmods_mods: %d\n", entry->mods_mods);
printf("\t\tmods_vmods: %d\n", entry->mods_vmods);
xcb_xkb_kt_map_entry_next(&iter2);
}
}
/* dump type's preserve entries. */
{
int length2;
xcb_xkb_mod_def_iterator_t iter2;
length2 = xcb_xkb_key_type_preserve_length(type);
iter2 = xcb_xkb_key_type_preserve_iterator(type);
for (int j = 0; j < length2; j++) {
xcb_xkb_mod_def_t *preserve = iter2.data;
printf("\tpreserve %d:\n", j);
printf("\t\tmask: %d\n", preserve->mask);
printf("\t\trealmods: %d\n", preserve->realmods);
printf("\t\tvmods: %d\n", preserve->vmods);
xcb_xkb_mod_def_next(&iter2);
}
}
xcb_xkb_key_type_next(&iter);
}
}
/* dump key sym maps. */
{
int length;
xcb_xkb_key_sym_map_iterator_t iter;
printf("firstkeysym: %d\n", reply->firstkeysym);
printf("totalsyms: %d\n", reply->totalsyms);
printf("nkeysyms: %d\n", reply->nkeysyms);
length = xcb_xkb_get_map_map_syms_rtrn_length(reply, &map);
iter = xcb_xkb_get_map_map_syms_rtrn_iterator(reply, &map);
for (int i = 0; i < length; i++) {
xcb_xkb_key_sym_map_t *sym_map = iter.data;
printf("sym_map %d:\n", reply->firstkeysym + i);
/* todo: kt_index[4] */
printf("\tgroupinfo: %d\n", sym_map->groupinfo);
printf("\twidth: %d\n", sym_map->width);
printf("\tnsyms: %d\n", sym_map->nsyms);
/* dump sym map's keysyms. */
printf("\tkeysyms:\n");
{
int length2;
xcb_keysym_t *iter2;
length2 = xcb_xkb_key_sym_map_syms_length(sym_map);
iter2 = xcb_xkb_key_sym_map_syms(sym_map);
for (int j = 0; j < length2; j++) {
xcb_keysym_t keysym = *iter2;
printf("\t\t%s\n", get_keysym_name(keysym));
iter2++;
}
}
xcb_xkb_key_sym_map_next(&iter);
}
}
/* dump key actions. */
{
printf("firstkeyaction: %d\n", reply->firstkeyaction);
printf("totalactions: %d\n", reply->totalactions);
printf("nkeyactions: %d\n", reply->nkeyactions);
/* dump number of actions bound to the keys. */
{
int length;
uint8_t *iter;
length = xcb_xkb_get_map_map_acts_rtrn_count_length(reply, &map);
iter = xcb_xkb_get_map_map_acts_rtrn_count(&map);
for (int i = 0 ; i < length; i++) {
uint8_t count = *iter;
printf("action count %d:\n", reply->firstkeyaction + i);
printf("\tcount: %d\n", count);
iter++;
}
}
/* dump the actions array. */
{
int length;
xcb_xkb_action_iterator_t iter;
length = xcb_xkb_get_map_map_acts_rtrn_acts_length(reply, &map);
iter = xcb_xkb_get_map_map_acts_rtrn_acts_iterator(reply, &map);
for (int i = 0; i < length; i++) {
xcb_xkb_action_t *action = iter.data;
/* todo: action union. */
printf("action %d:\n", i);
printf("\ttype: %d\n", action->type);
xcb_xkb_action_next(&iter);
}
}
}
/* dump key behaviors. */
{
int length;
xcb_xkb_set_behavior_iterator_t iter;
printf("firstkeybehavior: %d\n", reply->firstkeybehavior);
printf("nkeybehaviors: %d\n", reply->nkeybehaviors);
printf("totalkeybehaviors: %d\n", reply->totalkeybehaviors);
length = xcb_xkb_get_map_map_behaviors_rtrn_length(reply, &map);
iter = xcb_xkb_get_map_map_behaviors_rtrn_iterator(reply, &map);
for (int i = 0; i < length; i++) {
xcb_xkb_set_behavior_t *set_behavior = iter.data;
xcb_xkb_behavior_t *behavior = &set_behavior->behavior;
printf("set_behavior %d:\n", reply->firstkeybehavior + i);
printf("\tkeycode: %d\n", set_behavior->keycode);
/* todo: behavior union. */
printf("\tbehavior type: %d\n", behavior->type);
xcb_xkb_set_behavior_next(&iter);
}
}
/* dump virtual mods. */
{
int length;
uint8_t *iter;
printf("virtualmods: %x\n", reply->virtualmods);
length = xcb_xkb_get_map_map_vmods_rtrn_length(reply, &map);
iter = xcb_xkb_get_map_map_vmods_rtrn(&map);
printf("length: %d\n", length);
for (int i = 0; i < length; i++) {
uint8_t vmods = *iter;
printf("vmods %d:\n", i);
printf("\tmask: %x\n", vmods);
iter++;
}
}
/* dump key explicit masks. */
{
int length;
xcb_xkb_set_explicit_iterator_t iter;
printf("firstkeyexplicit: %d\n", reply->firstkeyexplicit);
printf("nkeyexplicit: %d\n", reply->nkeyexplicit);
printf("totalkeyexplicit: %d\n", reply->totalkeyexplicit);
length = xcb_xkb_get_map_map_explicit_rtrn_length(reply, &map);
iter = xcb_xkb_get_map_map_explicit_rtrn_iterator(reply, &map);
for (int i = 0; i < length; i++) {
xcb_xkb_set_explicit_t *set_explicit = iter.data;
printf("set_explicit %d:\n", i);
printf("\tkeycode: %d\n", set_explicit->keycode);
printf("\texplicit: %d\n", set_explicit->explicit);
xcb_xkb_set_explicit_next(&iter);
}
}
/* dump mod map keys. */
{
int length;
xcb_xkb_key_mod_map_iterator_t iter;
printf("firstmodmapkey: %d\n", reply->firstmodmapkey);
printf("nmodmapkeys: %d\n", reply->nmodmapkeys);
printf("totalmodmapkeys: %d\n", reply->totalmodmapkeys);
length = xcb_xkb_get_map_map_modmap_rtrn_length(reply, &map);
iter = xcb_xkb_get_map_map_modmap_rtrn_iterator(reply, &map);
for (int i = 0; i < length; i++) {
xcb_xkb_key_mod_map_t *key_mod_map = iter.data;
printf("key_mod_map %d:\n", i);
printf("\tkeycode: %d\n", key_mod_map->keycode);
printf("\tmods: %d\n", key_mod_map->mods);
xcb_xkb_key_mod_map_next(&iter);
}
}
/* dump key vmod map keys. */
{
int length;
xcb_xkb_key_v_mod_map_iterator_t iter;
printf("firstvmodmapkey: %d\n", reply->firstvmodmapkey);
printf("nvmodmapkeys: %d\n", reply->nvmodmapkeys);
printf("totalvmodmapkeys: %d\n", reply->totalvmodmapkeys);
length = xcb_xkb_get_map_map_vmodmap_rtrn_length(reply, &map);
iter = xcb_xkb_get_map_map_vmodmap_rtrn_iterator(reply, &map);
for (int i = 0; i < length; i++) {
xcb_xkb_key_v_mod_map_t *key_v_mod_map = iter.data;
printf("key_v_mod_map %d:\n", i);
printf("\tkeycode: %d\n", key_v_mod_map->keycode);
printf("\tvmods: %d\n", key_v_mod_map->vmods);
xcb_xkb_key_v_mod_map_next(&iter);
}
}
}
#define all_indicators 0xffffffff
static void
get_indicator_map(struct ctx *ctx)
{
_cleanup_free_ xcb_xkb_get_indicator_map_reply_t *reply = null;
{
_cleanup_free_ xcb_generic_error_t *error = null;
xcb_xkb_get_indicator_map_cookie_t cookie;
cookie = xcb_xkb_get_indicator_map(ctx->conn,
xcb_xkb_id_use_core_kbd,
all_indicators);
reply = xcb_xkb_get_indicator_map_reply(ctx->conn, cookie, &error);
if (!reply || error)
errx(1, "couldn't get get_indicator_map reply");
}
assert(reply->which == all_indicators);
printf("which: %x\n", reply->which);
printf("realindicators: %x\n", reply->realindicators);
printf("nindicators: %d\n", reply->nindicators);
/* dump indicator maps. */
{
int length;
xcb_xkb_indicator_map_iterator_t iter;
length = xcb_xkb_get_indicator_map_maps_length(reply);
iter = xcb_xkb_get_indicator_map_maps_iterator(reply);
for (int i = 0; i < length; i++) {
xcb_xkb_indicator_map_t *indicator_map = iter.data;
printf("indicator_map %d:\n", i);
printf("\tflags: %d\n", indicator_map->flags);
printf("\twhichgroups: %d\n", indicator_map->whichgroups);
printf("\tgroups: %d\n", indicator_map->groups);
printf("\twhichmods: %d\n", indicator_map->whichmods);
printf("\tmods: %d\n", indicator_map->mods);
printf("\trealmods: %d\n", indicator_map->realmods);
printf("\tvmods: %d\n", indicator_map->vmods);
printf("\tctrls: %d\n", indicator_map->ctrls);
xcb_xkb_indicator_map_next(&iter);
}
}
}
#define all_groups 0xf
static void
get_compat_map(struct ctx *ctx)
{
_cleanup_free_ xcb_xkb_get_compat_map_reply_t *reply = null;
{
_cleanup_free_ xcb_generic_error_t *error = null;
xcb_xkb_get_compat_map_cookie_t cookie;
cookie = xcb_xkb_get_compat_map(ctx->conn,
xcb_xkb_id_use_core_kbd,
all_groups,
true,
0,
0);
reply = xcb_xkb_get_compat_map_reply(ctx->conn, cookie, &error);
if (!reply || error)
errx(1, "couldn't get reply for get_compat_map");
}
printf("groupsrtrn: %#x\n", reply->groupsrtrn);
printf("firstsirtrn: %d\n", reply->firstsirtrn);
printf("nsirtrn: %d\n", reply->nsirtrn);
printf("ntotalsi: %d\n", reply->ntotalsi);
/* dump sym interprets. */
{
int length;
xcb_xkb_sym_interpret_iterator_t iter;
length = xcb_xkb_get_compat_map_si_rtrn_length(reply);
iter = xcb_xkb_get_compat_map_si_rtrn_iterator(reply);
for (int i = 0; i < length; i++) {
xcb_xkb_sym_interpret_t *sym_interpret = iter.data;
printf("sym_interpret %d\n", reply->firstsirtrn + i);
printf("\tsym: %s\n", get_keysym_name(sym_interpret->sym));
printf("\tmods: %d\n", sym_interpret->mods);
printf("\tmatch: %d\n", sym_interpret->match);
printf("\tvirtualmod: %d\n", sym_interpret->virtualmod);
printf("\tflags: %d\n", sym_interpret->flags);
printf("\taction type: %d\n", sym_interpret->action.type);
/* todo: action fields */
xcb_xkb_sym_interpret_next(&iter);
}
}
/* dump modmap definitons. */
{
int length;
xcb_xkb_mod_def_iterator_t iter;
length = xcb_xkb_get_compat_map_group_rtrn_length(reply);
iter = xcb_xkb_get_compat_map_group_rtrn_iterator(reply);
for (int i = 0; i < length; i++) {
xcb_xkb_mod_def_t *mod_def = iter.data;
printf("mod_def %d\n", i);
printf("\tmask: %d\n", mod_def->mask);
printf("\trealmods: %d\n", mod_def->realmods);
printf("\tvmods: %d\n", mod_def->vmods);
xcb_xkb_mod_def_next(&iter);
}
}
}
#define all_name_details \
(xcb_xkb_name_detail_keycodes | \
xcb_xkb_name_detail_geometry | \
xcb_xkb_name_detail_symbols | \
xcb_xkb_name_detail_phys_symbols | \
xcb_xkb_name_detail_types | \
xcb_xkb_name_detail_compat | \
xcb_xkb_name_detail_key_type_names | \
xcb_xkb_name_detail_kt_level_names | \
xcb_xkb_name_detail_indicator_names | \
xcb_xkb_name_detail_key_names | \
xcb_xkb_name_detail_key_aliases | \
xcb_xkb_name_detail_virtual_mod_names | \
xcb_xkb_name_detail_group_names | \
xcb_xkb_name_detail_rg_names)
static void
get_names(struct ctx *ctx)
{
_cleanup_free_ xcb_xkb_get_names_reply_t *reply = null;
{
_cleanup_free_ xcb_generic_error_t *error = null;
xcb_xkb_get_names_cookie_t cookie;
cookie = xcb_xkb_get_names(ctx->conn,
xcb_xkb_id_use_core_kbd,
all_name_details);
reply = xcb_xkb_get_names_reply(ctx->conn, cookie, &error);
if (!reply || error)
errx(1, "couldn't get reply for get_names");
/* if ((reply->which & all_name_details) != all_name_details) */
/* warnx("didn't get all name details: want %x got %x", */
/* all_name_details, reply->which); */
}
printf("which: %d\n", reply->which);
printf("minkeycode: %d\n", reply->minkeycode);
printf("maxkeycode: %d\n", reply->maxkeycode);
printf("ntypes: %d\n", reply->ntypes);
printf("groupnames: %d\n", reply->groupnames);
printf("virtualmods: %x\n", reply->virtualmods);
printf("firstkey: %d\n", reply->firstkey);
printf("nkeys: %d\n", reply->nkeys);
printf("indicators: %x\n", reply->indicators);
printf("nradiogroups: %d\n", reply->nradiogroups);
printf("nkeyaliases: %d\n", reply->nkeyaliases);
printf("nktlevels: %d\n", reply->nktlevels);
xcb_xkb_get_names_value_list_t list;
{
void *buffer;
buffer = xcb_xkb_get_names_value_list(reply);
xcb_xkb_get_names_value_list_unpack(buffer,
reply->ntypes,
reply->indicators,
reply->virtualmods,
reply->groupnames,
reply->nkeys,
reply->nkeyaliases,
reply->nradiogroups,
reply->which,
&list);
}
/* dump section names. */
printf("keycodesname: %s\n", get_atom_name(ctx, list.keycodesname));
printf("geometryname: %s\n", get_atom_name(ctx, list.geometryname));
printf("symbolsname: %s\n", get_atom_name(ctx, list.symbolsname));
printf("physsymbolsname: %s\n", get_atom_name(ctx, list.physsymbolsname));
printf("typesname: %s\n", get_atom_name(ctx, list.typesname));
printf("compatname: %s\n", get_atom_name(ctx, list.compatname));
/* dump key type names. */
{
int key_type_names_length;
xcb_atom_t *key_type_names_iter;
int n_levels_per_type_length;
uint8_t *n_levels_per_type_iter;
int kt_level_names_length;
xcb_atom_t *kt_level_names_iter;
key_type_names_length = xcb_xkb_get_names_value_list_type_names_length(reply, &list);
key_type_names_iter = xcb_xkb_get_names_value_list_type_names(&list);
n_levels_per_type_length = xcb_xkb_get_names_value_list_n_levels_per_type_length(reply, &list);
n_levels_per_type_iter = xcb_xkb_get_names_value_list_n_levels_per_type(&list);
kt_level_names_length = xcb_xkb_get_names_value_list_kt_level_names_length(reply, &list);
kt_level_names_iter = xcb_xkb_get_names_value_list_kt_level_names(&list);
assert(key_type_names_length == n_levels_per_type_length);
/* assert(kt_level_names_length == sum of num_levels); */
for (int i = 0; i < key_type_names_length; i++) {
xcb_atom_t type_name = *key_type_names_iter;
uint8_t num_levels = *n_levels_per_type_iter;
printf("type name %d: %s\n", i, get_atom_name(ctx, type_name));
for (int j = 0; j < num_levels; j++) {
xcb_atom_t level_name = *kt_level_names_iter;
printf("\tlevel name %d: %s\n", j, get_atom_name(ctx, level_name));
kt_level_names_iter++;
}
key_type_names_iter++;
n_levels_per_type_iter++;
}
}
/* dump indicator names. */
{
int length;
xcb_atom_t *iter;
length = xcb_xkb_get_names_value_list_indicator_names_length(reply, &list);
iter = xcb_xkb_get_names_value_list_indicator_names(&list);
for (int i = 0; i < length; i++) {
xcb_atom_t indicator_name = *iter;
printf("indicator_name %d: %s\n", i, get_atom_name(ctx, indicator_name));
iter++;
}
}
/* dump virtual modifier names. */
{
int length;
xcb_atom_t *iter;
length = xcb_xkb_get_names_value_list_virtual_mod_names_length(reply, &list);
iter = xcb_xkb_get_names_value_list_virtual_mod_names(&list);
for (int i = 0; i < length; i++) {
xcb_atom_t virtual_mod_name = *iter;
printf("virtual_mod_name %d: %s\n", i, get_atom_name(ctx, virtual_mod_name));
iter++;
}
}
/* dump group names. */
{
int length;
xcb_atom_t *iter;
length = xcb_xkb_get_names_value_list_groups_length(reply, &list);
iter = xcb_xkb_get_names_value_list_groups(&list);
for (int i = 0; i < length; i++) {
xcb_atom_t group_name = *iter;
printf("group_name %d: %s\n", i, get_atom_name(ctx, group_name));
iter++;
}
}
/* dump key names. */
{
int length;
xcb_xkb_key_name_iterator_t iter;
length = xcb_xkb_get_names_value_list_key_names_length(reply, &list);
iter = xcb_xkb_get_names_value_list_key_names_iterator(reply, &list);
for (int i = 0; i < length; i++) {
xcb_xkb_key_name_t *key_name = iter.data;
printf("key_name %d: %.4s\n", reply->firstkey + i, key_name->name);
xcb_xkb_key_name_next(&iter);
}
}
/* dump key aliases. */
{
int length;
xcb_xkb_key_alias_iterator_t iter;
length = xcb_xkb_get_names_value_list_key_aliases_length(reply, &list);
iter = xcb_xkb_get_names_value_list_key_aliases_iterator(reply, &list);
for (int i = 0; i < length; i++) {
xcb_xkb_key_alias_t *alias = iter.data;
printf("key_alias %d: real %.4s; alias %.4s\n", i, alias->real, alias->alias);
xcb_xkb_key_alias_next(&iter);
}
}
/* dump radio group names. */
{
int length;
xcb_atom_t *iter;
length = xcb_xkb_get_names_value_list_radio_group_names_length(reply, &list);
iter = xcb_xkb_get_names_value_list_radio_group_names(&list);
for (int i = 0; i < length; i++) {
xcb_atom_t radio_group_name = *iter;
printf("radio_group_name %d: %s\n", i, get_atom_name(ctx, radio_group_name));
iter++;
}
}
}
int
main(void)
{
struct ctx ctx;
ctx.conn = xcb_connect(null, null);
if (!ctx.conn)
errx(1, "couldn't connect to display");
setup_xkb(&ctx);
get_map(&ctx);
get_indicator_map(&ctx);
get_compat_map(&ctx);
get_names(&ctx);
xcb_disconnect(ctx.conn);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment