Compare commits

...

28 Commits

Author SHA1 Message Date
Lephenixnoir 4c307af02b
fxconv: preliminary support for fonts in PythonExtra 2024-03-21 08:29:06 +01:00
Lephenixnoir 2acc439ed4
fxsdk: add TARGET_FXCG50_FASTLOAD macro for add-in push builds 2024-02-03 19:45:15 +01:00
Lephenixnoir 8030d6bdc6
fxconv: PythonExtra support for bopti-cg 2024-02-03 16:00:05 +01:00
Lephenixnoir 4a84bfdcd4
fxconv: fix stupid compact mode bug 2024-02-03 16:00:05 +01:00
Lephenixnoir 6e62fb7d6d
fxconv: PythonExtra support for bopti-fx 2024-02-01 17:48:45 +01:00
Lephenixnoir 975f29a471
fxconv: (fix indent) 2024-01-31 23:28:49 +01:00
Lephenixnoir da79a6a0e8
fxlink: add --folder option to fxlink -s to select output folder 2024-01-30 22:19:19 +01:00
Lephenixnoir ecf04cb634
fxconv: make bopti-fx image data a pointer 2024-01-21 21:06:01 +01:00
Lephenixnoir 45fd52444f
fxlink: add (unused) scale parameter to SDL2 video capture 2024-01-21 21:05:36 +01:00
Lephenixnoir 9f4d17ca4f
fxlink: don't free glib pointer apparently not malloc'ed 2023-12-03 16:08:58 +01:00
Lephenixnoir 8f50f7694a
fxconv: fix c-c/c-v in range error messages 2023-10-17 21:56:38 +02:00
Lephenixnoir 88235041a3
fxconv: add i8/i16/i32, with range checks 2023-10-17 20:18:09 +02:00
Lephenixnoir be8c1f0d94
fxconv: alignment parameter in fxconv.ptr() (default 4) 2023-08-20 10:41:53 +02:00
Lephenixnoir 11e3b614c2
fxconv: add an elf_multi() function to produce multiple variables 2023-08-15 21:59:17 +02:00
Lephenixnoir cf3ab5d5e0
fxconv: copy elements in += ObjectData instead of referencing subobject
This way, after o1 += o2, when o1 is linked all the outer data of o2 is
linked alongside the outer data of o1, and the inner data remains
contiguous. This is important for arrays, where we don't want the outer
data of o2 to appear before the next inner field of o1.

With this commit, an update to o2 after o1 += o2 no longer updates o1.
This wasn't a feature in the first place.
2023-08-08 20:26:37 +02:00
Lephenixnoir 82027e1057
bump version to 2.10.0 2023-04-01 23:51:12 +02:00
Lephenixnoir 7b77fb9c0b
libfxlink: add status functions to avoid looking into fdev fields 2023-04-01 21:35:28 +02:00
Lephenixnoir 394d05726d
fxlink: add missing <unistd.h> to tui/tui-interactive.c 2023-03-28 21:43:59 +02:00
Lephenixnoir 0c0eb7f4f5
libfxlink: use old libusb for compatibility with 1.0.23 2023-03-28 19:35:00 +02:00
Lephenixnoir 1251ca23ee
libfxlink: return status from fxlink_device_start_bulk_{IN,OUT} 2023-03-27 19:47:08 +02:00
Lephenixnoir 1573db3860
libfxlink: install the library 2023-03-27 19:46:29 +02:00
Lephenixnoir 065233387d
split fxlink into library (not installed yet) and executable 2023-03-26 12:20:50 +02:00
Lephenixnoir 3f4aa1e750
fxlink: usable TUI command setup + gintctl test commands 2023-03-26 11:41:55 +02:00
Lephenixnoir cef9d21076
fxlink: start implementing TUI commands (mainly gintctl tests) 2023-03-17 21:32:01 +01:00
Lephenixnoir f83ea7e3d3
fxsdk: restore almost-original CG icon, but with the GIMP template 2023-03-12 22:07:30 +01:00
Lephenixnoir 4b980d949b
fxsdk: another failed attempt at a better CG icon 2023-03-12 20:55:48 +01:00
Lephenixnoir 0a61ffc523
fxlink: basic TUI commands 2023-03-12 20:55:18 +01:00
Lephenixnoir c7c1ec35f7
fxlink: send zero-length packets after commands 2023-03-12 20:54:53 +01:00
29 changed files with 1410 additions and 240 deletions

View File

@ -1,7 +1,7 @@
# Build system for the fxSDK
cmake_minimum_required(VERSION 3.15)
project(fxSDK VERSION 2.9.2 LANGUAGES C)
project(fxSDK VERSION 2.10.0 LANGUAGES C)
option(FXLINK_DISABLE_UDISKS2 "Do not build the UDisks2-based features of fxlink")
option(FXLINK_DISABLE_SDL2 "Do not build the SDL2-based features of fxlink")
@ -42,31 +42,41 @@ add_custom_command(OUTPUT "${BIN}/fxsdk.sh"
DEPENDS "${SRC}/fxsdk/fxsdk.sh")
add_custom_target(fxsdk ALL DEPENDS "${BIN}/fxsdk.sh")
# fxlink
configure_file(fxlink/include/fxlink/config.h.in
# libfxlink
configure_file(libfxlink/include/fxlink/config.h.in
"${BIN}/include/fxlink/config.h")
add_library(libfxlink STATIC
libfxlink/defs.c
libfxlink/devices.c
libfxlink/filter.c
libfxlink/logging.c
libfxlink/protocol.c)
target_link_libraries(libfxlink PUBLIC PkgConfig::libusb)
target_include_directories(libfxlink PUBLIC
"${BIN}/include"
"${SRC}/libfxlink/include")
set_target_properties(libfxlink PROPERTIES
OUTPUT_NAME "fxlink") # libfxlink.a
# fxlink
add_executable(fxlink
fxlink/defs.c
fxlink/devices.c
fxlink/filter.c
fxlink/logging.c
fxlink/main.c
fxlink/protocol.c
fxlink/modes/interactive.c
fxlink/modes/list.c
fxlink/modes/push.c
fxlink/modes/tui-interactive.c
fxlink/modes/udisks2.c
fxlink/tooling/libpng.c
fxlink/tooling/sdl2.c
fxlink/tooling/udisks2.c
fxlink/tui/commands.c
fxlink/tui/command-util.c
fxlink/tui/input.c
fxlink/tui/layout.c
fxlink/tui/render.c)
fxlink/tui/render.c
fxlink/tui/tui-interactive.c)
target_link_libraries(fxlink
PkgConfig::libpng PkgConfig::libusb PkgConfig::ncurses -lm)
libfxlink PkgConfig::libpng PkgConfig::ncurses -lm)
target_include_directories(fxlink PRIVATE
"${BIN}/include"
"${SRC}/fxlink/include")
if(NOT FXLINK_DISABLE_UDISKS2)
target_link_libraries(fxlink PkgConfig::udisks2)
@ -87,5 +97,11 @@ install(FILES "${BIN}/fxg1a" TYPE BIN)
# fxconv
install(PROGRAMS fxconv/fxconv-main.py TYPE BIN RENAME fxconv)
install(FILES fxconv/fxconv.py TYPE BIN)
#fxlink
# libfxlink
install(FILES "${BIN}/include/fxlink/config.h" DESTINATION include/fxlink/)
install(DIRECTORY libfxlink/include/ DESTINATION include
FILES_MATCHING PATTERN "*.h")
install(DIRECTORY libfxlink/cmake/ DESTINATION lib/cmake)
install(TARGETS libfxlink DESTINATION lib)
# fxlink
install(TARGETS fxlink)

View File

