forked from Lephenixnoir/fxsdk
Compare commits
28 Commits
7fcf2411bf
...
4c307af02b
Author | SHA1 | Date |
---|---|---|
Lephenixnoir | 4c307af02b | |
Lephenixnoir | 2acc439ed4 | |
Lephenixnoir | 8030d6bdc6 | |
Lephenixnoir | 4a84bfdcd4 | |
Lephenixnoir | 6e62fb7d6d | |
Lephenixnoir | 975f29a471 | |
Lephenixnoir | da79a6a0e8 | |
Lephenixnoir | ecf04cb634 | |
Lephenixnoir | 45fd52444f | |
Lephenixnoir | 9f4d17ca4f | |
Lephenixnoir | 8f50f7694a | |
Lephenixnoir | 88235041a3 | |
Lephenixnoir | be8c1f0d94 | |
Lephenixnoir | 11e3b614c2 | |
Lephenixnoir | cf3ab5d5e0 | |
Lephenixnoir | 82027e1057 | |
Lephenixnoir | 7b77fb9c0b | |
Lephenixnoir | 394d05726d | |
Lephenixnoir | 0c0eb7f4f5 | |
Lephenixnoir | 1251ca23ee | |
Lephenixnoir | 1573db3860 | |
Lephenixnoir | 065233387d | |
Lephenixnoir | 3f4aa1e750 | |
Lephenixnoir | cef9d21076 | |
Lephenixnoir | f83ea7e3d3 | |
Lephenixnoir | 4b980d949b | |
Lephenixnoir | 0a61ffc523 | |
Lephenixnoir | c7c1ec35f7 |
|
@ -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)
|
||||
|
|
|
@ -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__":
|
||||
|
|
200
fxconv/fxconv.py
200
fxconv/fxconv.py
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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, ¤t_w, ¤t_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, ¤t_w, ¤t_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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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))
|
|
@ -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.
|
@ -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)
|
||||
|
|
|
@ -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()
|
|
@ -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;
|
||||
}
|
||||
|
||||
//---
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue