mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-24 03:29:57 -07:00
JS Blebeacon keep/not prev cfg, use arraybuf, more options
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
let bleBeacon = require("blebeacon");
|
||||
let blebeacon = require("blebeacon");
|
||||
let math = require("math");
|
||||
|
||||
let currentIndex = 0;
|
||||
let currentByteValue = 0;
|
||||
let watchValues = [
|
||||
0x1A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0A, 0x0B, 0x0C, 0x11, 0x12, 0x13, 0x14, 0x15,
|
||||
@@ -9,50 +9,15 @@ let watchValues = [
|
||||
0x20, 0xEC, 0xEF
|
||||
];
|
||||
|
||||
function byteToHex(byte) {
|
||||
let hexChars = '0123456789abcdef';
|
||||
let hex = '';
|
||||
if (byte >= 0 && byte <= 255) {
|
||||
hex = hexChars[(byte >> 4) & 0x0F] + hexChars[byte & 0x0F];
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
function getNextByteValue() {
|
||||
let value = currentByteValue;
|
||||
currentByteValue = (currentByteValue + 1) % 256;
|
||||
return value;
|
||||
}
|
||||
|
||||
function generateRandomMac() {
|
||||
let mac = '';
|
||||
let mac = [];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (mac.length) mac += ':';
|
||||
let byte = getNextByteValue();
|
||||
mac += byteToHex(byte);
|
||||
mac.push(math.floor(math.random() * 256));
|
||||
}
|
||||
return mac;
|
||||
}
|
||||
|
||||
function bytesToHexString(bytes) {
|
||||
if (!bytes) {
|
||||
print("Invalid input for bytesToHexString");
|
||||
return '';
|
||||
}
|
||||
|
||||
let hexString = '';
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
hexString += byteToHex(bytes[i]);
|
||||
}
|
||||
return hexString;
|
||||
return Uint8Array(mac);
|
||||
}
|
||||
|
||||
function sendRandomModelAdvertisement() {
|
||||
if (!watchValues || watchValues.length === 0) {
|
||||
print("watchValues array is empty or undefined.");
|
||||
return;
|
||||
}
|
||||
|
||||
let model = watchValues[currentIndex];
|
||||
|
||||
let packet = [
|
||||
@@ -60,28 +25,27 @@ function sendRandomModelAdvertisement() {
|
||||
model
|
||||
];
|
||||
|
||||
let packetString = bytesToHexString(packet);
|
||||
if (!packetString) {
|
||||
print("Failed to generate packet string.");
|
||||
return;
|
||||
}
|
||||
let intervalMs = 50;
|
||||
|
||||
bleBeacon.setMac(generateRandomMac());
|
||||
bleBeacon.setData(packetString);
|
||||
bleBeacon.send();
|
||||
// Power level, min interval and max interval are optional
|
||||
blebeacon.setConfig(generateRandomMac(), 0x1F, intervalMs, intervalMs * 3);
|
||||
|
||||
blebeacon.setData(Uint8Array(packet));
|
||||
|
||||
blebeacon.start();
|
||||
|
||||
print("Sent data for model ID " + to_string(model));
|
||||
|
||||
currentIndex = (currentIndex + 1) % watchValues.length;
|
||||
|
||||
delay(500);
|
||||
delay(intervalMs);
|
||||
|
||||
bleBeacon.stop();
|
||||
|
||||
bleBeacon
|
||||
blebeacon.stop();
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Make sure it resets at script exit, true will keep advertising in background
|
||||
blebeacon.keepAlive(true);
|
||||
|
||||
while (true) {
|
||||
sendRandomModelAdvertisement();
|
||||
}
|
||||
@@ -3,81 +3,20 @@
|
||||
#include <extra_beacon.h>
|
||||
|
||||
typedef struct {
|
||||
char* data;
|
||||
char* mac_addr;
|
||||
size_t beacon_data_len;
|
||||
GapExtraBeaconConfig beacon_config;
|
||||
bool saved_prev_cfg;
|
||||
bool prev_cfg_set;
|
||||
GapExtraBeaconConfig prev_cfg;
|
||||
|
||||
bool saved_prev_data;
|
||||
uint8_t prev_data[EXTRA_BEACON_MAX_DATA_SIZE];
|
||||
uint8_t prev_data_len;
|
||||
|
||||
bool saved_prev_active;
|
||||
bool prev_active;
|
||||
|
||||
bool keep_alive;
|
||||
} JsBlebeaconInst;
|
||||
|
||||
struct OUI_MAP_ENTRY {
|
||||
const char* brand;
|
||||
const char* oui;
|
||||
};
|
||||
|
||||
struct OUI_MAP_ENTRY OUI_MAP[] = {
|
||||
{"Apple", "00:1F:7F"},
|
||||
{"Dell", "00:14:5F"},
|
||||
{"HP", "00:4C:6F"},
|
||||
{"Lenovo", "00:50:C2"},
|
||||
{"Microsoft", "00:0C:29"},
|
||||
{"Samsung", "00:1C:42"},
|
||||
{"Sony", "00:0A:95"},
|
||||
{"Acer", "00:26:A9"},
|
||||
{"Asus", "00:19:D8"},
|
||||
{"Google", "08:00:27"},
|
||||
{"HTC", "00:1F:B5"},
|
||||
{"Intel", "00:19:5D"},
|
||||
{"LG", "00:1C:61"},
|
||||
{"Motorola", "00:1F:42"},
|
||||
{"Toshiba", "00:1E:67"},
|
||||
{"Xiaomi", "00:26:A8"},
|
||||
};
|
||||
|
||||
#define OUI_MAP_SIZE (sizeof(OUI_MAP) / sizeof(OUI_MAP[0]))
|
||||
|
||||
int rand_range(int min, int max) {
|
||||
return min + rand() / (RAND_MAX / (max - min + 1) + 1);
|
||||
}
|
||||
|
||||
void byte_to_hex(char* output, unsigned char byte) {
|
||||
static const char hex_chars[] = "0123456789ABCDEF";
|
||||
output[0] = hex_chars[byte >> 4];
|
||||
output[1] = hex_chars[byte & 0x0F];
|
||||
}
|
||||
|
||||
static char* generate_mac_address(const char* brand) {
|
||||
char* mac_address = (char*)malloc(18 * sizeof(char));
|
||||
if(mac_address == NULL) {
|
||||
FURI_LOG_D("BLE", "Memory allocation failed.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* oui = NULL;
|
||||
for(unsigned int i = 0; i < OUI_MAP_SIZE; ++i) {
|
||||
if(strcmp(brand, OUI_MAP[i].brand) == 0) {
|
||||
oui = OUI_MAP[i].oui;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(oui == NULL) {
|
||||
FURI_LOG_D("BLE", "Brand not found.\n");
|
||||
free(mac_address);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char last_bytes[6];
|
||||
for(int i = 0; i < 3; ++i) {
|
||||
unsigned char byte = rand_range(0x00, 0xFF);
|
||||
byte_to_hex(&last_bytes[i * 2], byte);
|
||||
}
|
||||
|
||||
strcpy(mac_address, oui);
|
||||
strcat(mac_address, ":");
|
||||
strcat(mac_address, last_bytes);
|
||||
return mac_address;
|
||||
}
|
||||
|
||||
static JsBlebeaconInst* get_this_ctx(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsBlebeaconInst* storage = mjs_get_ptr(mjs, obj_inst);
|
||||
@@ -99,177 +38,162 @@ static bool check_arg_count(struct mjs* mjs, size_t count) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_str_arg(struct mjs* mjs, size_t index, char** value) {
|
||||
mjs_val_t str_obj = mjs_arg(mjs, index);
|
||||
if(!mjs_is_string(str_obj)) {
|
||||
ret_bad_args(mjs, "Argument must be a string");
|
||||
static bool get_int_arg(struct mjs* mjs, size_t index, uint8_t* value, bool error) {
|
||||
mjs_val_t int_obj = mjs_arg(mjs, index);
|
||||
if(!mjs_is_number(int_obj)) {
|
||||
if(error) ret_bad_args(mjs, "Argument must be a number");
|
||||
return false;
|
||||
}
|
||||
size_t str_len;
|
||||
const char* temp = mjs_get_string(mjs, &str_obj, &str_len);
|
||||
if(str_len == 0 || !temp) {
|
||||
ret_bad_args(mjs, "Bad string argument");
|
||||
return false;
|
||||
}
|
||||
|
||||
*value = (char*)malloc(str_len + 1);
|
||||
if(!*value) {
|
||||
ret_bad_args(mjs, "Memory allocation failed");
|
||||
return false;
|
||||
}
|
||||
strncpy(*value, temp, str_len);
|
||||
(*value)[str_len] = '\0'; // Ensure null termination
|
||||
*value = mjs_get_int(mjs, int_obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint8_t hex_char_to_uint(char c) {
|
||||
if(c >= '0' && c <= '9') return c - '0';
|
||||
if(c >= 'a' && c <= 'f') return 10 + c - 'a';
|
||||
if(c >= 'A' && c <= 'F') return 10 + c - 'A';
|
||||
return 0;
|
||||
}
|
||||
static void js_blebeacon_set_config(struct mjs* mjs) {
|
||||
JsBlebeaconInst* blebeacon = get_this_ctx(mjs);
|
||||
if(mjs_nargs(mjs) < 1 || mjs_nargs(mjs) > 4) {
|
||||
ret_bad_args(mjs, "Wrong argument count");
|
||||
return;
|
||||
}
|
||||
|
||||
static uint8_t* macstr_to_uint8(const char* macstr) {
|
||||
if(strlen(macstr) != 17) return NULL;
|
||||
char* mac = NULL;
|
||||
size_t mac_len = 0;
|
||||
mjs_val_t mac_arg = mjs_arg(mjs, 0);
|
||||
if(mjs_is_typed_array(mac_arg)) {
|
||||
if(mjs_is_data_view(mac_arg)) {
|
||||
mac_arg = mjs_dataview_get_buf(mjs, mac_arg);
|
||||
}
|
||||
mac = mjs_array_buf_get_ptr(mjs, mac_arg, &mac_len);
|
||||
}
|
||||
if(!mac || mac_len != EXTRA_BEACON_MAC_ADDR_SIZE) {
|
||||
ret_bad_args(mjs, "Wrong MAC address");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t* mac_bytes = (uint8_t*)malloc(6 * sizeof(uint8_t));
|
||||
if(!mac_bytes) return NULL;
|
||||
uint8_t power = GapAdvPowerLevel_0dBm;
|
||||
get_int_arg(mjs, 1, &power, false);
|
||||
power = CLAMP(power, GapAdvPowerLevel_6dBm, GapAdvPowerLevel_Neg40dBm);
|
||||
|
||||
for(size_t i = 0, j = 0; i < 17; i += 3, ++j) {
|
||||
mac_bytes[j] = (hex_char_to_uint(macstr[i]) << 4) | hex_char_to_uint(macstr[i + 1]);
|
||||
uint8_t intv_min = 50;
|
||||
get_int_arg(mjs, 2, &intv_min, false);
|
||||
intv_min = MAX(intv_min, 20);
|
||||
|
||||
if(i < 15 && macstr[i + 2] != ':' && macstr[i + 2] != '-') {
|
||||
free(mac_bytes);
|
||||
return NULL;
|
||||
uint8_t intv_max = 150;
|
||||
get_int_arg(mjs, 3, &intv_max, false);
|
||||
intv_max = MAX(intv_max, intv_min);
|
||||
|
||||
GapExtraBeaconConfig config = {
|
||||
.min_adv_interval_ms = intv_min,
|
||||
.max_adv_interval_ms = intv_max,
|
||||
.adv_channel_map = GapAdvChannelMapAll,
|
||||
.adv_power_level = power,
|
||||
.address_type = GapAddressTypePublic,
|
||||
};
|
||||
memcpy(config.address, (uint8_t*)mac, sizeof(config.address));
|
||||
|
||||
if(!blebeacon->saved_prev_cfg) {
|
||||
blebeacon->saved_prev_cfg = true;
|
||||
const GapExtraBeaconConfig* prev_cfg_ptr = furi_hal_bt_extra_beacon_get_config();
|
||||
if(prev_cfg_ptr) {
|
||||
blebeacon->prev_cfg_set = true;
|
||||
memcpy(&blebeacon->prev_cfg, prev_cfg_ptr, sizeof(blebeacon->prev_cfg));
|
||||
} else {
|
||||
blebeacon->prev_cfg_set = false;
|
||||
}
|
||||
}
|
||||
|
||||
return mac_bytes;
|
||||
}
|
||||
|
||||
static uint8_t* hexstr_to_uint8(const char* hexstr, size_t* out_length) {
|
||||
size_t len = strlen(hexstr);
|
||||
if(len % 2 != 0) return NULL;
|
||||
|
||||
if(len > EXTRA_BEACON_MAX_DATA_SIZE + 1) return NULL;
|
||||
|
||||
*out_length = len / 2;
|
||||
uint8_t* bytes = (uint8_t*)malloc(*out_length);
|
||||
if(!bytes) return NULL;
|
||||
|
||||
for(size_t i = 0; i < *out_length; ++i) {
|
||||
bytes[i] = (hex_char_to_uint(hexstr[i * 2]) << 4) | hex_char_to_uint(hexstr[i * 2 + 1]);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
furi_check(furi_hal_bt_extra_beacon_set_config(&config));
|
||||
}
|
||||
|
||||
static void js_blebeacon_set_data(struct mjs* mjs) {
|
||||
FURI_LOG_D("BLE", "Setting data");
|
||||
JsBlebeaconInst* blebeacon = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 1)) return;
|
||||
JsBlebeaconInst* inst = get_this_ctx(mjs);
|
||||
if(!inst) {
|
||||
FURI_LOG_D("BLE", "Beacon instance is null");
|
||||
ret_bad_args(mjs, "Beacon instance is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if(inst->data) {
|
||||
FURI_LOG_D("BLE", "Freeing existing data");
|
||||
free(inst->data);
|
||||
inst->data = NULL;
|
||||
}
|
||||
|
||||
if(!get_str_arg(mjs, 0, &(inst->data))) return; // get_str_arg now modifies inst->data directly
|
||||
|
||||
char* data = NULL;
|
||||
size_t data_len = 0;
|
||||
uint8_t* beacon_data = hexstr_to_uint8(inst->data, &data_len);
|
||||
if(!beacon_data) {
|
||||
FURI_LOG_D("BLE", "Failed to convert data to hex");
|
||||
ret_bad_args(mjs, "Failed to convert data to hex");
|
||||
mjs_val_t data_arg = mjs_arg(mjs, 0);
|
||||
if(mjs_is_typed_array(data_arg)) {
|
||||
if(mjs_is_data_view(data_arg)) {
|
||||
data_arg = mjs_dataview_get_buf(mjs, data_arg);
|
||||
}
|
||||
data = mjs_array_buf_get_ptr(mjs, data_arg, &data_len);
|
||||
}
|
||||
if(!data) {
|
||||
ret_bad_args(mjs, "Data must be a Uint8Array");
|
||||
return;
|
||||
}
|
||||
|
||||
FURI_LOG_D("BLE", "Successfully set beacon data");
|
||||
furi_hal_bt_extra_beacon_set_data(beacon_data, data_len);
|
||||
free(beacon_data);
|
||||
if(!blebeacon->saved_prev_data) {
|
||||
blebeacon->saved_prev_data = true;
|
||||
blebeacon->prev_data_len = furi_hal_bt_extra_beacon_get_data(blebeacon->prev_data);
|
||||
}
|
||||
furi_check(furi_hal_bt_extra_beacon_set_data((uint8_t*)data, data_len));
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_blebeacon_generate_mac(struct mjs* mjs) {
|
||||
char* company = "";
|
||||
if(!get_str_arg(mjs, 0, &company)) return;
|
||||
static void js_blebeacon_start(struct mjs* mjs) {
|
||||
JsBlebeaconInst* blebeacon = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 0)) return;
|
||||
|
||||
char* mac = generate_mac_address(company);
|
||||
|
||||
mjs_val_t js_mac_address = mjs_mk_string(mjs, mac, strlen(mac), 1);
|
||||
|
||||
return mjs_return(mjs, js_mac_address);
|
||||
}
|
||||
|
||||
static void js_blebeacon_set_mac(struct mjs* mjs) {
|
||||
FURI_LOG_D("BLE", "Setting Mac");
|
||||
if(!check_arg_count(mjs, 1)) {
|
||||
ret_bad_args(mjs, "Bad args");
|
||||
return;
|
||||
if(!blebeacon->saved_prev_active) {
|
||||
blebeacon->saved_prev_active = true;
|
||||
blebeacon->prev_active = furi_hal_bt_extra_beacon_is_active();
|
||||
}
|
||||
|
||||
JsBlebeaconInst* inst = get_this_ctx(mjs);
|
||||
char* mac_addr = "";
|
||||
if(!get_str_arg(mjs, 0, &mac_addr)) return;
|
||||
inst->mac_addr = mac_addr;
|
||||
inst->beacon_config.min_adv_interval_ms = 50;
|
||||
inst->beacon_config.max_adv_interval_ms = 150;
|
||||
|
||||
inst->beacon_config.adv_channel_map = GapAdvChannelMapAll;
|
||||
inst->beacon_config.adv_power_level = GapAdvPowerLevel_0dBm;
|
||||
|
||||
inst->beacon_config.address_type = GapAddressTypePublic;
|
||||
|
||||
uint8_t* mac = macstr_to_uint8(mac_addr);
|
||||
if(mac) {
|
||||
memcpy(inst->beacon_config.address, mac, 6);
|
||||
furi_hal_bt_extra_beacon_set_config(&inst->beacon_config);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
} else {
|
||||
FURI_LOG_D("BLE", "Bad MacAddress");
|
||||
ret_bad_args(mjs, "Bad Mac Address");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void js_blebeacon_send(struct mjs* mjs) {
|
||||
furi_hal_bt_extra_beacon_start();
|
||||
furi_check(furi_hal_bt_extra_beacon_start());
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_blebeacon_stop(struct mjs* mjs) {
|
||||
JsBlebeaconInst* blebeacon = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 0)) return;
|
||||
UNUSED(blebeacon);
|
||||
|
||||
furi_hal_bt_extra_beacon_stop();
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void* js_blebeacon_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
JsBlebeaconInst* inst = malloc(sizeof(JsBlebeaconInst));
|
||||
mjs_val_t blebeacon_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, blebeacon_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, inst));
|
||||
mjs_set(mjs, blebeacon_obj, "setData", ~0, MJS_MK_FN(js_blebeacon_set_data));
|
||||
mjs_set(mjs, blebeacon_obj, "setMac", ~0, MJS_MK_FN(js_blebeacon_set_mac));
|
||||
mjs_set(mjs, blebeacon_obj, "send", ~0, MJS_MK_FN(js_blebeacon_send));
|
||||
mjs_set(mjs, blebeacon_obj, "stop", ~0, MJS_MK_FN(js_blebeacon_stop));
|
||||
mjs_set(mjs, blebeacon_obj, "genMac", ~0, MJS_MK_FN(js_blebeacon_generate_mac));
|
||||
*object = blebeacon_obj;
|
||||
return inst;
|
||||
static void js_blebeacon_keep_alive(struct mjs* mjs) {
|
||||
JsBlebeaconInst* blebeacon = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 1)) return;
|
||||
|
||||
mjs_val_t bool_obj = mjs_arg(mjs, 0);
|
||||
blebeacon->keep_alive = mjs_get_bool(mjs, bool_obj);
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_blebeacon_destroy(void* ptr) {
|
||||
JsBlebeaconInst* inst = (JsBlebeaconInst*)ptr;
|
||||
if(inst) {
|
||||
free(inst->data);
|
||||
free(inst->mac_addr);
|
||||
free(inst);
|
||||
static void* js_blebeacon_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
JsBlebeaconInst* blebeacon = malloc(sizeof(JsBlebeaconInst));
|
||||
mjs_val_t blebeacon_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, blebeacon_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, blebeacon));
|
||||
mjs_set(mjs, blebeacon_obj, "setConfig", ~0, MJS_MK_FN(js_blebeacon_set_config));
|
||||
mjs_set(mjs, blebeacon_obj, "setData", ~0, MJS_MK_FN(js_blebeacon_set_data));
|
||||
mjs_set(mjs, blebeacon_obj, "start", ~0, MJS_MK_FN(js_blebeacon_start));
|
||||
mjs_set(mjs, blebeacon_obj, "stop", ~0, MJS_MK_FN(js_blebeacon_stop));
|
||||
mjs_set(mjs, blebeacon_obj, "keepAlive", ~0, MJS_MK_FN(js_blebeacon_keep_alive));
|
||||
*object = blebeacon_obj;
|
||||
return blebeacon;
|
||||
}
|
||||
|
||||
static void js_blebeacon_destroy(void* inst) {
|
||||
JsBlebeaconInst* blebeacon = inst;
|
||||
if(!blebeacon->keep_alive) {
|
||||
if(furi_hal_bt_extra_beacon_is_active()) {
|
||||
furi_hal_bt_extra_beacon_stop();
|
||||
}
|
||||
if(blebeacon->saved_prev_cfg && blebeacon->prev_cfg_set) {
|
||||
furi_check(furi_hal_bt_extra_beacon_set_config(&blebeacon->prev_cfg));
|
||||
}
|
||||
if(blebeacon->saved_prev_data) {
|
||||
furi_check(
|
||||
furi_hal_bt_extra_beacon_set_data(blebeacon->prev_data, blebeacon->prev_data_len));
|
||||
}
|
||||
if(blebeacon->prev_active) {
|
||||
furi_check(furi_hal_bt_extra_beacon_start());
|
||||
}
|
||||
}
|
||||
free(blebeacon);
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_blebeacon_desc = {
|
||||
|
||||
@@ -168,7 +168,7 @@ static void* js_keyboard_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
}
|
||||
|
||||
static void js_keyboard_destroy(void* inst) {
|
||||
JsKeyboardInst* keyboard = (JsKeyboardInst*)inst;
|
||||
JsKeyboardInst* keyboard = inst;
|
||||
view_dispatcher_remove_view(keyboard->view_dispatcher, JsKeyboardViewByteInput);
|
||||
byte_input_free(keyboard->byte_input);
|
||||
view_dispatcher_remove_view(keyboard->view_dispatcher, JsKeyboardViewTextInput);
|
||||
|
||||
Reference in New Issue
Block a user