@ -23,6 +23,7 @@ When TYPE is specified (one-shot conversion), it should be one of:
--libimg-image Convert to the libimg image format
--custom Use a custom converter; you might want to specify an explicit
type by adding "custom-type:your_custom_type" (see below)
Custom converters can be specified by:
--converters Semicolon-separated list of custom converters (converters.py
in the current directory is detected as one per legacy)
@ -31,8 +32,13 @@ syntax (names can contain dots). For example:
fxconv -f myfont.png -o myfont.o charset:ascii grid.padding:1 height:7
Some formats differ between platforms so you should specify it when possible:
--fx Casio fx-9860G family (black-and-white calculators)
--cg Casio fx-CG 50 family (16-bit color calculators)
--fx CASIO fx-9860G family (black-and-white calculators)
--cg CASIO fx-CG 50 family (16-bit color calculators)
Finally, there is some support (non-final) for PythonExtra, in which case the
output file is as Python file instead of an object file.
--py Convert for PythonExtra (some types supported)
--py-compact Use compact bytes notation (shorter, but non-printable)
""".strip()
# Simple error-warnings system
@ -60,6 +66,7 @@ def main():
target = { 'toolchain': None, 'arch': None, 'section': None }
use_custom = False
converter_paths = []
py = { 'enabled': False, 'compact': False }
# Parse command-line arguments
@ -69,7 +76,7 @@ def main():
try:
longs = ["help", "output=", "toolchain=", "arch=", "section=", "fx",
"cg", "converters="] + types.split()
"cg", "converters=", "py", "py-compact"] + types.split()
opts, args = getopt.gnu_getopt(sys.argv[1:], "hsbifo:", longs)
except getopt.GetoptError as error:
return err(error)
@ -94,6 +101,10 @@ def main():
mode = "custom"
elif name == "--converters":
converter_paths = [path for path in value.split(";") if path]
elif name == "--py":
py['enabled'] = True
elif name == "--py-compact":
py['compact'] = True
# Other names are modes
else:
mode = name[1] if len(name)==2 else name[2:]
@ -144,6 +155,7 @@ def main():
spec.loader.exec_module(module)
converters.append(module.convert)
params["py"] = py
fxconv.convert(input, params, target, output, model, converters)
if __name__ == "__main__":

View File

@ -15,7 +15,7 @@ __all__ = [
# Color names
"FX_BLACK", "FX_DARK", "FX_LIGHT", "FX_WHITE", "FX_ALPHA",
# Conversion mechanisms
"ObjectData", "u8", "u16", "u32", "ref", "sym",
"ObjectData", "u8", "u16", "u32", "i8", "i16", "i32", "ref", "sym",
# Functions
"quantize", "convert", "elf",
# Reusable classes
@ -69,7 +69,7 @@ FX_PROFILES = [
FxProfile(0x0, "mono", { FX_BLACK, FX_WHITE }, [
lambda c: (c == FX_BLACK),
]),
# Black-and-white with transparency, equivalent of two bitmaps in ML
# Black-and-white with transparency, equivalent of two bitmaps in ML
FxProfile(0x1, "mono_alpha", { FX_BLACK, FX_WHITE, FX_ALPHA }, [
lambda c: (c != FX_ALPHA),
lambda c: (c == FX_BLACK),
@ -159,7 +159,7 @@ FX_CHARSETS = {
"ascii": [ (0x00, 128) ],
# Custom Unicode block intervals
"unicode": [],
# Single block 0x00-0xff (does not imply single-byte encoding)
# Single block 0x00-0xff (does not imply single-byte encoding)
"256chars": [ (0x00, 256) ],
}
@ -167,21 +167,40 @@ FX_CHARSETS = {
# Conversion mechanisms
#
def u8(x):
return bytes([ x & 255 ])
def u16(x):
return bytes([ (x >> 8) & 255, x & 255 ])
def u32(x):
return bytes([ (x >> 24) & 255, (x >> 16) & 255, (x >> 8) & 255, x & 255 ])
def u8(x, check=False):
if check and not (0 <= x < 2**8):
raise FxconvError(f"integer {x} out of range for u8")
return bytes([ x & 255 ])
def u16(x, check=False):
if check and not (0 <= x < 2**16):
raise FxconvError(f"integer {x} out of range for u16")
return bytes([ (x >> 8) & 255, x & 255 ])
def u32(x, check=False):
if check and not (0 <= x < 2**32):
raise FxconvError(f"integer {x} out of range for u32")
return bytes([ (x >> 24) & 255, (x >> 16) & 255, (x >> 8) & 255, x & 255 ])
def ref(base, offset=None, padding=None, prefix_underscore=True):
def i8(x, check=True):
if check and not (-2**7 <= x < 2**7):
raise FxconvError(f"integer {x} out of range for i8")
return bytes([ x & 255 ])
def i16(x, check=True):
if check and not (-2**15 <= x < 2**15):
raise FxconvError(f"integer {x} out of range for i16")
return bytes([ (x >> 8) & 255, x & 255 ])
def i32(x, check=True):
if check and not (-2**31 <= x < 2**31):
raise FxconvError(f"integer {x} out of range for i32")
return bytes([ (x >> 24) & 255, (x >> 16) & 255, (x >> 8) & 255, x & 255 ])
def ref(base, offset=None, padding=None, prefix_underscore=True, align=None):
if isinstance(base, bytes) or isinstance(base, bytearray):
base = bytes(base)
if offset is not None:
raise FxconvError(f"reference to bytes does not allow offset")
if padding and len(base) % padding != 0:
base += bytes(padding - len(base) % padding)
return Ref("bytes", base)
return Ref("bytes", base, align or 4)
elif isinstance(base, str):
if padding is not None:
@ -191,19 +210,24 @@ def ref(base, offset=None, padding=None, prefix_underscore=True):
if offset is not None:
offset = int(offset)
base = f"{base} + {offset}"
return Ref("name", base)
return Ref("name", base, align or 4)
elif isinstance(base, ObjectData):
if offset is not None or padding is not None:
raise FxconvError("reference to structure does not allow offset " +
"or padding")
return Ref("struct", base)
if offset is not None:
raise FxconvError("reference to structure does not allow offset")
if padding is not None:
raise FxconvError("reference to structure does not allow padding")
# Allow over-aligning the structure (but not more than 4)
align = max(base.alignment, align or 4)
if align > 4:
raise FxconvError(f"align {align} > 4 will not be honored (yet)")
return Ref("struct", base, align)
else:
raise FxconvError(f"invalid type {type(base)} for ref()")
def ptr(base):
return ref(base)
def ptr(*args, **kwargs):
return ref(*args, **kwargs)
def chars(text, length, require_final_nul=True):
btext = bytes(text, 'utf-8')
@ -221,7 +245,7 @@ def sym(name):
# "bytes" -> target is a bytes(), we point to that data
# "name" -> target is an str like "_sym+2", we point to that
# "struct" -> target is an ObjectData
Ref = collections.namedtuple("Ref", ["kind", "target"])
Ref = collections.namedtuple("Ref", ["kind", "target", "align"])
Sym = collections.namedtuple("Sym", ["name"])
@ -250,7 +274,7 @@ class ObjectData:
elif isinstance(other, Sym):
self.inner.append(other)
elif isinstance(other, ObjectData):
self.inner.append(other)
self.inner += other.inner
return self
@staticmethod
@ -273,34 +297,23 @@ class ObjectData:
return padding
def link(self, symbol):
inner = []
inner = self.inner
outer = []
elements = []
size = 0
# First unfold all structures within [inner] as we accumulate the total
# size of the inner data
for el in self.inner:
if isinstance(el, ObjectData):
size += self.align(size, el.alignment, inner)
code, code_size = el.link(f"{symbol} + {size}")
inner.append((code, code_size))
size += code_size
else:
inner.append(el)
size += self.element_size(el)
size = sum(self.element_size(el) for el in inner)
# Then replace complex references with unfolded data appended at the
# end of the structure
for el in inner:
if isinstance(el, Ref) and el.kind == "bytes":
elements.append(Ref("name", f"{symbol} + {size}"))
size += self.align(size, el.align, outer)
elements.append(Ref("name", f"{symbol} + {size}", None))
outer.append(el.target)
size += self.element_size(el.target)
elif isinstance(el, Ref) and el.kind == "struct":
size += self.align(size, el.target.alignment, outer)
elements.append(Ref("name", f"{symbol} + {size}"))
elements.append(Ref("name", f"{symbol} + {size}", None))
code, code_size = el.target.link(f"{symbol} + {size}")
outer.append((code, code_size))
size += code_size
@ -500,8 +513,16 @@ def convert_bopti_fx(input, params):
data[n] = layer[4 * longword + i]
n += 1
if params["py"]["enabled"]:
w, h = img.size
return ["import gint\n",
f"{params['name']} = gint.image({p.id}, {w}, {h}, ", data, ")\n"]
# Generate the object file
return header + data
o = ObjectData()
o += header
o += ptr(data)
return o
def _image_project(img, f):
# New width and height
@ -581,6 +602,17 @@ def convert_image_cg(input, params):
data, stride, palette, color_count = image_encode(img, format)
if params["py"]["enabled"]:
w, h = img.size
return [
"import gint\n",
f"{params['name']} = gint.image({format.id}, {color_count}, ",
f"{img.width}, {img.height}, {stride}, ",
data,
", ",
"None" if palette is None else palette,
")\n"]
o = ObjectData()
o += u8(format.id)
o += u8(3) # DATA_RO, PALETTE_RO
@ -775,6 +807,19 @@ def convert_topti(input, params):
# Object file generation
#---
if params["py"]["enabled"]:
l = [ "import gint\n",
f"{params['name']} = gint.font({title or None}, {flags}, ",
f"{line_height}, {grid.h}, {len(blocks)}, {glyph_count}, ",
f"{char_spacing}, ",
data_blocks,
data_glyphs ]
if proportional:
l += [data_index, data_width]
else:
l += [f"{grid.w}, {(grid.w * grid.h + 31) >> 5}"]
return l + [")\n"]
# Base data: always put the raw data and blocks first since they are
# 4-aligned, to preserve alignment on the rest of the references.
o = ObjectData()
@ -1139,8 +1184,14 @@ def convert(input, params, target, output=None, model=None, custom=None):
else:
raise FxconvError(f'unknown resource type \'{t}\'')
# PythonExtra conversion: output a file
if params["py"]["enabled"]:
if isinstance(o, ObjectData):
raise FxconvError(f'conversion doe not support Python output')
pyout(o, output, params)
# Standard conversions: save to ELF in the natural way
elf(o, output, "_" + params["name"], **target)
else:
elf(o, output, "_" + params["name"], **target)
def elf(data, output, symbol, toolchain=None, arch=None, section=None,
assembly=None):
@ -1154,9 +1205,9 @@ def elf(data, output, symbol, toolchain=None, arch=None, section=None,
The symbol name must have a leading underscore if it is to be declared and
used from a C program.
The toolchain can be any target triplet for which the compiler is
available. The architecture is deduced from some typical triplets;
otherwise it can be set, usually as "sh3" or "sh4-nofpu". This affects the
The toolchain can be any target triplet for which the compiler is
available. The architecture is deduced from some typical triplets;
otherwise it can be set, usually as "sh3" or "sh4-nofpu". This affects the
--binary-architecture flag of objcopy. If arch is set to "fx" or "cg", this
function tries to be smart and:
@ -1186,6 +1237,14 @@ def elf(data, output, symbol, toolchain=None, arch=None, section=None,
Produces an output file and returns nothing.
"""
# Unfold ObjectData into data and assembly
if isinstance(data, ObjectData):
assembly = f".global {symbol}\n" + \
f"{symbol}:\n" + \
data.link(symbol)[0] + \
(assembly or "")
data = None
# Toolchain parameters
if toolchain is None:
@ -1206,16 +1265,9 @@ def elf(data, output, symbol, toolchain=None, arch=None, section=None,
raise FxconvError(f"non-trivial architecture for {toolchain} must be "+
"specified")
# Unfold ObjectData into data and assembly
if isinstance(data, ObjectData):
asm = ".section " + section.split(",",1)[0] + "\n"
asm += f".global {symbol}\n"
asm += f"{symbol}:\n"
asm += data.link(symbol)[0]
asm += (assembly or "")
data = None
assembly = asm
if assembly:
sec = ".section " + section.split(",",1)[0]
assembly = sec + "\n" + assembly
if data is None and assembly is None:
raise FxconvError("elf() but no data and no assembly")
@ -1275,6 +1327,52 @@ def elf(data, output, symbol, toolchain=None, arch=None, section=None,
if assembly:
fp_asm.close()
def elf_multi(vars, output, assembly=None, **kwargs):
"""
Like elf(), but instead of one symbol/data pair, allows defining multiple
variables. vars should be a list [(symbol, ObjectData), ...]. Keyword
arguments are passed to elf().
"""
asm = ""
for symbol, objdata in vars:
asm += f".global {symbol}\n"
asm += f"{symbol}:\n"
asm += objdata.link(symbol)[0]
asm += assembly or ""
return elf(None, output, None, assembly=asm, **kwargs)
def pyout(bits, output, params):
# Compact into byte strings to avoid building tuples in the heap;
# MicroPython allows basically anything in literal strings (including
# NUL!), we just have to escape \, \n, \r, and ".
def byteify(c):
if c == ord('"'):
return b'\\"'
if c == ord('\n'):
return b'\\n'
if c == ord('\r'):
return b'\\r'
if c == ord('\\'):
return b'\\\\'
return bytes([c])
with open(output, "wb") as fp:
for section in bits:
if isinstance(section, bytearray):
section = bytes(section)
if isinstance(section, bytes):
if params["py"]["compact"]:
fp.write(b'b"')
for byte in section:
fp.write(byteify(byte))
fp.write(b'"')
else:
fp.write(repr(section).encode("utf-8"))
else:
fp.write(section.encode("utf-8"))
#
# Meta API
#

View File

@ -28,7 +28,8 @@ int main_list(struct fxlink_filter *filter, delay_t *delay,
int main_blocks(struct fxlink_filter *filter, delay_t *delay);
/* Main function for -s */
int main_send(struct fxlink_filter *filter, delay_t *delay, char **files);
int main_send(struct fxlink_filter *filter, delay_t *delay, char **files,
char *outfolder);
/* Main function for -i */
int main_interactive(struct fxlink_filter *filter, delay_t *delay,

View File

@ -20,7 +20,7 @@ static const char *help_string =
"usage: %1$s (-l|-b|-t) [General options]\n"
" %1$s -i [-r] [--fxlink-log[=<FILE>]] [General options]\n"
" %1$s -p <FILE> [General options]\n"
" %1$s -s <FILES>... [General options]\n"
" %1$s -s <FILES>... [--folder=OUTFOLDER] [General options]\n"
"\n"
"fxlink interacts with CASIO calculators of the fx and fx-CG families over\n"
"the USB port, using libusb. It can also transfer files for Mass Storage\n"
@ -47,6 +47,7 @@ static const char *help_string =
" --fxlink-log[=FILE] -i: Append fxlink text messages to FILE. Without\n"
" argument, a unique name is generated.\n"
" -r, --repeat -i: Reconnect if the calc disconnects (implies -w)\n"
" --folder=FOLDER -s: Select destination folder for files\n"
"\n"
"Device filters:\n"
" A device filter narrows down what devices we list or connect to by\n"
@ -67,6 +68,7 @@ int main(int argc, char **argv)
delay_t delay = delay_seconds(0);
struct fxlink_filter *filter = NULL;
bool repeat = false;
char *outfolder = NULL;
options.log_file = NULL;
options.verbose = false;
@ -77,7 +79,7 @@ int main(int argc, char **argv)
// Command-line argument parsing
//---
enum { LIBUSB_LOG=1, LOG_TO_FILE=2 };
enum { LIBUSB_LOG=1, LOG_TO_FILE=2, OUT_FOLDER=3 };
const struct option longs[] = {
{ "help", no_argument, NULL, 'h' },
{ "list", no_argument, NULL, 'l' },
@ -90,6 +92,7 @@ int main(int argc, char **argv)
{ "fxlink-log", optional_argument, NULL, LOG_TO_FILE },
{ "repeat", no_argument, NULL, 'r' },
{ "verbose", no_argument, NULL, 'v' },
{ "folder", required_argument, NULL, OUT_FOLDER },
/* Deprecated options ignored for compatibility: */
{ "quiet", no_argument, NULL, 'q' },
{ "unmount", no_argument, NULL, 'u' },
@ -165,6 +168,9 @@ int main(int argc, char **argv)
case 'f':
filter = fxlink_filter_parse(optarg);
break;
case OUT_FOLDER:
outfolder = strdup(optarg);
break;
case '?':
error = 1;
}
@ -219,7 +225,7 @@ int main(int argc, char **argv)
}
else if(mode == 's') {
#ifndef FXLINK_DISABLE_UDISKS2
rc = main_send(filter, &delay, argv + optind);
rc = main_send(filter, &delay, argv + optind, outfolder);
#else
rc = elog("this fxlink was built without UDisks2; -s is disabled");
#endif

View File

@ -44,11 +44,13 @@ static void handle_new_message(struct fxlink_device *fdev,
if(options.verbose)
printf("------------------\n");
fwrite(str, 1, msg->size, stdout);
#if 0
if(str[msg->size - 1] != '\n') {
if(!options.verbose)
printf("\e[30;47m%%\e[0m");
printf("\n");
}
#endif
if(options.verbose) {
printf("------------------\n");
}

View File

@ -16,7 +16,8 @@
#include <sys/types.h>
#include <sys/wait.h>
int main_send(struct fxlink_filter *filter, delay_t *delay, char **files)
int main_send(struct fxlink_filter *filter, delay_t *delay, char **files,
char *outfolder)
{
fxlink_filter_clean_udisks2(filter);
GError *error = NULL;
@ -53,10 +54,14 @@ int main_send(struct fxlink_filter *filter, delay_t *delay, char **files)
printf("Mounted %s to %s.\n", dev, folder);
}
else {
folder = strdup(mount_points[0]);
folder = mount_points[0];
printf("Already mounted at %s.\n", folder);
}
gchar *outpath = folder;
if(outfolder)
asprintf(&outpath, "%s/%s/", folder, outfolder);
/* Copy files with external cp(1) */
int file_count = 0;
while(files[file_count]) file_count++;
@ -69,7 +74,7 @@ int main_send(struct fxlink_filter *filter, delay_t *delay, char **files)
argv[0] = "cp";
for(int i = 0; files[i]; i++)
argv[i+1] = files[i];
argv[file_count+1] = folder;
argv[file_count+1] = outpath;
argv[file_count+2] = NULL;
/* Print command */
@ -97,6 +102,9 @@ int main_send(struct fxlink_filter *filter, delay_t *delay, char **files)
}
}
if(outfolder)
free(outpath);
/* Unmount the filesystem and eject the device */
GVariant *args = g_variant_new("a{sv}", NULL);
udisks_filesystem_call_unmount_sync(fs, args, NULL, &error);
@ -113,7 +121,6 @@ int main_send(struct fxlink_filter *filter, delay_t *delay, char **files)
printf("Ejected %s.\n", dev);
end:
free(folder);
if(argv) free(argv);
if(fs) g_object_unref(fs);
if(drive) g_object_unref(drive);

View File

@ -46,18 +46,31 @@ static void quit(void)
/* Generate an RGB888 surface from image data. */
static SDL_Surface *surface_for_image(uint8_t **RGB888_rows, int width,
int height)
int height, int scale)
{
/* Little endian setup for RGB */
SDL_Surface *s = SDL_CreateRGBSurface(0, width, height, 24,
SDL_Surface *s = SDL_CreateRGBSurface(0, width*scale, height*scale, 24,
0x000000ff, 0x0000ff00, 0x0000ff00, 0);
if(!s) {
elog("cannot create surface for image\n");
return NULL;
}
for(int i = 0; i < height; i++)
memcpy(s->pixels + i * s->pitch, RGB888_rows[i], width * 3);
for(int y = 0; y < height; y++) {
for(int dy = 0; dy < scale; dy++) {
uint8_t *src = RGB888_rows[y];
uint8_t *dst = s->pixels + (y*scale+dy) * s->pitch;
for(int x = 0; x < width; x++) {
for(int dx = 0; dx < scale; dx++) {
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst += 3;
}
src += 3;
}
}
}
return s;
}
@ -68,11 +81,14 @@ void fxlink_sdl2_display_raw(struct fxlink_message_image_raw const *raw)
return;
int current_w, current_h;
SDL_GetWindowSize(window, &current_w, &current_h);
if(current_w != raw->width || current_h != raw->height)
SDL_SetWindowSize(window, raw->width, raw->height);
int scale = 1;
SDL_Surface *src = surface_for_image(raw->data, raw->width, raw->height);
SDL_GetWindowSize(window, &current_w, &current_h);
if(current_w != raw->width * scale || current_h != raw->height * scale)
SDL_SetWindowSize(window, raw->width * scale, raw->height * scale);
SDL_Surface *src =
surface_for_image(raw->data, raw->width, raw->height, scale);
if(!src)
return;

379
fxlink/tui/command-util.c Normal file
View File

@ -0,0 +1,379 @@
//---------------------------------------------------------------------------//
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
// |:::| Made by Lephe' as part of the fxSDK. //
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
//---------------------------------------------------------------------------//
#include "tui.h"
#include "command-util.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>
//---
// Command parsing utilities
//---
struct fxlink_tui_cmd fxlink_tui_cmd_parse(char const *input)
{
struct fxlink_tui_cmd cmd;
cmd.argc = 0;
cmd.argv = NULL;
cmd.data = malloc(strlen(input) + 1);
if(!cmd.data)
return cmd;
char const *escapes1 = "\\nter";
char const *escapes2 = "\\\n\t\e\r";
/* Whether a new word needs to be created at the next character */
bool word_finished = true;
/* Offset into cmd.data */
int i = 0;
/* Read words eagerly, appending to cmd.data as we go */
for(int j = 0; input[j]; j++) {
int c = input[j];
/* Stop words at spaces */
if(isspace(c)) {
if(!word_finished)
cmd.data[i++] = 0;
word_finished = true;
continue;
}
/* Translate escapes */
if(c == '\\') {
char *p = strchr(escapes1, input[j+1]);
if(p) {
c = escapes2[p - escapes1];
j++;
}
}
/* Add a new word if necessary */
if(word_finished) {
cmd.argv = realloc(cmd.argv, (++cmd.argc) * sizeof *cmd.argv);
cmd.argv[cmd.argc - 1] = cmd.data + i;
word_finished = false;
}
/* Copy literals */
cmd.data[i++] = c;
}
cmd.data[i++] = 0;
cmd.argv = realloc(cmd.argv, (cmd.argc + 1) * sizeof *cmd.argv);
cmd.argv[cmd.argc] = 0;
return cmd;
}
void fxlink_tui_cmd_dump(struct fxlink_tui_cmd const *cmd)
{
print(TUI.wConsole, "[%d]", cmd->argc);
for(int i = 0; i < cmd->argc; i++) {
char const *arg = cmd->argv[i];
print(TUI.wConsole, " '%s'(%d)", arg, (int)strlen(arg));
}
print(TUI.wConsole, "\n");
}
void fxlink_tui_cmd_free(struct fxlink_tui_cmd const *cmd)
{
free(cmd->argv);
free(cmd->data);
}
static struct fxlink_device *find_connected_device(void)
{
/* TODO: Use the "selected" device */
for(int i = 0; i < TUI.devices.count; i++) {
if(TUI.devices.devices[i].status == FXLINK_FDEV_STATUS_CONNECTED)
return &TUI.devices.devices[i];
}
return NULL;
}
bool fxlink_tui_parse_args(int argc, char const **argv, char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
int i = 0;
char const *spec = fmt;
bool got_variadic = false;
for(; *spec; i++, spec++) {
/* Implicit/silent specifiers */
if(*spec == 'd') {
struct fxlink_device **ptr = va_arg(args, struct fxlink_device **);
*ptr = find_connected_device();
if(!*ptr) {
fprint(TUI.wConsole, FMT_RED, "error: ");
print(TUI.wConsole, "no device connected\n");
goto failure;
}
/* Bad */
i--;
continue;
}
/* No specifier that consumes stuff allowed after '*' */
if(got_variadic) {
fprint(TUI.wConsole, FMT_RED, "error: ");
print(TUI.wConsole, "got specifiers '%s' after '*'\n", spec);
goto failure;
}
/* Specifiers allowed even when there is no argument left */
if(*spec == '*') {
char const ***ptr = va_arg(args, char const ***);
*ptr = argv + i;
got_variadic = true;
continue;
}
/* Argument required beyond this point */
if(i >= argc && *spec != '*') {
fprint(TUI.wConsole, FMT_RED, "error: ");
print(TUI.wConsole, "too few arguments\n");
goto failure;
}
/* Standard specifiers */
if(*spec == 's') {
char const **ptr = va_arg(args, char const **);
*ptr = argv[i];
}
else if(*spec == 'i') {
int *ptr = va_arg(args, int *);
char *endptr;
long l = strtol(argv[i], &endptr, 0);
if(*endptr) {
fprint(TUI.wConsole, FMT_RED, "error: ");
print(TUI.wConsole, "not a valid integer: '%s'\n", argv[i]);
goto failure;
}
*ptr = l;
}
}
va_end(args);
return true;
failure:
va_end(args);
return false;
}
//---
// Command tree
//---
struct node {
/* Command or subtree name */
char *name;
/* true if tree node, false if raw command */
bool is_tree;
union {
struct node *children; /* is_subtree = true */
int (*func)(int argc, char const **argv); /* is_subtree = false */
};
/* Next sibling */
struct node *next;
};
static struct node *node_mkcmd(char const *name, int (*func)())
{
assert(name);
struct node *cmd = calloc(1, sizeof *cmd);
cmd->name = strdup(name);
cmd->func = func;
return cmd;
}
static struct node *node_mktree(char const *name)
{
assert(name);
struct node *tree = calloc(1, sizeof *tree);
tree->is_tree = true;
tree->name = strdup(name);
return tree;
}
static void node_free(struct node *node);
static void node_free_chain(struct node *node)
{
struct node *next;
while(node) {
next = node->next;
node_free(node);
node = next;
}
}
static void node_free(struct node *node)
{
free(node->name);
if(node->is_tree) {
node_free_chain(node->children);
free(node);
}
}
static void node_tree_add(struct node *tree, struct node *node)
{
assert(tree->is_tree);
node->next = tree->children;
tree->children = node;
}
static struct node *node_tree_get(struct node const *tree, char const *name)
{
assert(tree->is_tree);
for(struct node *n = tree->children; n; n = n->next) {
if(!strcmp(n->name, name))
return n;
}
return NULL;
}
static struct node *node_tree_get_or_make_subtree(struct node *tree,
char const *name)
{
assert(tree->is_tree);
struct node *n = node_tree_get(tree, name);
if(n)
return n;
n = node_mktree(name);
node_tree_add(tree, n);
return n;
}
static struct node *node_tree_get_path(struct node *tree, char const **path,
int *path_end_index)
{
assert(tree->is_tree);
struct node *n = node_tree_get(tree, path[0]);
if(!n)
return NULL;
(*path_end_index)++;
if(!n->is_tree)
return n;
if(!path[1]) {
fprint(TUI.wConsole, FMT_RED, "error: ");
print(TUI.wConsole, "'%s' takes a sub-command argument\n", path[0]);
return NULL;
}
return node_tree_get_path(n, path+1, path_end_index);
}
static void node_insert_command(struct node *tree, char const **path,
int (*func)(), int i)
{
assert(tree->is_tree);
if(!path[i]) {
fprintf(stderr, "error: cannot register empty command!\n");
return;
}
else if(!path[i+1]) {
struct node *cmd = node_tree_get(tree, path[i]);
if(cmd) {
fprintf(stderr, "error: '%s' already registred!\n", path[i]);
return;
}
node_tree_add(tree, node_mkcmd(path[i], func));
}
else {
struct node *subtree = node_tree_get_or_make_subtree(tree, path[i]);
if(!subtree->is_tree) {
fprintf(stderr, "error: '%s' is not a category!\n", path[i]);
return;
}
return node_insert_command(subtree, path, func, i+1);
}
}
static void node_dump(struct node const *node, int indent)
{
print(TUI.wConsole, "%*s", 2*indent, "");
if(node->is_tree) {
print(TUI.wConsole, "%s\n", node->name);
struct node *child = node->children;
while(child) {
node_dump(child, indent+1);
child = child->next;
}
}
else {
print(TUI.wConsole, "%s: %p\n", node->name, node->func);
}
}
static struct node *cmdtree = NULL;
void fxlink_tui_register_cmd(char const *name,
int (*func)(int argc, char const **argv))
{
int i = 0;
while(name[i] && (isalpha(name[i]) || strchr("?/-_ ", name[i])))
i++;
if(name[i] != 0) {
fprintf(stderr, "error: invalid command path '%s'\n", name);
return;
}
if(!cmdtree)
cmdtree = node_mktree("(root)");
/* Parse as a command because why not */
struct fxlink_tui_cmd path = fxlink_tui_cmd_parse(name);
node_insert_command(cmdtree, path.argv, func, 0);
fxlink_tui_cmd_free(&path);
}
__attribute__((destructor))
static void free_command_tree(void)
{
node_free(cmdtree);
cmdtree = NULL;
}
void TUI_execute_command(char const *command)
{
struct fxlink_tui_cmd cmd = fxlink_tui_cmd_parse(command);
if(cmd.argc < 1)
goto end;
int args_index = 0;
struct node *node = node_tree_get_path(cmdtree, cmd.argv, &args_index);
if(node) {
node->func(cmd.argc - args_index, cmd.argv + args_index);
/* ignore return code? */
}
else {
fprint(TUI.wConsole, FMT_RED, "error: ");
print(TUI.wConsole, "unrecognized command: ");
fxlink_tui_cmd_dump(&cmd);
}
end:
fxlink_tui_cmd_free(&cmd);
}
FXLINK_COMMAND("?cmdtree")
{
node_dump(cmdtree, 0);
return 0;
}

157
fxlink/tui/command-util.h Normal file
View File

@ -0,0 +1,157 @@
//---------------------------------------------------------------------------//
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
// |:::| Made by Lephe' as part of the fxSDK. //
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
//---------------------------------------------------------------------------//
// fxlink.tui.command-util: Preprocessor black magic for command definition
//
// This header provides the following method for declaring TUI commands that
// are automatically registered at startup, and are invoked with arguments
// pre-parsed:
//
// FXLINK_COMMAND("<NAME>", <TYPE>(<NAME>), <TYPE>(<NAME>), ...) {
// /* normal code... */
// return <STATUS>;
// }
//
// The command name is a string. It can have multiple space-separated words as
// in "gintctl test", in which case it is matched against argv[0], argv[1], etc
// and the prefix ("gintctl") is automatically made into a sub-category command
// with relevant error messages.
//
// Each argument has a type and a name, as in INT(x). The type carries
// information on the parsing method, the acceptable range, and of course the
// actual runtime type of the argument. Available types are:
//
// Name Runtime type Meaning and range
// --------------------------------------------------------------------------
// INT int Any integer
// STRING char const * Any string argument from argv[]
// VARIADIC char const ** End of the argv array (NULL-terminated)
// --------------------------------------------------------------------------
// DEVICE struct fxlink_device * Selected device (implicit; never NULL)
// --------------------------------------------------------------------------
//
// The function returns a status code, which is an integer. The entire command
// declaration might look like:
//
// FXLINK_COMMAND("gintctl test", INT(lower_bound), INT(upper_bound)) {
// int avg = (lower_bound + upper_bound) / 2;
// return 0;
// }
//
// I considered doing the entire thing in C++, but absolute preprocessor abuse
// is fun once in a while.
//---
#include <fxlink/defs.h>
//---
// Shell-like command parsing (without the features)
//---
struct fxlink_tui_cmd {
int argc;
char const **argv;
char *data;
};
/* Parse a string into an argument vector */
struct fxlink_tui_cmd fxlink_tui_cmd_parse(char const *input);
/* Dump a command to TUI console for debugging */
void fxlink_tui_cmd_dump(struct fxlink_tui_cmd const *cmd);
/* Free a command */
void fxlink_tui_cmd_free(struct fxlink_tui_cmd const *cmd);
//---
// Command registration and argument scanning
//---
/* Parse a list of arguments into structured data. The format is a string of
argument specifiers, each of which can be:
s String (char *)
d Integer (int)
* Other variadic arguments (char **)
(-- will probably be expanded later.)
Returns true if parsing succeeded, false otherwise (including if arguments
are missing) after printing an error message. */
bool fxlink_tui_parse_args(int argc, char const **argv, char const *fmt, ...);
/* Register a command with the specified name and invocation function. This can
be called manually or generated (along with the parser) using the macro
FXLINK_COMMAND. */
void fxlink_tui_register_cmd(char const *name,
int (*func)(int argc, char const **argv));
/* Apply a macro to every variadic argument. _M1 is applied to the first
argument and _Mn is applied to all subsequent arguments. */
#define MAPn(_M1,_Mn,...) __VA_OPT__(MAP_1(_M1,_Mn,__VA_ARGS__))
#define MAP_1(_M1,_Mn,_X,...) _M1(_X) __VA_OPT__(MAP_2(_M1,_Mn,__VA_ARGS__))
#define MAP_2(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_3(_M1,_Mn,__VA_ARGS__))
#define MAP_3(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_4(_M1,_Mn,__VA_ARGS__))
#define MAP_4(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_5(_M1,_Mn,__VA_ARGS__))
#define MAP_5(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_6(_M1,_Mn,__VA_ARGS__))
#define MAP_6(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_7(_M1,_Mn,__VA_ARGS__))
#define MAP_7(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_8(_M1,_Mn,__VA_ARGS__))
#define MAP_8(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_MAX_8_ARGS(_))
#define MAP_MAX_8_ARGS()
/* Simpler version where the same macro is applied to all arguments */
#define MAP(_M, ...) MAPn(_M, _M, ##__VA_ARGS__)
/* Command declaration macro. Builds an invocation function and a registration
function so the command name doesn't have to be repeated. */
#define FXLINK_COMMAND(_NAME, ...) DO_COMMAND1(_NAME, __COUNTER__, __VA_ARGS__)
/* This call forces __COUNTER__ to expand */
#define DO_COMMAND1(...) DO_COMMAND(__VA_ARGS__)
#define DO_COMMAND(_NAME, _COUNTER, ...) \
static int ___command_ ## _COUNTER(); \
static int ___invoke_command_ ## _COUNTER \
(int ___argc, char const **___argv) { \
MAP(MKVAR, ##__VA_ARGS__) \
if(!fxlink_tui_parse_args(___argc, ___argv, \
"" MAP(MKFMT, ##__VA_ARGS__) \
MAP(MKPTR, ##__VA_ARGS__))) return 1; \
return ___command_ ## _COUNTER( \
MAPn(MKCALL_1, MKCALL_n, ##__VA_ARGS__)); \
} \
__attribute__((constructor)) \
static void ___declare_command_ ## _COUNTER (void) { \
fxlink_tui_register_cmd(_NAME, ___invoke_command_ ## _COUNTER); \
} \
static int ___command_ ## _COUNTER(MAPn(MKFML_1, MKFML_n, ##__VA_ARGS__))
/* Make the format string for an argument */
#define MKFMT(_TV) MKFMT_ ## _TV
#define MKFMT_INT(_X) "i"
#define MKFMT_STRING(_X) "s"
#define MKFMT_VARIADIC(_X) "*"
#define MKFMT_DEVICE(_X) "d"
/* Make the formal function parameter for an argument */
#define MKFML_1(_TV) MKFML_ ## _TV
#define MKFML_n(_TV) , MKFML_1(_TV)
#define MKFML_INT(_X) int _X
#define MKFML_STRING(_X) char const * _X
#define MKFML_VARIADIC(_X) char const ** _X
#define MKFML_DEVICE(_X) struct fxlink_device * _X
/* Create a variable */
#define MKVAR(_TV) MKFML_1(_TV);
/* Make a pointer to an argument (sadly we can't get the name directly) */
#define MKPTR(_TV) , MKPTR_ ## _TV
#define MKPTR_INT(_X) &_X
#define MKPTR_STRING(_X) &_X
#define MKPTR_VARIADIC(_X) &_X
#define MKPTR_DEVICE(_X) &_X
/* Pass a variable as a function argument */
#define MKCALL_1(_TV) MKCALL_ ## _TV
#define MKCALL_n(_TV) , MKCALL_1(_TV)
#define MKCALL_INT(_X) _X
#define MKCALL_STRING(_X) _X
#define MKCALL_VARIADIC(_X) _X
#define MKCALL_DEVICE(_X) _X

273
fxlink/tui/commands.c Normal file
View File

@ -0,0 +1,273 @@
//---------------------------------------------------------------------------//
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
// |:::| Made by Lephe' as part of the fxSDK. //
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
//---------------------------------------------------------------------------//
#include "tui.h"
#include "command-util.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>
//---
// Standard commands
//---
FXLINK_COMMAND("/echo", DEVICE(fdev), VARIADIC(argv))
{
int l = 5, j = 5;
for(int i = 0; argv[i]; i++)
l += strlen(argv[i]) + 1;
char *concat = malloc(l + 1);
strcpy(concat, "echo ");
for(int i = 0; argv[i]; i++) {
strcpy(concat + j, argv[i]);
j += strlen(argv[i]);
concat[j++] = (argv[i+1] == NULL) ? '\n' : ' ';
}
concat[j] = '\0';
fxlink_device_start_bulk_OUT(fdev,
"fxlink", "command", concat, l, true);
return 0;
}
FXLINK_COMMAND("/identify", DEVICE(fdev))
{
fxlink_device_start_bulk_OUT(fdev,
"fxlink", "command", "identify", 8, false);
return 0;
}
//---
// gintctl commands
//---
static char const *lipsum =
"When the war of the beasts brings about the world's end,\n"
"The goddess descends from the sky.\n"
"Wings of light and dark spread afar,\n"
"She guides us to bliss, her gift everlasting.\n"
"\n"
"Infinite in mystery is the gift of the goddess.\n"
"We seek it thus, and take to the sky.\n"
"Ripples form on the water's surface;\n"
"The wandering soul knows no rest.\n"
"\n"
"There is no hate, only joy,\n"
"For you are beloved by the goddess.\n"
"Hero of the dawn, healer of worlds,\n"
"Dreams of the morrow hath the shattered soul.\n"
"Pride is lost -- wings stripped away, the end is nigh.\n"
"\n"
"My friend, do you fly away now?\n"
"To a world that abhors you and I?\n"
"All that awaits you is a somber morrow\n"
"No matter where the winds may blow.\n"
"My friend, your desire\n"
"Is the bringer of life, the gift of the goddess.\n"
"Even if the morrow is barren of promises,\n"
"Nothing shall forestall my return.\n"
"\n"
"My friend, the fates are cruel.\n"
"There are no dreams, no honor remains.\n"
"The arrow has left the bow of the goddess.\n"
"My soul, corrupted by vengeance,\n"
"Hath endured torment to find the end of the journey\n"
"In my own salvation and your eternal slumber.\n"
"Legend shall speak of sacrifice at world's end\n"
"The wind sails over the water's surface, quietly, but surely.\n"
"\n"
"Even if the morrow is barren of promises,\n"
"Nothing shall forestall my return.\n"
"To become the dew that quenches the lands,\n"
"To spare the sands, the seas, the skies,\n"
"I offer thee this silent sacrifice.\n";
FXLINK_COMMAND("gintctl echo-bounds", DEVICE(fdev), INT(count))
{
if(count < 0 || count > 8192) {
fprint(TUI.wConsole, FMT_RED, "error: ");
print(TUI.wConsole, "count should be 0..8192 (not %d)\n", count);
return 1;
}
uint32_t *data = malloc(count * 4);
for(int i = 0; i < count; i++)
data[i] = i;
fxlink_device_start_bulk_OUT(fdev,
"gintctl", "echo-bounds", data, count * 4, true);
return 0;
}
FXLINK_COMMAND("gintctl garbage", DEVICE(fdev), INT(count))
{
if(count < 0 || count > 8192) {
fprint(TUI.wConsole, FMT_RED, "error: ");
print(TUI.wConsole, "count should be 0..8192 (not %d)\n", count);
return 1;
}
uint32_t *data = malloc(count * 4);
for(int i = 0; i < count; i++)
data[i] = i + 0xdead0000;
fxlink_device_start_bulk_OUT(fdev,
"gintctl", "garbage", data, count * 4, true);
return 0;
}
static void status(bool b, char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
fprint(TUI.wConsole, b ? FMT_GREEN : FMT_RED, b ? "<PASSED> ":"<FAILED> ");
vw_printw(TUI.wConsole, fmt, args);
va_end(args);
}
static void unit_echo(struct fxlink_device *fdev, char const *str,
char const *description)
{
char *echo = malloc(5 + strlen(str) + 1);
strcpy(echo, "echo ");
strcat(echo, str);
fxlink_device_start_bulk_OUT(fdev,
"fxlink", "command", echo, strlen(echo), true);
struct fxlink_message *msg = NULL;
while(TUI_wait_message(fdev, "fxlink", "text", &msg)) {
bool success =
msg->size == strlen(str)
&& !strncmp(msg->data, str, msg->size);
if(description)
status(success, "%s\n", description);
else
status(success, "echo of '%s': '%.*s' (%d)\n", str, msg->size,
(char *)msg->data, msg->size);
}
}
static void unit_echo_bounds(struct fxlink_device *fdev, int count)
{
char reference[256];
sprintf(reference, "first=%08x last=%08x total=%d B\n",
0, count-1, 4*count);
uint32_t *data = malloc(count * 4);
for(int i = 0; i < count; i++)
data[i] = i;
fxlink_device_start_bulk_OUT(fdev,
"gintctl", "echo-bounds", data, count * 4, true);
struct fxlink_message *msg = NULL;
while(TUI_wait_message(fdev, "fxlink", "text", &msg)) {
bool success =
msg->size == strlen(reference)
&& !strncmp(msg->data, reference, msg->size);
status(success, "echo bounds %d B\n", count * 4);
}
}
static void unit_read_unaligned(struct fxlink_device *fdev, char const *str,
int kind)
{
char *payload = malloc(strlen(str) + 2);
sprintf(payload, "%c%s", kind, str);
fxlink_device_start_bulk_OUT(fdev,
"gintctl", "read-unaligned", payload, strlen(payload), true);
struct fxlink_message *msg = NULL;
while(TUI_wait_message(fdev, "fxlink", "text", &msg)) {
bool success =
msg->size == strlen(str)
&& !strncmp(msg->data, str, msg->size);
if(strlen(str) < 20)
status(success, "unaligned echo type '%c' of '%s'\n", kind, str);
else
status(success, "unaligned echo type '%c' of %d-byte string\n",
kind, strlen(str));
}
}
static void test_read_basic(struct fxlink_device *fdev)
{
unit_echo(fdev, "123", NULL);
unit_echo(fdev, "1234", NULL);
unit_echo(fdev, "12345", NULL);
unit_echo(fdev, "123456", NULL);
unit_echo(fdev, lipsum, "echo of better lorem ipsum");
}
static void test_read_buffers(struct fxlink_device *fdev)
{
/* 128 and 384 bytes -> less than a packet */
unit_echo_bounds(fdev, 32);
unit_echo_bounds(fdev, 96);
/* 512 bytes -> exactly one packet */
unit_echo_bounds(fdev, 128);
unit_echo_bounds(fdev, 128);
unit_echo_bounds(fdev, 128);
/* 516 and 768 -> one packet and a short one */
unit_echo_bounds(fdev, 129);
unit_echo_bounds(fdev, 192);
/* 1024 bytes -> exactly two packets */
unit_echo_bounds(fdev, 256);
/* 2044 bytes -> just shy of a full buffer */
unit_echo_bounds(fdev, 511);
/* 2048 bytes -> a full buffer */
unit_echo_bounds(fdev, 512);
unit_echo_bounds(fdev, 512);
/* 2300 bytes -> more than a full buffer */
unit_echo_bounds(fdev, 575);
/* 6000 bytes -> non-integral number of full buffers but more than 2 */
unit_echo_bounds(fdev, 1500);
/* 8192 bytes -> "large" amount of full buffers */
unit_echo_bounds(fdev, 2048);
}
static void test_read_unaligned(struct fxlink_device *fdev)
{
char const *alpha = "aBcDeFgHiJkLmNoPqR";
for(int i = 1; i <= 9; i++)
unit_read_unaligned(fdev, alpha, '0' + i);
unit_read_unaligned(fdev, alpha, 'i');
unit_read_unaligned(fdev, alpha, 'r');
for(int i = 1; i <= 9; i++)
unit_read_unaligned(fdev, lipsum, '0' + i);
unit_read_unaligned(fdev, lipsum, 'i');
unit_read_unaligned(fdev, lipsum, 'r');
}
FXLINK_COMMAND("gintctl test read-basic", DEVICE(fdev))
{
test_read_basic(fdev);
return 0;
}
FXLINK_COMMAND("gintctl test read-buffers", DEVICE(fdev))
{
test_read_buffers(fdev);
return 0;
}
FXLINK_COMMAND("gintctl test read-unaligned", DEVICE(fdev))
{
test_read_unaligned(fdev);
return 0;
}
FXLINK_COMMAND("gintctl test all", DEVICE(fdev))
{
test_read_basic(fdev);
test_read_buffers(fdev);
test_read_unaligned(fdev);
return 0;
}

View File

@ -5,38 +5,36 @@
//---------------------------------------------------------------------------//
#include "../fxlink.h"
#include <fxlink/tui/layout.h>
#include <fxlink/tui/render.h>
#include <fxlink/tui/input.h>
#include <fxlink/devices.h>
#include <fxlink/logging.h>
#include "tui.h"
#include <fxlink/tooling/libpng.h>
#include <fxlink/tooling/sdl2.h>
#include <libusb.h>
#include <ncurses.h>
#include <poll.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
static struct TUIData {
/* SIGWINCH flag */
bool resize_needed;
/* ncurses window panels */
WINDOW *wStatus;
WINDOW *wLogs;
WINDOW *wTransfers;
WINDOW *wTextOutput;
WINDOW *wConsole;
/* Root box */
struct fxlink_TUI_box *bRoot;
/* Application data */
struct fxlink_pollfds polled_fds;
struct fxlink_device_list devices;
/*
Plan for the TUI command set.
} TUI = { 0 };
fxlink commands:
select Select the calculator we're talking to
remote-control Enable/disable/setup remote calculator control
Protocol commands (executed by the calculator):
/identify Send calculator identification information
/echo Echo a message
/screenshot Take a screenshot
/video Enable/disable video capture
Application-specific commands:
gintctl read-long Transmission tests (long messages)
gintctl read-alignment Pipe reading alignment
gintctl bench USB transfer speed benchmark
*/
struct TUIData TUI = { 0 };
//---
// TUI management and rendering
@ -402,11 +400,130 @@ static void handle_fxlink_log(int display_fmt, char const *str)
wattroff(TUI.wLogs, attr);
}
bool TUI_core_update(bool allow_console, bool auto_refresh, bool *has_command)
{
struct timeval zero_tv = { 0 };
struct timeval usb_timeout;
struct pollfd stdinfd = { .fd = STDIN_FILENO, .events = POLLIN };
int rc = libusb_get_next_timeout(TUI.ctx, &usb_timeout);
int timeout = -1;
if(rc > 0)
timeout = usb_timeout.tv_sec * 1000 + usb_timeout.tv_usec / 1000;
bool timeout_is_libusb = true;
/* Time out at least every 100 ms so we can handle SDL events */
if(timeout < 0 || timeout > 100) {
timeout = 100;
timeout_is_libusb = false;
}
if(has_command)
*has_command = false;
rc = fxlink_multipoll(timeout,
&stdinfd, 1, TUI.polled_fds.fds, TUI.polled_fds.count, NULL);
if(rc < 0 && errno != EINTR) {
elog("poll: %s\n", strerror(errno));
return false;
}
if(rc < 0 && errno == EINTR)
return false;
/* Handle SIGWINCH */
if(TUI.resize_needed) {
endwin();
refresh();
TUI_setup_windows();
TUI.resize_needed = false;
TUI_render_all(true);
TUI_refresh_all(true);
return false;
}
/* Determine which even source was activated */
bool stdin_activity = (stdinfd.revents & POLLIN) != 0;
bool usb_activity = false;
for(int i = 0; i < TUI.polled_fds.count; i++)
usb_activity |= (TUI.polled_fds.fds[i].revents != 0);
/* Determine what to do. We update the console on stdin activity. We
update libusb on USB activity or appropriate timeout. We update SDL
events on any timeout. */
bool update_console = stdin_activity;
bool update_usb = usb_activity || (rc == 0 && timeout_is_libusb);
bool update_sdl = (rc == 0);
if(allow_console && update_console) {
bool finished = fxlink_TUI_input_getch(&TUI.input, TUI.wLogs);
TUI_refresh_console();
if(has_command)
*has_command = finished;
}
if(update_usb) {
libusb_handle_events_timeout(TUI.ctx, &zero_tv);
fxlink_device_list_refresh(&TUI.devices);
for(int i = 0; i < TUI.devices.count; i++) {
struct fxlink_device *fdev = &TUI.devices.devices[i];
/* Check for devices ready to connect to */
if(fxlink_device_ready_to_connect(fdev)
&& fxlink_device_has_fxlink_interface(fdev)) {
if(fxlink_device_claim_fxlink(fdev))
fxlink_device_start_bulk_IN(fdev);
}
}
}
if(update_sdl) {
fxlink_sdl2_handle_events();
}
bool refresh = update_console || update_usb;
if(auto_refresh) {
TUI_render_all(false);
TUI_refresh_all(false);
}
return refresh;
}
bool TUI_wait_message(struct fxlink_device *fdev,
char const *application, char const *type, struct fxlink_message **msg_ptr)
{
if(*msg_ptr) {
fxlink_message_free(*msg_ptr, true);
fxlink_device_start_bulk_IN(fdev);
return false;
}
while(1) {
TUI_core_update(false, true, NULL);
/* Check for new messages */
struct fxlink_message *msg = fxlink_device_finish_bulk_IN(fdev);
if(msg) {
if(fxlink_message_is_apptype(msg, application, type)) {
*msg_ptr = msg;
return true;
}
else {
fxlink_interactive_handle_message(msg);
fxlink_message_free(msg, true);
fxlink_device_start_bulk_IN(fdev);
}
}
}
}
int main_tui_interactive(libusb_context *ctx)
{
if(!TUI_setup())
return elog("error: failed to setup ncurses TUI o(x_x)o\n");
TUI.ctx = ctx;
/* Redirect fxlink logs to the logging window in the TUI */
fxlink_log_set_handler(handle_fxlink_log);
/* Set up hotplug notification */
@ -414,10 +531,6 @@ int main_tui_interactive(libusb_context *ctx)
/* Set up file descriptor tracking */
fxlink_pollfds_track(&TUI.polled_fds, ctx);
struct timeval zero_tv = { 0 };
struct timeval usb_timeout;
struct pollfd stdinfd = { .fd = STDIN_FILENO, .events = POLLIN };
/* Initial render */
print(TUI.wConsole, "fxlink version %s (libusb/TUI interactive mode)\n",
FXLINK_VERSION);
@ -427,117 +540,45 @@ int main_tui_interactive(libusb_context *ctx)
TUI_render_all(true);
TUI_refresh_all(true);
struct fxlink_TUI_input input;
fxlink_TUI_input_init(&input, TUI.wConsole, 16);
fxlink_TUI_input_init(&TUI.input, TUI.wConsole, 16);
while(1) {
int rc = libusb_get_next_timeout(ctx, &usb_timeout);
int timeout = -1;
if(rc > 0)
timeout = usb_timeout.tv_sec * 1000 + usb_timeout.tv_usec / 1000;
bool timeout_is_libusb = true;
/* Time out at least every 100 ms so we can handle SDL events */
if(timeout < 0 || timeout > 100) {
timeout = 100;
timeout_is_libusb = false;
}
bool has_command;
bool activity = TUI_core_update(true, false, &has_command);
rc = fxlink_multipoll(timeout,
&stdinfd, 1, TUI.polled_fds.fds, TUI.polled_fds.count, NULL);
if(rc < 0 && errno != EINTR)
elog("poll: %s\n", strerror(errno));
/* Handle SIGWINCH */
if(TUI.resize_needed) {
endwin();
refresh();
TUI_setup_windows();
TUI.resize_needed = false;
TUI_render_all(true);
TUI_refresh_all(true);
continue;
}
/* Determine which even source was activated */
bool stdin_activity = (stdinfd.revents & POLLIN) != 0;
bool usb_activity = false;
for(int i = 0; i < TUI.polled_fds.count; i++)
usb_activity |= (TUI.polled_fds.fds[i].revents != 0);
/* Determine what to do. We update the console on stdin activity. We
update libusb on USB activity or appropriate timeout. We update SDL
events on any timeout. */
bool update_console = stdin_activity;
bool update_usb = usb_activity || (rc == 0 && timeout_is_libusb);
bool update_sdl = (rc == 0);
if(update_console) {
bool finished = fxlink_TUI_input_getch(&input, TUI.wLogs);
TUI_refresh_console();
if(finished) {
char *command = input.data;
if(command[0] != 0)
log_("command: '%s'\n", command);
if(!strcmp(command, "q"))
break;
if(!strcmp(command, "test")) {
/* Find a device */
struct fxlink_device *fdev = NULL;
for(int i = 0; i < TUI.devices.count; i++) {
fdev = &TUI.devices.devices[i];
if(fdev->status == FXLINK_FDEV_STATUS_CONNECTED)
break;
else fdev = NULL;
}
if(fdev) {
print(TUI.wConsole, "using device %s (%s)\n",
fxlink_device_id(fdev), fdev->calc->serial);
fxlink_device_start_bulk_OUT(fdev,
"fxlink", "command", "test", 4);
}
else {
print(TUI.wConsole, "no connected device!\n");
}
}
fxlink_TUI_input_free(&input);
print(TUI.wConsole, "%s", prompt);
fxlink_TUI_input_init(&input, TUI.wConsole, 16);
TUI_refresh_console();
/* Check for devices with finished transfers */
for(int i = 0; i < TUI.devices.count; i++) {
struct fxlink_device *fdev = &TUI.devices.devices[i];
struct fxlink_message *msg = fxlink_device_finish_bulk_IN(fdev);
if(msg) {
fxlink_interactive_handle_message(msg);
fxlink_message_free(msg, true);
fxlink_device_start_bulk_IN(fdev);
}
}
if(update_usb) {
libusb_handle_events_timeout(ctx, &zero_tv);
fxlink_device_list_refresh(&TUI.devices);
/* Check for console commands */
if(has_command) {
char *command = TUI.input.data;
for(int i = 0; i < TUI.devices.count; i++) {
struct fxlink_device *fdev = &TUI.devices.devices[i];
if(command[0] != 0)
log_("command: '%s'\n", command);
if(!strcmp(command, ""))
{}
else if(!strcmp(command, "q") || !strcmp(command, "quit"))
break;
else
TUI_execute_command(command);
/* Check for devices ready to connect to */
if(fdev->status == FXLINK_FDEV_STATUS_IDLE && fdev->comm
&& fdev->comm->ep_bulk_IN != 0xff) {
if(fxlink_device_claim_fxlink(fdev))
fxlink_device_start_bulk_IN(fdev);
}
/* Check for devices with finished transfers */
struct fxlink_message *msg=fxlink_device_finish_bulk_IN(fdev);
if(msg) {
fxlink_interactive_handle_message(msg);
fxlink_message_free(msg, true);
fxlink_device_start_bulk_IN(fdev);
}
}
fxlink_TUI_input_free(&TUI.input);
print(TUI.wConsole, "%s", prompt);
fxlink_TUI_input_init(&TUI.input, TUI.wConsole, 16);
}
if(activity) {
TUI_render_all(false);
TUI_refresh_all(false);
}
if(update_sdl) {
fxlink_sdl2_handle_events();
}
}
while(fxlink_device_list_interrupt(&TUI.devices))

68
fxlink/tui/tui.h Normal file
View File

@ -0,0 +1,68 @@
//---------------------------------------------------------------------------//
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
// |:::| Made by Lephe' as part of the fxSDK. //
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
//---------------------------------------------------------------------------//
// fxlink.tui.tui: Global data and main functions for the interactive TUI
#pragma once
#include <fxlink/tui/layout.h>
#include <fxlink/tui/render.h>
#include <fxlink/tui/input.h>
#include <fxlink/devices.h>
#include <fxlink/logging.h>
#include <libusb.h>
#include <ncurses.h>
struct TUIData {
/* libusb context */
libusb_context *ctx;
/* SIGWINCH flag */
bool resize_needed;
/* ncurses window panels */
WINDOW *wStatus;
WINDOW *wLogs;
WINDOW *wTransfers;
WINDOW *wTextOutput;
WINDOW *wConsole;
/* Root box */
struct fxlink_TUI_box *bRoot;
/* Application data */
struct fxlink_pollfds polled_fds;
struct fxlink_device_list devices;
/* Main console input */
struct fxlink_TUI_input input;
};
extern struct TUIData TUI;
/* Run a single asynchronous update. This polls a bunch of file descriptors
along with a short timeout (< 1s). Returns true if there is any activity.
If `allow_console` is true, console events are handled; otherwise they are
ignored so they can be collected by the main loop. Setting this parameter to
false is useful when waiting for messages in TUI commands. `has_command` is
set to whether there is a new command to be run at the console (only ever
true when `allow_console` is true).
If `auto_refresh` is true, this function will refresh the TUI upon relevant
activity. */
bool TUI_core_update(bool allow_console, bool auto_refresh, bool *has_command);
/* Run the specified TUI command. */
void TUI_execute_command(char const *command);
/* Wait for a message of a particular type to arrive, and then clean it up.
This function should be called in a loop, eg.
struct fxlink_message *msg = NULL;
while(TUI_wait_message(fdev, "fxlink", "text", &msg)) {
// Handle msg...
}
TUI_wait_message() will only return true once, however it will use the next
call to free the message, restart communication on the device, and reset
`msg` to NULL. */
bool TUI_wait_message(struct fxlink_device *fdev,
char const *application, char const *type, struct fxlink_message **msg);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

View File

@ -21,6 +21,10 @@ add_link_options(-nostdlib -Wl,--no-warn-rwx-segments)
link_libraries(-lgcc)
add_compile_definitions(TARGET_FXCG50)
if(CMAKE_BUILD_TYPE STREQUAL FastLoad)
add_compile_definitions(TARGET_FXCG50_FASTLOAD)
endif()
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

View File

@ -0,0 +1,57 @@
# Locate the library file and includes
find_library(
LIBFXLINK_PATH "fxlink"
HINTS "$ENV{HOME}/.local/lib" "$ENV{FXSDK_PATH}/lib"
)
if(LIBFXLINK_PATH STREQUAL "LIBFXLINK_PATH-NOTFOUND")
message(SEND_ERROR
"Could not find libfxlink.a!\n"
"You can specify the install path with the environment variable "
"FXSDK_PATH, such as FXSDK_PATH=$HOME/.local")
else()
get_filename_component(LIBFXLINK_PATH "${LIBFXLINK_PATH}/../.." ABSOLUTE)
set(LIBFXLINK_LIB "${LIBFXLINK_PATH}/lib/libfxlink.a")
set(LIBFXLINK_INCLUDE "${LIBFXLINK_PATH}/include")
message("(libfxlink) Found libfxlink at: ${LIBFXLINK_LIB}")
message("(libfxlink) Will take includes from: ${LIBFXLINK_INCLUDE}")
endif()
# Find library version
if(NOT EXISTS "${LIBFXLINK_INCLUDE}/fxlink/config.h")
message(SEND_ERROR
"No <fxlink/config.h> exists at ${LIBFXLINK_INCLUDE}/fxlink/config.h\n"
"Is libfxlink installed alongside the headers?")
endif()
execute_process(
COMMAND sed "s/#define FXLINK_VERSION \"\\([^\"]\\{1,\\}\\)\"/\\1/p; d"
"${LIBFXLINK_INCLUDE}/fxlink/config.h"
OUTPUT_VARIABLE LIBFXLINK_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE)
message("(libfxlink) Library version found in header: ${LIBFXLINK_VERSION}")
# Handle find_package() arguments and find dependencies
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LibFxlink
REQUIRED_VARS LIBFXLINK_LIB LIBFXLINK_INCLUDE
VERSION_VAR LIBFXLINK_VERSION)
find_package(PkgConfig REQUIRED)
pkg_check_modules(libusb REQUIRED libusb-1.0 IMPORTED_TARGET)
# Generate targets
if(LibFxlink_FOUND)
if(NOT TARGET LibFxlink::LibFxlink)
add_library(LibFxlink::LibFxlink UNKNOWN IMPORTED)
endif()
set_target_properties(LibFxlink::LibFxlink PROPERTIES
IMPORTED_LOCATION "${LIBFXLINK_LIB}"
INTERFACE_INCLUDE_DIRECTORIES "${LIBFXLINK_INCLUDE}")
target_link_libraries(LibFxlink::LibFxlink INTERFACE PkgConfig::libusb)
endif()

View File

@ -142,10 +142,10 @@ static bool find_fxlink_endpoints(struct fxlink_device *fdev, bool quiet)
int type = ed->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK;
if(dir == LIBUSB_ENDPOINT_OUT
&& type == LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK)
&& type == LIBUSB_TRANSFER_TYPE_BULK)
comm->ep_bulk_OUT = ed->bEndpointAddress;
if(dir == LIBUSB_ENDPOINT_IN
&& type == LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK)
&& type == LIBUSB_TRANSFER_TYPE_BULK)
comm->ep_bulk_IN = ed->bEndpointAddress;
}
@ -280,10 +280,23 @@ void fxlink_device_analysis_2(struct fxlink_device *fdev)
fdev->status = FXLINK_FDEV_STATUS_IDLE;
}
bool fxlink_device_ready_to_connect(struct fxlink_device const *fdev)
{
bool status = (fdev->status == FXLINK_FDEV_STATUS_IDLE) ||
(fdev->status == FXLINK_FDEV_STATUS_CONNECTED);
return fdev->calc && fdev->comm && status;
}
bool fxlink_device_has_fxlink_interface(struct fxlink_device const *fdev)
{
return fdev->calc && fdev->comm && (fdev->comm->ep_bulk_IN != 0xff);
}
bool fxlink_device_claim_fxlink(struct fxlink_device *fdev)
{
/* Only connect to calculators with an fxlink interface */
if(!fdev->comm || fdev->status != FXLINK_FDEV_STATUS_IDLE)
if(!fxlink_device_ready_to_connect(fdev) ||
!fxlink_device_has_fxlink_interface(fdev) ||
fdev->comm->claimed)
return false;
/* Allocate transfer data */
@ -438,15 +451,15 @@ static void bulk_IN_callback(struct libusb_transfer *transfer)
}
}
void fxlink_device_start_bulk_IN(struct fxlink_device *fdev)
bool fxlink_device_start_bulk_IN(struct fxlink_device *fdev)
{
if(!fdev->comm || !fdev->comm->claimed || fdev->comm->tr_bulk_IN)
return;
return false;
fdev->comm->tr_bulk_IN = libusb_alloc_transfer(0);
if(!fdev->comm->tr_bulk_IN) {
elog("allocation of bulk IN transfer failed\n");
return;
return false;
}
libusb_fill_bulk_transfer(fdev->comm->tr_bulk_IN,
@ -462,11 +475,12 @@ void fxlink_device_start_bulk_IN(struct fxlink_device *fdev)
if(rc < 0) {
elog_libusb(rc, "bulk IN transfer failed to submit");
fdev->status = FXLINK_FDEV_STATUS_ERROR;
return;
return false;
}
// hlog("calculators %s", fxlink_device_id(fdev));
// log_("submitted new IN transfer (no timeout)\n");
return true;
}
struct fxlink_message *fxlink_device_finish_bulk_IN(struct fxlink_device *fdev)
@ -490,7 +504,6 @@ struct fxlink_message *fxlink_device_finish_bulk_IN(struct fxlink_device *fdev)
version_major, version_minor,
msg->application, msg->type, fxlink_size_string(msg->size));
fxlink_transfer_free(comm->ftransfer_IN);
comm->ftransfer_IN = NULL;
return msg;
}
@ -553,24 +566,26 @@ static void bulk_OUT_callback(struct libusb_transfer *transfer)
}
}
void fxlink_device_start_bulk_OUT(struct fxlink_device *fdev,
char const *app, char const *type, void const *data, int size)
bool fxlink_device_start_bulk_OUT(struct fxlink_device *fdev,
char const *app, char const *type, void const *data, int size,
bool own_data)
{
struct fxlink_comm *comm = fdev->comm;
if(!comm || !comm->claimed || comm->ftransfer_OUT)
return;
return false;
comm->ftransfer_OUT = fxlink_transfer_make_OUT(app, type, data, size);
comm->ftransfer_OUT =
fxlink_transfer_make_OUT(app, type, data, size, own_data);
if(!comm->ftransfer_OUT) {
elog("allocation of OUT transfer (protocol) failed\n");
return;
return false;
}
comm->tr_bulk_OUT = libusb_alloc_transfer(0);
if(!comm->tr_bulk_OUT) {
elog("allocation of bulk OUT transfer (libusb) failed\n");
free(comm->ftransfer_OUT);
return;
return false;
}
libusb_fill_bulk_transfer(comm->tr_bulk_OUT, fdev->dh,
@ -579,12 +594,18 @@ void fxlink_device_start_bulk_OUT(struct fxlink_device *fdev,
FXLINK_MESSAGE_HEADER_SIZE, /* Buffer size */
bulk_OUT_callback, fdev, -1); /* Callback and timeout */
/* The fxlink protocol generally doesn't rely on sizes and instead expects
zero-length packets to mark the ends of transactions */
comm->tr_bulk_OUT->flags = LIBUSB_TRANSFER_ADD_ZERO_PACKET;
int rc = libusb_submit_transfer(comm->tr_bulk_OUT);
if(rc < 0) {
elog_libusb(rc, "bulk OUT transfer failed to submit");
fdev->status = FXLINK_FDEV_STATUS_ERROR;
return;
return false;
}
return true;
}
//---

View File

@ -195,13 +195,23 @@ struct fxlink_comm {
bool cancelled_OUT;
};
/* Check whether the device is ready to have interfaces claimed. This function
only checks that the device is a calculator and could be opened; it doesn't
guarantee that claiming the interfaces will succeed. This function returns
true even after an interface has been claimed since multiple interfaces can
be claimed at the same time. */
bool fxlink_device_ready_to_connect(struct fxlink_device const *fdev);
/* Check whether the device exposes an fxlink interface. */
bool fxlink_device_has_fxlink_interface(struct fxlink_device const *fdev);
/* Claim the fxlink interface (for a device that has one). Returns false and
sets the status to ERROR on failure. */
bool fxlink_device_claim_fxlink(struct fxlink_device *fdev);
/* Start an IN transfer on the device if none is currently running, so that the
device structure is always ready to receive data from the calculator. */
void fxlink_device_start_bulk_IN(struct fxlink_device *fdev);
bool fxlink_device_start_bulk_IN(struct fxlink_device *fdev);
/* Finish an IN transfer and obtain the completed message. This function should
be checked every frame as it will return a non-NULL pointer as soon as the
@ -210,9 +220,11 @@ void fxlink_device_start_bulk_IN(struct fxlink_device *fdev);
struct fxlink_message *fxlink_device_finish_bulk_IN(
struct fxlink_device *fdev);
/* Start an OUT transfer on the device. */
void fxlink_device_start_bulk_OUT(struct fxlink_device *fdev,
char const *app, char const *type, void const *data, int size);
/* Start an OUT transfer on the device. If `own_data` is set, the transfer will
free(data) when it completes. */
bool fxlink_device_start_bulk_OUT(struct fxlink_device *fdev,
char const *app, char const *type, void const *data, int size,
bool own_data);
/* Interrupt any active transfers on the device. */
void fxlink_device_interrupt_transfers(struct fxlink_device *fdev);

View File

@ -113,6 +113,8 @@ struct fxlink_transfer {
uint8_t direction;
/* Size of data sent or received so far */
int processed_size;
/* Whether we own msg.data and should free(3) it */
bool own_data;
};
enum {
@ -130,7 +132,8 @@ enum {
struct fxlink_transfer *fxlink_transfer_make_IN(void *data, int size);
/* If the provided IN transfer is finished, extract the message and free the
transfer pointer. Otherwise, return NULL. */
transfer (do not fxlink_transfer_free(tr) later!). Returns NULL if the
transfer isn't finished yet. */
struct fxlink_message *fxlink_transfer_finish_IN(struct fxlink_transfer *tr);
/* Append data to a previously-initialized inbound transfer. */
@ -138,7 +141,7 @@ void fxlink_transfer_receive(struct fxlink_transfer *tr, void *data, int size);
/* Make an outbound transfer structure. */
struct fxlink_transfer *fxlink_transfer_make_OUT(char const *application,
char const *type, void const *data, int size);
char const *type, void const *data, int size, bool own_data);
/* Check whether a transfer is complete. */
bool fxlink_transfer_complete(struct fxlink_transfer const *tr);

View File

@ -9,7 +9,7 @@
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "fxlink.h"
#include <assert.h>
bool fxlink_message_is_apptype(struct fxlink_message const *msg,
char const *application, char const *type)
@ -236,6 +236,7 @@ struct fxlink_transfer *fxlink_transfer_make_IN(void *data, int size)
tr->msg.data = malloc(tr->msg.size);
tr->direction = FXLINK_TRANSFER_IN;
tr->processed_size = 0;
tr->own_data = true;
if(!tr->msg.data) {
elog("cannot allocate buffer for %d bytes\n", tr->msg.size);
@ -258,10 +259,8 @@ struct fxlink_message *fxlink_transfer_finish_IN(struct fxlink_transfer *tr)
return NULL;
}
if(options.verbose)
log_("extracting completed message (%d bytes)\n", tr->msg.size);
/* Shallow copy the malloc()'d data pointer */
assert(tr->own_data && "Can't shallow copy data if we don't own it!");
memcpy(msg, &tr->msg, sizeof *msg);
free(tr);
return msg;
@ -275,15 +274,12 @@ void fxlink_transfer_receive(struct fxlink_transfer *tr, void *data, int size)
size = remaining_size;
}
if(options.verbose)
log_("got %d bytes (out of %d left)\n", size, remaining_size);
memcpy(tr->msg.data + tr->processed_size, data, size);
tr->processed_size += size;
}
struct fxlink_transfer *fxlink_transfer_make_OUT(char const *application,
char const *type, void const *data, int size)
char const *type, void const *data, int size, bool own_data)
{
struct fxlink_transfer *tr = calloc(1, sizeof *tr);
if(!tr)
@ -297,6 +293,7 @@ struct fxlink_transfer *fxlink_transfer_make_OUT(char const *application,
tr->msg.data = (void *)data;
tr->direction = FXLINK_TRANSFER_OUT;
tr->processed_size = -1;
tr->own_data = own_data;
return tr;
}
@ -307,7 +304,7 @@ bool fxlink_transfer_complete(struct fxlink_transfer const *tr)
void fxlink_transfer_free(struct fxlink_transfer *tr)
{
if(tr->direction == FXLINK_TRANSFER_IN)
if(tr->own_data)
free(tr->msg.data);
free(tr);
}