Hex viewer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

hex.c 55KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031
  1. /*
  2. * hex -- hex viewer
  3. *
  4. * Copyright (c) 2016 - 2017, Přemysl Janouch <p@janouch.name>
  5. *
  6. * Permission to use, copy, modify, and/or distribute this software for any
  7. * purpose with or without fee is hereby granted.
  8. *
  9. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  10. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  12. * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
  14. * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  15. * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. *
  17. */
  18. #include "config.h"
  19. // We "need" to have an enum for attributes before including liberty.
  20. // Avoiding colours in the defaults here in order to support dumb terminals.
  21. #define ATTRIBUTE_TABLE(XX) \
  22. XX( FOOTER, "footer", -1, -1, 0 ) \
  23. XX( FOOTER_HL, "footer_hl", -1, -1, A_BOLD ) \
  24. /* Bar */ \
  25. XX( BAR, "bar", -1, -1, A_REVERSE ) \
  26. XX( BAR_HL, "bar_hl", -1, -1, A_REVERSE | A_BOLD ) \
  27. /* View */ \
  28. XX( EVEN, "even", -1, -1, 0 ) \
  29. XX( ODD, "odd", -1, -1, 0 ) \
  30. XX( SELECTION, "selection", -1, -1, A_REVERSE ) \
  31. /* Field highlights */ \
  32. XX( C1, "c1", 22, 194, 0 ) \
  33. XX( C2, "c2", 88, 224, 0 ) \
  34. XX( C3, "c3", 58, 229, 0 ) \
  35. XX( C4, "c4", 20, 189, 0 ) \
  36. /* These are for debugging only */ \
  37. XX( WARNING, "warning", 3, -1, 0 ) \
  38. XX( ERROR, "error", 1, -1, 0 )
  39. enum
  40. {
  41. #define XX(name, config, fg_, bg_, attrs_) ATTRIBUTE_ ## name,
  42. ATTRIBUTE_TABLE (XX)
  43. #undef XX
  44. ATTRIBUTE_COUNT
  45. };
  46. // User data for logger functions to enable formatted logging
  47. #define print_fatal_data ((void *) ATTRIBUTE_ERROR)
  48. #define print_error_data ((void *) ATTRIBUTE_ERROR)
  49. #define print_warning_data ((void *) ATTRIBUTE_WARNING)
  50. #define LIBERTY_WANT_POLLER
  51. #define LIBERTY_WANT_ASYNC
  52. #define LIBERTY_WANT_PROTO_HTTP
  53. #include "liberty/liberty.c"
  54. #include "liberty/liberty-tui.c"
  55. #include <locale.h>
  56. #include <termios.h>
  57. #ifndef TIOCGWINSZ
  58. #include <sys/ioctl.h>
  59. #endif // ! TIOCGWINSZ
  60. #include "termo.h"
  61. #ifdef HAVE_LUA
  62. #include <lua.h>
  63. #include <lualib.h>
  64. #include <lauxlib.h>
  65. #include <dirent.h>
  66. #endif // HAVE_LUA
  67. #define APP_TITLE PROGRAM_NAME ///< Left top corner
  68. // --- Utilities ---------------------------------------------------------------
  69. // The standard endwin/refresh sequence makes the terminal flicker
  70. static void
  71. update_curses_terminal_size (void)
  72. {
  73. #if defined (HAVE_RESIZETERM) && defined (TIOCGWINSZ)
  74. struct winsize size;
  75. if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size))
  76. {
  77. char *row = getenv ("LINES");
  78. char *col = getenv ("COLUMNS");
  79. unsigned long tmp;
  80. resizeterm (
  81. (row && xstrtoul (&tmp, row, 10)) ? tmp : size.ws_row,
  82. (col && xstrtoul (&tmp, col, 10)) ? tmp : size.ws_col);
  83. }
  84. #else // HAVE_RESIZETERM && TIOCGWINSZ
  85. endwin ();
  86. refresh ();
  87. #endif // HAVE_RESIZETERM && TIOCGWINSZ
  88. }
  89. // --- Application -------------------------------------------------------------
  90. enum
  91. {
  92. ROW_SIZE = 16, ///< How many bytes on a row
  93. };
  94. enum endianity
  95. {
  96. ENDIANITY_LE, ///< Little endian
  97. ENDIANITY_BE ///< Big endian
  98. };
  99. struct mark
  100. {
  101. int64_t offset; ///< Offset of the mark
  102. int64_t len; ///< Length of the mark
  103. size_t description; ///< Textual description string offset
  104. };
  105. // XXX: can we avoid constructing the marks_by_offset lookup array?
  106. // How much memory is it even going to consume in reality?
  107. /// This is the final result suitable for display, including unmarked areas.
  108. /// We might infer `color` from the index of this entry but then unmarked areas
  109. /// would skip a color, which is undesired.
  110. struct marks_by_offset
  111. {
  112. int64_t offset; ///< Offset in the file
  113. size_t marks; ///< Offset into "offset_entries"
  114. int color; ///< Color of the area until next offset
  115. };
  116. static struct app_context
  117. {
  118. // Event loop:
  119. struct poller poller; ///< Poller
  120. bool quitting; ///< Quit signal for the event loop
  121. bool polling; ///< The event loop is running
  122. struct poller_fd tty_event; ///< Terminal input event
  123. struct poller_fd signal_event; ///< Signal FD event
  124. #ifdef HAVE_LUA
  125. lua_State *L; ///< Lua state
  126. int ref_format; ///< Reference to "string.format"
  127. struct str_map coders; ///< Map of coders by name
  128. #endif // HAVE_LUA
  129. // Data:
  130. char *message; ///< Last logged message
  131. int message_attr; ///< Attributes for the logged message
  132. struct config config; ///< Program configuration
  133. char *filename; ///< Target filename
  134. uint8_t *data; ///< Target data
  135. int64_t data_len; ///< Length of the data
  136. int64_t data_offset; ///< Offset of the data within the file
  137. // Field marking:
  138. ARRAY (struct mark, marks) ///< Marks
  139. struct str mark_strings; ///< Storage for mark descriptions
  140. ARRAY (struct marks_by_offset, marks_by_offset)
  141. ARRAY (struct mark *, offset_entries)
  142. // View:
  143. int64_t view_top; ///< Offset of the top of the screen
  144. int64_t view_cursor; ///< Offset of the cursor
  145. bool view_skip_nibble; ///< Half-byte offset
  146. enum endianity endianity; ///< Endianity
  147. // Emulated widgets:
  148. struct poller_idle refresh_event; ///< Refresh the screen
  149. // Terminal:
  150. termo_t *tk; ///< termo handle
  151. struct poller_timer tk_timer; ///< termo timeout timer
  152. bool locale_is_utf8; ///< The locale is Unicode
  153. struct attrs attrs[ATTRIBUTE_COUNT];
  154. }
  155. g_ctx;
  156. /// Shortcut to retrieve named terminal attributes
  157. #define APP_ATTR(name) g_ctx.attrs[ATTRIBUTE_ ## name].attrs
  158. // --- Configuration -----------------------------------------------------------
  159. static struct config_schema g_config_colors[] =
  160. {
  161. #define XX(name_, config, fg_, bg_, attrs_) \
  162. { .name = config, .type = CONFIG_ITEM_STRING },
  163. ATTRIBUTE_TABLE (XX)
  164. #undef XX
  165. {}
  166. };
  167. static const char *
  168. get_config_string (struct config_item *root, const char *key)
  169. {
  170. struct config_item *item = config_item_get (root, key, NULL);
  171. hard_assert (item);
  172. if (item->type == CONFIG_ITEM_NULL)
  173. return NULL;
  174. hard_assert (config_item_type_is_string (item->type));
  175. return item->value.string.str;
  176. }
  177. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  178. static void
  179. load_config_colors (struct config_item *subtree, void *user_data)
  180. {
  181. config_schema_apply_to_object (g_config_colors, subtree, user_data);
  182. // The attributes cannot be changed dynamically right now, so it doesn't
  183. // make much sense to make use of "on_change" callbacks either.
  184. // For simplicity, we should reload the entire table on each change anyway.
  185. const char *value;
  186. #define XX(name, config, fg_, bg_, attrs_) \
  187. if ((value = get_config_string (subtree, config))) \
  188. g_ctx.attrs[ATTRIBUTE_ ## name] = attrs_decode (value);
  189. ATTRIBUTE_TABLE (XX)
  190. #undef XX
  191. }
  192. static void
  193. app_load_configuration (void)
  194. {
  195. struct config *config = &g_ctx.config;
  196. config_register_module (config, "colors", load_config_colors, NULL);
  197. // Bootstrap configuration, so that we can access schema items at all
  198. config_load (config, config_item_object ());
  199. char *filename = resolve_filename
  200. (PROGRAM_NAME ".conf", resolve_relative_config_filename);
  201. if (!filename)
  202. return;
  203. struct error *e = NULL;
  204. struct config_item *root = config_read_from_file (filename, &e);
  205. free (filename);
  206. if (e)
  207. {
  208. print_error ("error loading configuration: %s", e->message);
  209. error_free (e);
  210. exit (EXIT_FAILURE);
  211. }
  212. if (root)
  213. {
  214. config_load (&g_ctx.config, root);
  215. config_schema_call_changed (g_ctx.config.root);
  216. }
  217. }
  218. // --- Application -------------------------------------------------------------
  219. static void
  220. app_init_attributes (void)
  221. {
  222. #define XX(name, config, fg_, bg_, attrs_) \
  223. g_ctx.attrs[ATTRIBUTE_ ## name].fg = fg_; \
  224. g_ctx.attrs[ATTRIBUTE_ ## name].bg = bg_; \
  225. g_ctx.attrs[ATTRIBUTE_ ## name].attrs = attrs_;
  226. ATTRIBUTE_TABLE (XX)
  227. #undef XX
  228. }
  229. static void
  230. app_init_context (void)
  231. {
  232. poller_init (&g_ctx.poller);
  233. g_ctx.config = config_make ();
  234. ARRAY_INIT (g_ctx.marks);
  235. g_ctx.mark_strings = str_make ();
  236. ARRAY_INIT (g_ctx.marks_by_offset);
  237. ARRAY_INIT (g_ctx.offset_entries);
  238. // This is also approximately what libunistring does internally,
  239. // since the locale name is canonicalized by locale_charset().
  240. // Note that non-Unicode locales are handled pretty inefficiently.
  241. g_ctx.locale_is_utf8 = !strcasecmp_ascii (locale_charset (), "UTF-8");
  242. app_init_attributes ();
  243. }
  244. static void
  245. app_init_terminal (void)
  246. {
  247. TERMO_CHECK_VERSION;
  248. if (!(g_ctx.tk = termo_new (STDIN_FILENO, NULL, 0)))
  249. abort ();
  250. if (!initscr () || nonl () == ERR)
  251. abort ();
  252. // By default we don't use any colors so they're not required...
  253. if (start_color () == ERR
  254. || use_default_colors () == ERR
  255. || COLOR_PAIRS <= ATTRIBUTE_COUNT)
  256. return;
  257. for (int a = 0; a < ATTRIBUTE_COUNT; a++)
  258. {
  259. // ...thus we can reset back to defaults even after initializing some
  260. if (g_ctx.attrs[a].fg >= COLORS || g_ctx.attrs[a].fg < -1
  261. || g_ctx.attrs[a].bg >= COLORS || g_ctx.attrs[a].bg < -1)
  262. {
  263. // FIXME: we need a 256color default palette that fails gracefully
  264. // to something like underlined fields
  265. app_init_attributes ();
  266. return;
  267. }
  268. init_pair (a + 1, g_ctx.attrs[a].fg, g_ctx.attrs[a].bg);
  269. g_ctx.attrs[a].attrs |= COLOR_PAIR (a + 1);
  270. }
  271. }
  272. static void
  273. app_free_context (void)
  274. {
  275. config_free (&g_ctx.config);
  276. poller_free (&g_ctx.poller);
  277. free (g_ctx.marks);
  278. str_free (&g_ctx.mark_strings);
  279. free (g_ctx.marks_by_offset);
  280. free (g_ctx.offset_entries);
  281. free (g_ctx.message);
  282. free (g_ctx.filename);
  283. free (g_ctx.data);
  284. if (g_ctx.tk)
  285. termo_destroy (g_ctx.tk);
  286. }
  287. static void
  288. app_quit (void)
  289. {
  290. g_ctx.quitting = true;
  291. g_ctx.polling = false;
  292. }
  293. static bool
  294. app_is_character_in_locale (ucs4_t ch)
  295. {
  296. // Avoid the overhead joined with calling iconv() for all characters.
  297. if (g_ctx.locale_is_utf8)
  298. return true;
  299. // The library really creates a new conversion object every single time
  300. // and doesn't provide any smarter APIs. Luckily, most users use UTF-8.
  301. size_t len;
  302. char *tmp = u32_conv_to_encoding (locale_charset (), iconveh_error,
  303. &ch, 1, NULL, NULL, &len);
  304. if (!tmp)
  305. return false;
  306. free (tmp);
  307. return true;
  308. }
  309. // --- Field marking -----------------------------------------------------------
  310. /// Find the "marks_by_offset" span covering the offset (if any)
  311. static ssize_t
  312. app_find_marks (int64_t offset)
  313. {
  314. ssize_t min = 0, end = g_ctx.marks_by_offset_len;
  315. while (min < end)
  316. {
  317. ssize_t mid = min + (end - min) / 2;
  318. if (offset >= g_ctx.marks_by_offset[mid].offset)
  319. min = mid + 1;
  320. else
  321. end = mid;
  322. }
  323. return min - 1;
  324. }
  325. static struct marks_by_offset *
  326. app_marks_at_offset (int64_t offset)
  327. {
  328. ssize_t i = app_find_marks (offset);
  329. if (i < 0 || (size_t) i >= g_ctx.marks_by_offset_len)
  330. return NULL;
  331. struct marks_by_offset *marks = &g_ctx.marks_by_offset[i];
  332. if (marks->offset > offset)
  333. return NULL;
  334. return marks;
  335. }
  336. static int
  337. app_mark_cmp (const void *first, const void *second)
  338. {
  339. const struct mark *a = first, *b = second;
  340. // This ordering is pretty much arbitrary, seemed to make sense
  341. if (a->offset < b->offset) return -1;
  342. if (a->offset > b->offset) return 1;
  343. if (a->len < b->len) return 1;
  344. if (a->len > b->len) return -1;
  345. return 0;
  346. }
  347. static size_t
  348. app_store_marks (struct mark **entries, size_t len)
  349. {
  350. size_t result = g_ctx.offset_entries_len;
  351. ARRAY_RESERVE (g_ctx.offset_entries, len);
  352. memcpy (g_ctx.offset_entries + g_ctx.offset_entries_len, entries,
  353. sizeof *entries * len);
  354. g_ctx.offset_entries_len += len;
  355. return result;
  356. }
  357. /// Flattens marks into sequential non-overlapping spans suitable for search
  358. /// by offset, assigning different colors to them in the process:
  359. /// @code
  360. /// ________ _______ ___
  361. /// |________|__|_______| |___|
  362. /// |_________|
  363. /// ___ ____ __ _ _____ ___ ___
  364. /// |___|____|__|_|_____|___|___|
  365. /// @endcode
  366. static void
  367. app_flatten_marks (void)
  368. {
  369. qsort (g_ctx.marks, g_ctx.marks_len, sizeof *g_ctx.marks, app_mark_cmp);
  370. if (!g_ctx.marks_len)
  371. return;
  372. ARRAY (struct mark *, current)
  373. ARRAY_INIT (current);
  374. int current_color = 0;
  375. // Make offset zero actually point to an empty entry
  376. g_ctx.offset_entries[g_ctx.offset_entries_len++] = NULL;
  377. struct mark *next = g_ctx.marks;
  378. struct mark *end = next + g_ctx.marks_len;
  379. while (current_len || next < end)
  380. {
  381. // Find the closest offset at which marks change
  382. int64_t closest = g_ctx.data_offset + g_ctx.data_len;
  383. if (next < end)
  384. closest = next->offset;
  385. for (size_t i = 0; i < current_len; i++)
  386. closest = MIN (closest, current[i]->offset + current[i]->len);
  387. // Remove from "current" marks that have ended
  388. for (size_t i = 0; i < current_len; )
  389. if (closest == current[i]->offset + current[i]->len)
  390. memmove (current + i, current + i + 1,
  391. (--current_len - i) * sizeof *current);
  392. else
  393. i++;
  394. // Add any new marks at "closest"
  395. while (next < end && next->offset == closest)
  396. {
  397. current[current_len++] = next++;
  398. ARRAY_RESERVE (current, 1);
  399. }
  400. current[current_len] = NULL;
  401. // Save marks at that offset to be used by rendering
  402. size_t marks = 0;
  403. int color = -1;
  404. if (current_len)
  405. {
  406. marks = app_store_marks (current, current_len + 1);
  407. color = ATTRIBUTE_C1 + current_color++;
  408. current_color %= 4;
  409. }
  410. ARRAY_RESERVE (g_ctx.marks_by_offset, 1);
  411. g_ctx.marks_by_offset[g_ctx.marks_by_offset_len++] =
  412. (struct marks_by_offset) { closest, marks, color };
  413. }
  414. free (current);
  415. }
  416. // --- Rendering ---------------------------------------------------------------
  417. static void
  418. app_invalidate (void)
  419. {
  420. poller_idle_set (&g_ctx.refresh_event);
  421. }
  422. static void
  423. app_flush_buffer (struct row_buffer *buf, int width, chtype attrs)
  424. {
  425. row_buffer_align (buf, width, attrs);
  426. row_buffer_flush (buf);
  427. row_buffer_free (buf);
  428. }
  429. /// Write the given UTF-8 string padded with spaces.
  430. /// @param[in] attrs Text attributes for the text, including padding.
  431. static void
  432. app_write_line (const char *str, chtype attrs)
  433. {
  434. struct row_buffer buf = row_buffer_make ();
  435. row_buffer_append (&buf, str, attrs);
  436. app_flush_buffer (&buf, COLS, attrs);
  437. }
  438. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  439. static int
  440. app_visible_rows (void)
  441. {
  442. return MAX (0, LINES - 1 /* bar */ - 3 /* decoder */ - !!g_ctx.message);
  443. }
  444. static void
  445. app_make_row (struct row_buffer *buf, int64_t addr, int attrs)
  446. {
  447. char *row_addr_str = xstrdup_printf ("%08" PRIx64, addr);
  448. row_buffer_append (buf, row_addr_str, attrs);
  449. free (row_addr_str);
  450. struct row_buffer ascii = row_buffer_make ();
  451. row_buffer_append (&ascii, " ", attrs);
  452. int64_t end_addr = g_ctx.data_offset + g_ctx.data_len;
  453. const char *hexa = "0123456789abcdef";
  454. for (int x = 0; x < ROW_SIZE; x++)
  455. {
  456. if (x % 8 == 0) row_buffer_append (buf, " ", attrs);
  457. if (x % 2 == 0) row_buffer_append (buf, " ", attrs);
  458. int64_t cell_addr = addr + x;
  459. if (cell_addr < g_ctx.data_offset
  460. || cell_addr >= end_addr)
  461. {
  462. row_buffer_append (buf, " ", attrs);
  463. row_buffer_append (&ascii, " ", attrs);
  464. }
  465. else
  466. {
  467. int attrs_mark = attrs;
  468. struct marks_by_offset *marks = app_marks_at_offset (cell_addr);
  469. if (marks && marks->color >= 0)
  470. attrs_mark = g_ctx.attrs[marks->color].attrs;
  471. int highlight = 0;
  472. if (cell_addr >= g_ctx.view_cursor
  473. && cell_addr < g_ctx.view_cursor + 8)
  474. highlight = A_UNDERLINE;
  475. // TODO: leave it up to the user to decide what should be colored
  476. uint8_t cell = g_ctx.data[cell_addr - g_ctx.data_offset];
  477. row_buffer_append (buf,
  478. (char[3]) { hexa[cell >> 4], hexa[cell & 7], 0 },
  479. attrs | highlight);
  480. char s[2] = { (cell >= 32 && cell < 127) ? cell : '.', 0 };
  481. row_buffer_append (&ascii, s, attrs_mark | highlight);
  482. }
  483. }
  484. row_buffer_append_buffer (buf, &ascii);
  485. row_buffer_free (&ascii);
  486. }
  487. static void
  488. app_draw_view (void)
  489. {
  490. move (0, 0);
  491. int64_t end_addr = g_ctx.data_offset + g_ctx.data_len;
  492. for (int y = 0; y < app_visible_rows (); y++)
  493. {
  494. int64_t addr = g_ctx.view_top + y * ROW_SIZE;
  495. if (addr >= end_addr)
  496. break;
  497. int attrs = (addr / ROW_SIZE & 1) ? APP_ATTR (ODD) : APP_ATTR (EVEN);
  498. struct row_buffer buf = row_buffer_make ();
  499. app_make_row (&buf, addr, attrs);
  500. app_flush_buffer (&buf, COLS, attrs);
  501. }
  502. }
  503. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  504. static void
  505. app_draw_info (void)
  506. {
  507. const struct marks_by_offset *marks;
  508. if (!(marks = app_marks_at_offset (g_ctx.view_cursor)))
  509. return;
  510. int x_offset = 70;
  511. struct mark *mark, **iter = g_ctx.offset_entries + marks->marks;
  512. for (int y = 0; y < app_visible_rows (); y++)
  513. {
  514. // TODO: we can use the field background
  515. // TODO: we can keep going through subsequent fields to fill the column
  516. if (!(mark = *iter++))
  517. break;
  518. struct row_buffer buf = row_buffer_make ();
  519. row_buffer_append (&buf,
  520. g_ctx.mark_strings.str + mark->description, 0);
  521. move (y, x_offset);
  522. app_flush_buffer (&buf, COLS - x_offset, 0);
  523. }
  524. }
  525. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  526. static uint64_t
  527. app_decode (const uint8_t *p, size_t len, enum endianity endianity)
  528. {
  529. uint64_t val = 0;
  530. if (endianity == ENDIANITY_BE)
  531. for (size_t i = 0; i < len; i++)
  532. val = val << 8 | (uint64_t) p[i];
  533. else
  534. while (len--)
  535. val = val << 8 | (uint64_t) p[len];
  536. return val;
  537. }
  538. static void
  539. app_write_footer (struct row_buffer *b, char id, int len, const char *fmt, ...)
  540. ATTRIBUTE_PRINTF (4, 5);
  541. static void
  542. app_footer_field (struct row_buffer *b, char id, int len, const char *fmt, ...)
  543. {
  544. const char *coding;
  545. if (len <= 1)
  546. coding = "";
  547. else if (g_ctx.endianity == ENDIANITY_LE)
  548. coding = "le";
  549. else if (g_ctx.endianity == ENDIANITY_BE)
  550. coding = "be";
  551. char *key = xstrdup_printf ("%c%d%s", id, len * 8, coding);
  552. row_buffer_append (b, key, APP_ATTR (FOOTER_HL));
  553. free (key);
  554. va_list ap;
  555. va_start (ap, fmt);
  556. struct str value = str_make ();
  557. str_append_vprintf (&value, fmt, ap);
  558. va_end (ap);
  559. row_buffer_append (b, value.str, APP_ATTR (FOOTER));
  560. str_free (&value);
  561. }
  562. static void
  563. app_draw_footer (void)
  564. {
  565. move (app_visible_rows (), 0);
  566. struct row_buffer buf = row_buffer_make ();
  567. row_buffer_append (&buf, APP_TITLE, APP_ATTR (BAR));
  568. if (g_ctx.filename)
  569. {
  570. row_buffer_append (&buf, " ", APP_ATTR (BAR));
  571. char *filename = (char *) u8_strconv_from_locale (g_ctx.filename);
  572. row_buffer_append (&buf, filename, APP_ATTR (BAR_HL));
  573. free (filename);
  574. }
  575. struct str right = str_make ();
  576. str_append_printf (&right, " %08" PRIx64, g_ctx.view_cursor);
  577. str_append (&right, g_ctx.endianity == ENDIANITY_LE ? " LE " : " BE ");
  578. int64_t top = g_ctx.view_top;
  579. int64_t bot = g_ctx.view_top + app_visible_rows () * ROW_SIZE;
  580. if (top <= g_ctx.data_offset
  581. && bot >= g_ctx.data_offset + g_ctx.data_len)
  582. str_append (&right, "All");
  583. else if (top <= g_ctx.data_offset)
  584. str_append (&right, "Top");
  585. else if (bot >= g_ctx.data_offset + g_ctx.data_len)
  586. str_append (&right, "Bot");
  587. else
  588. {
  589. int64_t end_addr = g_ctx.data_offset + g_ctx.data_len;
  590. int64_t cur = g_ctx.view_top / ROW_SIZE;
  591. int64_t max = (end_addr - 1) / ROW_SIZE - app_visible_rows () + 1;
  592. cur -= g_ctx.data_offset / ROW_SIZE;
  593. max -= g_ctx.data_offset / ROW_SIZE;
  594. str_append_printf (&right, "%2d%%", (int) (100 * cur / max));
  595. }
  596. row_buffer_align (&buf, COLS - right.len, APP_ATTR (BAR));
  597. row_buffer_append (&buf, right.str, APP_ATTR (BAR));
  598. app_flush_buffer (&buf, COLS, APP_ATTR (BAR));
  599. str_free (&right);
  600. int64_t end_addr = g_ctx.data_offset + g_ctx.data_len;
  601. if (g_ctx.view_cursor < g_ctx.data_offset
  602. || g_ctx.view_cursor >= end_addr)
  603. return;
  604. int64_t len = end_addr - g_ctx.view_cursor;
  605. uint8_t *p = g_ctx.data + (g_ctx.view_cursor - g_ctx.data_offset);
  606. struct row_buffer x = row_buffer_make ();
  607. struct row_buffer u = row_buffer_make ();
  608. struct row_buffer s = row_buffer_make ();
  609. if (len >= 1)
  610. {
  611. app_footer_field (&x, 'x', 1, " %02x ", p[0]);
  612. app_footer_field (&u, 'u', 1, " %4u ", p[0]);
  613. app_footer_field (&s, 's', 1, " %4d ", (int8_t) p[0]);
  614. }
  615. if (len >= 2)
  616. {
  617. uint16_t val = app_decode (p, 2, g_ctx.endianity);
  618. app_footer_field (&x, 'x', 2, " %04x ", val);
  619. app_footer_field (&u, 'u', 2, " %6u ", val);
  620. app_footer_field (&s, 's', 2, " %6d ", (int16_t) val);
  621. }
  622. if (len >= 4)
  623. {
  624. uint32_t val = app_decode (p, 4, g_ctx.endianity);
  625. app_footer_field (&x, 'x', 4, " %08x ", val);
  626. app_footer_field (&u, 'u', 4, " %11u ", val);
  627. app_footer_field (&s, 's', 4, " %11d ", (int32_t) val);
  628. }
  629. if (len >= 8)
  630. {
  631. uint64_t val = app_decode (p, 8, g_ctx.endianity);
  632. app_footer_field (&x, 'x', 8, " %016" PRIx64, val);
  633. app_footer_field (&u, 'u', 8, " %20" PRIu64, val);
  634. app_footer_field (&s, 's', 8, " %20" PRId64, (int64_t) val);
  635. }
  636. app_flush_buffer (&x, COLS, APP_ATTR (FOOTER));
  637. app_flush_buffer (&u, COLS, APP_ATTR (FOOTER));
  638. app_flush_buffer (&s, COLS, APP_ATTR (FOOTER));
  639. if (g_ctx.message)
  640. app_write_line (g_ctx.message, g_ctx.attrs[g_ctx.message_attr].attrs);
  641. }
  642. static void
  643. app_on_refresh (void *user_data)
  644. {
  645. (void) user_data;
  646. poller_idle_reset (&g_ctx.refresh_event);
  647. erase ();
  648. app_draw_view ();
  649. app_draw_info ();
  650. app_draw_footer ();
  651. int64_t diff = g_ctx.view_cursor - g_ctx.view_top;
  652. int64_t y = diff / ROW_SIZE;
  653. int64_t x = diff % ROW_SIZE;
  654. if (diff >= 0 && y < app_visible_rows ())
  655. {
  656. curs_set (1);
  657. move (y, 10 + x*2 + g_ctx.view_skip_nibble + x/8 + x/2);
  658. }
  659. else
  660. curs_set (0);
  661. refresh ();
  662. }
  663. // --- Lua ---------------------------------------------------------------------
  664. #ifdef HAVE_LUA
  665. static void *
  666. app_lua_alloc (void *ud, void *ptr, size_t o_size, size_t n_size)
  667. {
  668. (void) ud;
  669. (void) o_size;
  670. if (n_size)
  671. return realloc (ptr, n_size);
  672. free (ptr);
  673. return NULL;
  674. }
  675. static int
  676. app_lua_panic (lua_State *L)
  677. {
  678. // XXX: we might be able to do something better
  679. print_fatal ("Lua panicked: %s", lua_tostring (L, -1));
  680. lua_close (L);
  681. exit (EXIT_FAILURE);
  682. return 0;
  683. }
  684. static bool
  685. app_lua_getfield (lua_State *L, int idx, const char *name,
  686. int expected, bool optional)
  687. {
  688. int found = lua_getfield (L, idx, name);
  689. if (found == expected)
  690. return true;
  691. if (optional && found == LUA_TNIL)
  692. return false;
  693. const char *message = optional
  694. ? "invalid field \"%s\" (found: %s, expected: %s or nil)"
  695. : "invalid or missing field \"%s\" (found: %s, expected: %s)";
  696. return luaL_error (L, message, name,
  697. lua_typename (L, found), lua_typename (L, expected));
  698. }
  699. static int
  700. app_lua_error_handler (lua_State *L)
  701. {
  702. luaL_traceback (L, L, luaL_checkstring (L, 1), 1);
  703. return 1;
  704. }
  705. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  706. struct app_lua_coder
  707. {
  708. int ref_detect; ///< Reference to the "detect" method
  709. int ref_decode; ///< Reference to the "decode" method
  710. };
  711. static void
  712. app_lua_coder_free (void *coder)
  713. {
  714. struct app_lua_coder *self = coder;
  715. luaL_unref (g_ctx.L, LUA_REGISTRYINDEX, self->ref_decode);
  716. luaL_unref (g_ctx.L, LUA_REGISTRYINDEX, self->ref_detect);
  717. free (self);
  718. }
  719. static int
  720. app_lua_register (lua_State *L)
  721. {
  722. luaL_checktype (L, 1, LUA_TTABLE);
  723. (void) app_lua_getfield (L, 1, "type", LUA_TSTRING, false);
  724. const char *type = lua_tostring (L, -1);
  725. if (str_map_find (&g_ctx.coders, type))
  726. luaL_error (L, "a coder has already been registered for `%s'", type);
  727. (void) app_lua_getfield (L, 1, "detect", LUA_TFUNCTION, true);
  728. (void) app_lua_getfield (L, 1, "decode", LUA_TFUNCTION, false);
  729. struct app_lua_coder *coder = xcalloc (1, sizeof *coder);
  730. coder->ref_decode = luaL_ref (L, LUA_REGISTRYINDEX);
  731. coder->ref_detect = luaL_ref (L, LUA_REGISTRYINDEX);
  732. str_map_set (&g_ctx.coders, type, coder);
  733. return 0;
  734. }
  735. static luaL_Reg app_lua_library[] =
  736. {
  737. { "register", app_lua_register },
  738. { NULL, NULL }
  739. };
  740. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  741. #define XLUA_CHUNK_METATABLE "chunk"
  742. struct app_lua_chunk
  743. {
  744. int64_t offset; ///< Offset from start of file
  745. int64_t len; ///< Length of the sequence
  746. int64_t position; ///< Read position in the sequence
  747. enum endianity endianity; ///< Read endianity
  748. };
  749. static struct app_lua_chunk *
  750. app_lua_chunk_new (lua_State *L)
  751. {
  752. struct app_lua_chunk *chunk = lua_newuserdata (L, sizeof *chunk);
  753. luaL_setmetatable (L, XLUA_CHUNK_METATABLE);
  754. memset (chunk, 0, sizeof *chunk);
  755. return chunk;
  756. }
  757. static int
  758. app_lua_chunk_len (lua_State *L)
  759. {
  760. struct app_lua_chunk *self = luaL_checkudata (L, 1, XLUA_CHUNK_METATABLE);
  761. lua_pushinteger (L, self->len);
  762. return 1;
  763. }
  764. /// Create a new subchunk following Lua's string.sub() semantics.
  765. /// An implication is that it is not possible to go extend a chunk's bounds.
  766. static int
  767. app_lua_chunk_call (lua_State *L)
  768. {
  769. struct app_lua_chunk *self = luaL_checkudata (L, 1, XLUA_CHUNK_METATABLE);
  770. lua_Integer start = luaL_optinteger (L, 2, 1);
  771. lua_Integer end = luaL_optinteger (L, 3, -1);
  772. if (start < 0) start += self->len + 1;
  773. if (end < 0) end += self->len + 1;
  774. start = MAX (start, 1);
  775. end = MIN (end, self->len);
  776. struct app_lua_chunk *clone = app_lua_chunk_new (L);
  777. clone->position = 0;
  778. clone->endianity = self->endianity;
  779. if (start > end)
  780. {
  781. // "start" can be too high and "end" can be too low;
  782. // the length is zero, so the offset doesn't matter much anyway
  783. clone->offset = self->offset;
  784. clone->len = 0;
  785. }
  786. else
  787. {
  788. clone->offset = self->offset + start - 1;
  789. clone->len = end - start + 1;
  790. }
  791. return 1;
  792. }
  793. static int
  794. app_lua_chunk_index (lua_State *L)
  795. {
  796. struct app_lua_chunk *self = luaL_checkudata (L, 1, XLUA_CHUNK_METATABLE);
  797. const char *key = luaL_checkstring (L, 2);
  798. if (luaL_getmetafield (L, 1, key))
  799. return 1;
  800. if (!strcmp (key, "offset"))
  801. lua_pushinteger (L, self->offset);
  802. else if (!strcmp (key, "endianity"))
  803. lua_pushstring (L, self->endianity == ENDIANITY_LE ? "le" : "be");
  804. else if (!strcmp (key, "position"))
  805. lua_pushinteger (L, self->position + 1);
  806. else if (!strcmp (key, "eof"))
  807. lua_pushboolean (L, self->position >= self->len);
  808. else
  809. return luaL_argerror (L, 2, "not a readable property");
  810. return 1;
  811. }
  812. static int
  813. app_lua_chunk_newindex (lua_State *L)
  814. {
  815. struct app_lua_chunk *self = luaL_checkudata (L, 1, XLUA_CHUNK_METATABLE);
  816. const char *key = luaL_checkstring (L, 2);
  817. if (!strcmp (key, "endianity"))
  818. {
  819. // Needs to be in the enum order
  820. const char *options[] = { "le", "be" };
  821. self->endianity = luaL_checkoption (L, 3, options[0], options);
  822. }
  823. else if (!strcmp (key, "position"))
  824. {
  825. lua_Integer position = luaL_checkinteger (L, 3);
  826. if (position < 1 || position > self->len + 1)
  827. return luaL_error (L, "position out of range: %I", position);
  828. self->position = position - 1;
  829. }
  830. else
  831. return luaL_argerror (L, 2, "not a writable property");
  832. return 0;
  833. }
  834. static void
  835. app_lua_mark (int64_t offset, int64_t len, const char *desc)
  836. {
  837. // That would cause stupid entries, making trouble in marks_by_offset
  838. if (len <= 0)
  839. return;
  840. ARRAY_RESERVE (g_ctx.marks, 1);
  841. g_ctx.marks[g_ctx.marks_len++] =
  842. (struct mark) { offset, len, g_ctx.mark_strings.len };
  843. str_append (&g_ctx.mark_strings, desc);
  844. str_append_c (&g_ctx.mark_strings, 0);
  845. }
  846. static int
  847. app_lua_chunk_mark (lua_State *L)
  848. {
  849. struct app_lua_chunk *self = luaL_checkudata (L, 1, XLUA_CHUNK_METATABLE);
  850. int n_args = lua_gettop (L);
  851. lua_rawgeti (L, LUA_REGISTRYINDEX, g_ctx.ref_format);
  852. lua_insert (L, 2);
  853. lua_call (L, n_args - 1, 1);
  854. app_lua_mark (self->offset, self->len, luaL_checkstring (L, -1));
  855. return 0;
  856. }
  857. /// Try to detect any registered type in the data and return its name
  858. static int
  859. app_lua_chunk_identify (lua_State *L)
  860. {
  861. (void) luaL_checkudata (L, 1, XLUA_CHUNK_METATABLE);
  862. struct str_map_iter iter = str_map_iter_make (&g_ctx.coders);
  863. struct app_lua_coder *coder;
  864. while ((coder = str_map_iter_next (&iter)))
  865. {
  866. if (coder->ref_detect == LUA_REFNIL)
  867. continue;
  868. lua_rawgeti (L, LUA_REGISTRYINDEX, coder->ref_detect);
  869. // Clone the chunk first to reset its read position
  870. lua_pushcfunction (L, app_lua_chunk_call);
  871. lua_pushvalue (L, 1);
  872. lua_call (L, 1, 1);
  873. lua_call (L, 1, 1);
  874. if (lua_toboolean (L, -1))
  875. {
  876. lua_pushstring (L, iter.link->key);
  877. return 1;
  878. }
  879. lua_pop (L, 1);
  880. }
  881. return 0;
  882. }
  883. static int
  884. app_lua_chunk_decode (lua_State *L)
  885. {
  886. (void) luaL_checkudata (L, 1, XLUA_CHUNK_METATABLE);
  887. const char *type = luaL_optstring (L, 2, NULL);
  888. // TODO: further arguments should be passed to the decoding function
  889. if (!type)
  890. {
  891. lua_pushcfunction (L, app_lua_chunk_identify);
  892. lua_pushvalue (L, 1);
  893. lua_call (L, 1, 1);
  894. type = lua_tostring (L, -1);
  895. }
  896. // Can't identify -> can't decode, nothing to do here
  897. if (!type)
  898. return 0;
  899. // While we could call "detect" here, just to be sure, some kinds may not
  900. // even be detectable and it's better to leave it up to the plugin
  901. struct app_lua_coder *coder = str_map_find (&g_ctx.coders, type);
  902. if (!coder)
  903. return luaL_error (L, "unknown type: %s", type);
  904. lua_rawgeti (L, LUA_REGISTRYINDEX, coder->ref_decode);
  905. lua_pushvalue (L, 1);
  906. // TODO: the chunk could remember the name of the coder and prepend it
  907. // to all marks set from the callback; then reset it back to NULL
  908. lua_call (L, 1, 0);
  909. return 0;
  910. }
  911. static int
  912. app_lua_chunk_read (lua_State *L)
  913. {
  914. struct app_lua_chunk *self = luaL_checkudata (L, 1, XLUA_CHUNK_METATABLE);
  915. lua_Integer len = luaL_checkinteger (L, 2);
  916. if (len < 0)
  917. return luaL_argerror (L, 2, "invalid read length");
  918. int64_t start = self->offset + self->position;
  919. // XXX: or just return a shorter string in this case?
  920. if (start + len > g_ctx.data_offset + g_ctx.data_len)
  921. return luaL_argerror (L, 2, "chunk is too short");
  922. lua_pushlstring (L, (char *) g_ctx.data + (start - g_ctx.data_offset), len);
  923. self->position += len;
  924. return 1;
  925. }
  926. /// Mark a field that has just been read from the chunk and advance position:
  927. /// - the second argument, if present, is a simple format string for marking;
  928. /// - the third argument, if present, is a filtering function.
  929. ///
  930. /// I am aware of how ugly the implicit "string.format" is. Convenience wins.
  931. static void
  932. app_lua_chunk_finish_read
  933. (lua_State *L, struct app_lua_chunk *self, int64_t len)
  934. {
  935. int n_args = lua_gettop (L) - 1;
  936. if (n_args < 2)
  937. {
  938. self->position += len;
  939. return;
  940. }
  941. // Prepare <string.format>, <format>, <value>
  942. lua_rawgeti (L, LUA_REGISTRYINDEX, g_ctx.ref_format);
  943. lua_pushvalue (L, 2);
  944. int pre_filter_top = lua_gettop (L);
  945. lua_pushvalue (L, -3);
  946. // Transform the value if a filtering function is provided
  947. if (n_args >= 3)
  948. {
  949. lua_pushvalue (L, 3);
  950. lua_insert (L, -2);
  951. lua_call (L, 1, LUA_MULTRET);
  952. int n_ret = lua_gettop (L) - pre_filter_top;
  953. // When no value has been returned, keep the old one
  954. if (n_ret < 1)
  955. lua_pushvalue (L, pre_filter_top - 2);
  956. // Forward multiple return values to "string.format"
  957. if (n_ret > 1)
  958. {
  959. lua_pushvalue (L, pre_filter_top - 1);
  960. lua_insert (L, -n_ret - 1);
  961. lua_call (L, n_ret, 1);
  962. }
  963. }
  964. lua_call (L, 2, 1);
  965. app_lua_mark (self->offset + self->position, len, lua_tostring (L, -1));
  966. self->position += len;
  967. lua_pop (L, 1);
  968. }
  969. static int
  970. app_lua_chunk_cstring (lua_State *L)
  971. {
  972. struct app_lua_chunk *self = luaL_checkudata (L, 1, XLUA_CHUNK_METATABLE);
  973. void *s = g_ctx.data + (self->offset - g_ctx.data_offset) + self->position;
  974. void *nil;
  975. if (!(nil = memchr (s, '\0', self->len - self->position)))
  976. return luaL_error (L, "unexpected EOF");
  977. lua_pushlstring (L, s, nil - s);
  978. app_lua_chunk_finish_read (L, self, nil - s + 1);
  979. return 1;
  980. }
  981. /// Decode "len" bytes as a number starting at the current position in "self"
  982. static uint64_t
  983. app_lua_chunk_decode_int (lua_State *L, struct app_lua_chunk *self, size_t len)
  984. {
  985. if (self->position + (int64_t) len > self->len)
  986. return luaL_error (L, "unexpected EOF");
  987. void *s = g_ctx.data + (self->offset - g_ctx.data_offset) + self->position;
  988. return app_decode (s, len, self->endianity);
  989. }
  990. #define APP_LUA_CHUNK_INT(name, type) \
  991. static int \
  992. app_lua_chunk_ ## name (lua_State *L) \
  993. { \
  994. struct app_lua_chunk *self = \
  995. luaL_checkudata (L, 1, XLUA_CHUNK_METATABLE); \
  996. type v = app_lua_chunk_decode_int (L, self, sizeof v); \
  997. lua_pushinteger (L, v); \
  998. app_lua_chunk_finish_read (L, self, sizeof v); \
  999. return 1; \
  1000. }
  1001. APP_LUA_CHUNK_INT (u8, uint8_t) APP_LUA_CHUNK_INT (s8, int8_t)
  1002. APP_LUA_CHUNK_INT (u16, uint16_t) APP_LUA_CHUNK_INT (s16, int16_t)
  1003. APP_LUA_CHUNK_INT (u32, uint32_t) APP_LUA_CHUNK_INT (s32, int32_t)
  1004. APP_LUA_CHUNK_INT (u64, uint64_t) APP_LUA_CHUNK_INT (s64, int64_t)
  1005. static luaL_Reg app_lua_chunk_table[] =
  1006. {
  1007. { "__len", app_lua_chunk_len },
  1008. { "__call", app_lua_chunk_call },
  1009. { "__index", app_lua_chunk_index },
  1010. { "__newindex", app_lua_chunk_newindex },
  1011. { "mark", app_lua_chunk_mark },
  1012. { "identify", app_lua_chunk_identify },
  1013. { "decode", app_lua_chunk_decode },
  1014. { "read", app_lua_chunk_read },
  1015. { "cstring", app_lua_chunk_cstring },
  1016. { "u8", app_lua_chunk_u8 },
  1017. { "s8", app_lua_chunk_s8 },
  1018. { "u16", app_lua_chunk_u16 },
  1019. { "s16", app_lua_chunk_s16 },
  1020. { "u32", app_lua_chunk_u32 },
  1021. { "s32", app_lua_chunk_s32 },
  1022. { "u64", app_lua_chunk_u64 },
  1023. { "s64", app_lua_chunk_s64 },
  1024. { NULL, NULL }
  1025. };
  1026. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1027. static void
  1028. app_lua_load_plugins (const char *plugin_dir)
  1029. {
  1030. DIR *dir;
  1031. if (!(dir = opendir (plugin_dir)))
  1032. {
  1033. if (errno != ENOENT)
  1034. print_error ("cannot open directory `%s': %s",
  1035. plugin_dir, strerror (errno));
  1036. return;
  1037. }
  1038. struct dirent *iter;
  1039. while ((errno = 0, iter = readdir (dir)))
  1040. {
  1041. const char *dot = strrchr (iter->d_name, '.');
  1042. if (!dot || strcmp (dot, ".lua"))
  1043. continue;
  1044. char *path = xstrdup_printf ("%s/%s", plugin_dir, iter->d_name);
  1045. lua_pushcfunction (g_ctx.L, app_lua_error_handler);
  1046. if (luaL_loadfile (g_ctx.L, path)
  1047. || lua_pcall (g_ctx.L, 0, 0, -2))
  1048. exit_fatal ("Lua: %s", lua_tostring (g_ctx.L, -1));
  1049. lua_pop (g_ctx.L, 1);
  1050. free (path);
  1051. }
  1052. if (errno)
  1053. exit_fatal ("readdir: %s", strerror (errno));
  1054. closedir (dir);
  1055. }
  1056. static void
  1057. app_lua_init (void)
  1058. {
  1059. if (!(g_ctx.L = lua_newstate (app_lua_alloc, NULL)))
  1060. exit_fatal ("Lua initialization failed");
  1061. g_ctx.coders = str_map_make (app_lua_coder_free);
  1062. lua_atpanic (g_ctx.L, app_lua_panic);
  1063. luaL_openlibs (g_ctx.L);
  1064. luaL_checkversion (g_ctx.L);
  1065. // I don't want to reimplement this and the C function is not exported
  1066. hard_assert (lua_getglobal (g_ctx.L, LUA_STRLIBNAME));
  1067. hard_assert (lua_getfield (g_ctx.L, -1, "format"));
  1068. g_ctx.ref_format = luaL_ref (g_ctx.L, LUA_REGISTRYINDEX);
  1069. luaL_newlib (g_ctx.L, app_lua_library);
  1070. lua_setglobal (g_ctx.L, PROGRAM_NAME);
  1071. luaL_newmetatable (g_ctx.L, XLUA_CHUNK_METATABLE);
  1072. luaL_setfuncs (g_ctx.L, app_lua_chunk_table, 0);
  1073. lua_pop (g_ctx.L, 1);
  1074. struct strv v = strv_make ();
  1075. get_xdg_data_dirs (&v);
  1076. for (size_t i = 0; i < v.len; i++)
  1077. {
  1078. char *path = xstrdup_printf
  1079. ("%s/%s", v.vector[i], PROGRAM_NAME "/plugins");
  1080. app_lua_load_plugins (path);
  1081. free (path);
  1082. }
  1083. strv_free (&v);
  1084. }
  1085. #endif // HAVE_LUA
  1086. // --- Actions -----------------------------------------------------------------
  1087. /// Checks what items are visible and returns if fixes were needed
  1088. static bool
  1089. app_fix_view_range (void)
  1090. {
  1091. int64_t data_view_start = g_ctx.data_offset / ROW_SIZE * ROW_SIZE;
  1092. if (g_ctx.view_top < data_view_start)
  1093. {
  1094. g_ctx.view_top = data_view_start;
  1095. app_invalidate ();
  1096. return false;
  1097. }
  1098. // If the contents are at least as long as the screen, always fill it
  1099. int64_t last_byte = g_ctx.data_offset + g_ctx.data_len - 1;
  1100. int64_t max_view_top =
  1101. (last_byte / ROW_SIZE - app_visible_rows () + 1) * ROW_SIZE;
  1102. // But don't let that suggest a negative offset
  1103. max_view_top = MAX (max_view_top, 0);
  1104. if (g_ctx.view_top > max_view_top)
  1105. {
  1106. g_ctx.view_top = max_view_top;
  1107. app_invalidate ();
  1108. return false;
  1109. }
  1110. return true;
  1111. }
  1112. /// Scroll down (positive) or up (negative) @a n items
  1113. static bool
  1114. app_scroll (int n)
  1115. {
  1116. g_ctx.view_top += n * ROW_SIZE;
  1117. app_invalidate ();
  1118. return app_fix_view_range ();
  1119. }
  1120. static void
  1121. app_ensure_selection_visible (void)
  1122. {
  1123. int too_high = g_ctx.view_top / ROW_SIZE - g_ctx.view_cursor / ROW_SIZE;
  1124. if (too_high > 0)
  1125. app_scroll (-too_high);
  1126. int too_low = g_ctx.view_cursor / ROW_SIZE - g_ctx.view_top / ROW_SIZE
  1127. - app_visible_rows () + 1;
  1128. if (too_low > 0)
  1129. app_scroll (too_low);
  1130. }
  1131. static bool
  1132. app_move_cursor_by_rows (int diff)
  1133. {
  1134. // TODO: disallow partial up/down movement
  1135. int64_t fixed = g_ctx.view_cursor += diff * ROW_SIZE;
  1136. fixed = MAX (fixed, g_ctx.data_offset);
  1137. fixed = MIN (fixed, g_ctx.data_offset + g_ctx.data_len - 1);
  1138. bool result = g_ctx.view_cursor == fixed;
  1139. g_ctx.view_cursor = fixed;
  1140. app_invalidate ();
  1141. app_ensure_selection_visible ();
  1142. return result;
  1143. }
  1144. static bool
  1145. app_jump_to_marks (ssize_t i)
  1146. {
  1147. if (i < 0 || (size_t) i >= g_ctx.marks_by_offset_len)
  1148. return false;
  1149. g_ctx.view_cursor = g_ctx.marks_by_offset[i].offset;
  1150. g_ctx.view_skip_nibble = false;
  1151. app_invalidate ();
  1152. app_ensure_selection_visible ();
  1153. return true;
  1154. }
  1155. // --- User input handling -----------------------------------------------------
  1156. enum action
  1157. {
  1158. ACTION_NONE, ACTION_QUIT, ACTION_REDRAW, ACTION_TOGGLE_ENDIANITY,
  1159. ACTION_SCROLL_UP, ACTION_GOTO_TOP, ACTION_GOTO_PAGE_PREVIOUS,
  1160. ACTION_SCROLL_DOWN, ACTION_GOTO_BOTTOM, ACTION_GOTO_PAGE_NEXT,
  1161. ACTION_UP, ACTION_DOWN, ACTION_LEFT, ACTION_RIGHT,
  1162. ACTION_ROW_START, ACTION_ROW_END,
  1163. ACTION_FIELD_PREVIOUS, ACTION_FIELD_NEXT,
  1164. ACTION_COUNT
  1165. };
  1166. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1167. static bool
  1168. app_process_action (enum action action)
  1169. {
  1170. switch (action)
  1171. {
  1172. // XXX: these should rather be parametrized
  1173. case ACTION_SCROLL_UP: app_scroll (-1); break;
  1174. case ACTION_SCROLL_DOWN: app_scroll (1); break;
  1175. case ACTION_GOTO_TOP:
  1176. g_ctx.view_cursor = g_ctx.data_offset;
  1177. g_ctx.view_skip_nibble = false;
  1178. app_ensure_selection_visible ();
  1179. app_invalidate ();
  1180. break;
  1181. case ACTION_GOTO_BOTTOM:
  1182. if (!g_ctx.data_len)
  1183. return false;
  1184. g_ctx.view_cursor = g_ctx.data_offset + g_ctx.data_len - 1;
  1185. g_ctx.view_skip_nibble = false;
  1186. app_ensure_selection_visible ();
  1187. app_invalidate ();
  1188. break;
  1189. case ACTION_GOTO_PAGE_PREVIOUS:
  1190. app_scroll (-app_visible_rows ());
  1191. app_move_cursor_by_rows (-app_visible_rows ());
  1192. break;
  1193. case ACTION_GOTO_PAGE_NEXT:
  1194. app_scroll (app_visible_rows ());
  1195. app_move_cursor_by_rows (app_visible_rows ());
  1196. break;
  1197. case ACTION_UP: app_move_cursor_by_rows (-1); break;
  1198. case ACTION_DOWN: app_move_cursor_by_rows (1); break;
  1199. case ACTION_LEFT:
  1200. if (g_ctx.view_skip_nibble)
  1201. g_ctx.view_skip_nibble = false;
  1202. else
  1203. {
  1204. if (g_ctx.view_cursor <= g_ctx.data_offset)
  1205. return false;
  1206. g_ctx.view_skip_nibble = true;
  1207. g_ctx.view_cursor--;
  1208. app_ensure_selection_visible ();
  1209. }
  1210. app_invalidate ();
  1211. break;
  1212. case ACTION_RIGHT:
  1213. if (!g_ctx.view_skip_nibble)
  1214. g_ctx.view_skip_nibble = true;
  1215. else
  1216. {
  1217. if (g_ctx.view_cursor >= g_ctx.data_offset + g_ctx.data_len - 1)
  1218. return false;
  1219. g_ctx.view_skip_nibble = false;
  1220. g_ctx.view_cursor++;
  1221. app_ensure_selection_visible ();
  1222. }
  1223. app_invalidate ();
  1224. break;
  1225. case ACTION_ROW_START:
  1226. {
  1227. int64_t new = g_ctx.view_cursor / ROW_SIZE * ROW_SIZE;
  1228. new = MAX (new, g_ctx.data_offset);
  1229. new = MIN (new, g_ctx.data_offset + g_ctx.data_len - 1);
  1230. g_ctx.view_cursor = new;
  1231. g_ctx.view_skip_nibble = false;
  1232. app_invalidate ();
  1233. break;
  1234. }
  1235. case ACTION_ROW_END:
  1236. {
  1237. int64_t new = (g_ctx.view_cursor / ROW_SIZE + 1) * ROW_SIZE - 1;
  1238. new = MAX (new, g_ctx.data_offset);
  1239. new = MIN (new, g_ctx.data_offset + g_ctx.data_len - 1);
  1240. g_ctx.view_cursor = new;
  1241. g_ctx.view_skip_nibble = false;
  1242. app_invalidate ();
  1243. break;
  1244. }
  1245. case ACTION_FIELD_PREVIOUS:
  1246. {
  1247. ssize_t i = app_find_marks (g_ctx.view_cursor);
  1248. if (i >= 0 && (size_t) i < g_ctx.marks_by_offset_len
  1249. && g_ctx.marks_by_offset[i].offset == g_ctx.view_cursor)
  1250. i--;
  1251. return app_jump_to_marks (i);
  1252. }
  1253. case ACTION_FIELD_NEXT:
  1254. return app_jump_to_marks (app_find_marks (g_ctx.view_cursor) + 1);
  1255. case ACTION_QUIT:
  1256. app_quit ();
  1257. case ACTION_NONE:
  1258. break;
  1259. case ACTION_REDRAW:
  1260. clear ();
  1261. app_invalidate ();
  1262. break;
  1263. case ACTION_TOGGLE_ENDIANITY:
  1264. g_ctx.endianity = (g_ctx.endianity == ENDIANITY_LE)
  1265. ? ENDIANITY_BE : ENDIANITY_LE;
  1266. app_invalidate ();
  1267. break;
  1268. default:
  1269. return false;
  1270. }
  1271. return true;
  1272. }
  1273. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1274. static bool
  1275. app_process_left_mouse_click (int line, int column)
  1276. {
  1277. if (line < 0)
  1278. return false;
  1279. if (line == app_visible_rows ())
  1280. {
  1281. if (column < COLS - 7
  1282. || column >= COLS - 5)
  1283. return false;
  1284. return app_process_action (ACTION_TOGGLE_ENDIANITY);
  1285. }
  1286. else if (line < app_visible_rows ())
  1287. {
  1288. // TODO: when holding a mouse button over a mark string,
  1289. // go to a locked mode that highlights that entire mark
  1290. // (probably by inverting colors)
  1291. // TODO: employ strict checking here before the autofix
  1292. int offset;
  1293. if (column >= 10 && column < 50)
  1294. {
  1295. offset = column - 10;
  1296. offset -= offset/5 + offset/21;
  1297. g_ctx.view_skip_nibble = offset % 2;
  1298. offset /= 2;
  1299. }
  1300. else if (column >= 52 && column < 68)
  1301. {
  1302. offset = column - 52;
  1303. g_ctx.view_skip_nibble = false;
  1304. }
  1305. else
  1306. return false;
  1307. g_ctx.view_cursor = g_ctx.view_top + line * ROW_SIZE + offset;
  1308. return app_move_cursor_by_rows (0);
  1309. }
  1310. return true;
  1311. }
  1312. static bool
  1313. app_process_mouse (termo_mouse_event_t type, int line, int column, int button)
  1314. {
  1315. if (type != TERMO_MOUSE_PRESS)
  1316. return true;
  1317. if (button == 1)
  1318. return app_process_left_mouse_click (line, column);
  1319. else if (button == 4)
  1320. return app_process_action (ACTION_SCROLL_UP);
  1321. else if (button == 5)
  1322. return app_process_action (ACTION_SCROLL_DOWN);
  1323. return false;
  1324. }
  1325. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1326. static struct binding
  1327. {
  1328. const char *key; ///< Key definition
  1329. enum action action; ///< Action to take
  1330. termo_key_t decoded; ///< Decoded key definition
  1331. }
  1332. g_default_bindings[] =
  1333. {
  1334. { "Escape", ACTION_QUIT, {}},
  1335. { "q", ACTION_QUIT, {}},
  1336. { "C-l", ACTION_REDRAW, {}},
  1337. { "Tab", ACTION_TOGGLE_ENDIANITY, {}},
  1338. { "Home", ACTION_ROW_START, {}},
  1339. { "End", ACTION_ROW_END, {}},
  1340. { "M-<", ACTION_GOTO_TOP, {}},
  1341. { "M->", ACTION_GOTO_BOTTOM, {}},
  1342. { "g", ACTION_GOTO_TOP, {}},
  1343. { "G", ACTION_GOTO_BOTTOM, {}},
  1344. { "PageUp", ACTION_GOTO_PAGE_PREVIOUS, {}},
  1345. { "PageDown", ACTION_GOTO_PAGE_NEXT, {}},
  1346. { "C-b", ACTION_GOTO_PAGE_PREVIOUS, {}},
  1347. { "C-f", ACTION_GOTO_PAGE_NEXT, {}},
  1348. { "Up", ACTION_UP, {}},
  1349. { "Down", ACTION_DOWN, {}},
  1350. { "Left", ACTION_LEFT, {}},
  1351. { "Right", ACTION_RIGHT, {}},
  1352. { "k", ACTION_UP, {}},
  1353. { "j", ACTION_DOWN, {}},
  1354. { "h", ACTION_LEFT, {}},
  1355. { "l", ACTION_RIGHT, {}},
  1356. { "C-p", ACTION_UP, {}},
  1357. { "C-n", ACTION_DOWN, {}},
  1358. { "b", ACTION_FIELD_PREVIOUS, {}},
  1359. { "w", ACTION_FIELD_NEXT, {}},
  1360. { "C-y", ACTION_SCROLL_UP, {}},
  1361. { "C-e", ACTION_SCROLL_DOWN, {}},
  1362. };
  1363. static int
  1364. app_binding_cmp (const void *a, const void *b)
  1365. {
  1366. return termo_keycmp (g_ctx.tk,
  1367. &((struct binding *) a)->decoded, &((struct binding *) b)->decoded);
  1368. }
  1369. static void
  1370. app_init_bindings (void)
  1371. {
  1372. for (size_t i = 0; i < N_ELEMENTS (g_default_bindings); i++)
  1373. {
  1374. struct binding *binding = &g_default_bindings[i];
  1375. hard_assert (!*termo_strpkey_utf8 (g_ctx.tk,
  1376. binding->key, &binding->decoded, TERMO_FORMAT_ALTISMETA));
  1377. }
  1378. qsort (g_default_bindings, N_ELEMENTS (g_default_bindings),
  1379. sizeof *g_default_bindings, app_binding_cmp);
  1380. }
  1381. static bool
  1382. app_process_termo_event (termo_key_t *event)
  1383. {
  1384. struct binding dummy = { NULL, 0, *event }, *binding =
  1385. bsearch (&dummy, g_default_bindings, N_ELEMENTS (g_default_bindings),
  1386. sizeof *g_default_bindings, app_binding_cmp);
  1387. if (binding)
  1388. return app_process_action (binding->action);
  1389. // TODO: once we become an editor, use 0-9 a-f to overwrite nibbles
  1390. return false;
  1391. }
  1392. // --- Signals -----------------------------------------------------------------
  1393. static int g_signal_pipe[2]; ///< A pipe used to signal... signals
  1394. /// Program termination has been requested by a signal
  1395. static volatile sig_atomic_t g_termination_requested;
  1396. /// The window has changed in size
  1397. static volatile sig_atomic_t g_winch_received;
  1398. static void
  1399. signals_postpone_handling (char id)
  1400. {
  1401. int original_errno = errno;
  1402. if (write (g_signal_pipe[1], &id, 1) == -1)
  1403. soft_assert (errno == EAGAIN);
  1404. errno = original_errno;
  1405. }
  1406. static void
  1407. signals_superhandler (int signum)
  1408. {
  1409. switch (signum)
  1410. {
  1411. case SIGWINCH:
  1412. g_winch_received = true;
  1413. signals_postpone_handling ('w');
  1414. break;
  1415. case SIGINT:
  1416. case SIGTERM:
  1417. g_termination_requested = true;
  1418. signals_postpone_handling ('t');
  1419. break;
  1420. default:
  1421. hard_assert (!"unhandled signal");
  1422. }
  1423. }
  1424. static void
  1425. signals_setup_handlers (void)
  1426. {
  1427. if (pipe (g_signal_pipe) == -1)
  1428. exit_fatal ("%s: %s", "pipe", strerror (errno));
  1429. set_cloexec (g_signal_pipe[0]);
  1430. set_cloexec (g_signal_pipe[1]);
  1431. // So that the pipe cannot overflow; it would make write() block within
  1432. // the signal handler, which is something we really don't want to happen.
  1433. // The same holds true for read().
  1434. set_blocking (g_signal_pipe[0], false);
  1435. set_blocking (g_signal_pipe[1], false);
  1436. signal (SIGPIPE, SIG_IGN);
  1437. struct sigaction sa;
  1438. sa.sa_flags = SA_RESTART;
  1439. sa.sa_handler = signals_superhandler;
  1440. sigemptyset (&sa.sa_mask);
  1441. if (sigaction (SIGWINCH, &sa, NULL) == -1
  1442. || sigaction (SIGINT, &sa, NULL) == -1
  1443. || sigaction (SIGTERM, &sa, NULL) == -1)
  1444. exit_fatal ("sigaction: %s", strerror (errno));
  1445. }
  1446. // --- Initialisation, event handling ------------------------------------------
  1447. static void
  1448. app_on_tty_readable (const struct pollfd *fd, void *user_data)
  1449. {
  1450. (void) user_data;
  1451. if (fd->revents & ~(POLLIN | POLLHUP | POLLERR))
  1452. print_debug ("fd %d: unexpected revents: %d", fd->fd, fd->revents);
  1453. poller_timer_reset (&g_ctx.tk_timer);
  1454. termo_advisereadable (g_ctx.tk);
  1455. termo_key_t event;
  1456. termo_result_t res;
  1457. while ((res = termo_getkey (g_ctx.tk, &event)) == TERMO_RES_KEY)
  1458. {
  1459. int y, x, button;
  1460. termo_mouse_event_t type;
  1461. bool success;
  1462. if (termo_interpret_mouse (g_ctx.tk, &event, &type, &button, &y, &x))
  1463. success = app_process_mouse (type, y, x, button);
  1464. else
  1465. success = app_process_termo_event (&event);
  1466. if (!success)
  1467. beep ();
  1468. }
  1469. if (res == TERMO_RES_AGAIN)
  1470. poller_timer_set (&g_ctx.tk_timer, termo_get_waittime (g_ctx.tk));
  1471. else if (res == TERMO_RES_ERROR || res == TERMO_RES_EOF)
  1472. app_quit ();
  1473. }
  1474. static void
  1475. app_on_key_timer (void *user_data)
  1476. {
  1477. (void) user_data;
  1478. termo_key_t event;
  1479. if (termo_getkey_force (g_ctx.tk, &event) == TERMO_RES_KEY)
  1480. if (!app_process_termo_event (&event))
  1481. app_quit ();
  1482. }
  1483. static void
  1484. app_on_signal_pipe_readable (const struct pollfd *fd, void *user_data)
  1485. {
  1486. (void) user_data;
  1487. char id = 0;
  1488. (void) read (fd->fd, &id, 1);
  1489. if (g_termination_requested && !g_ctx.quitting)
  1490. app_quit ();
  1491. if (g_winch_received)
  1492. {
  1493. g_winch_received = false;
  1494. update_curses_terminal_size ();
  1495. app_fix_view_range ();
  1496. app_invalidate ();
  1497. }
  1498. }
  1499. static void
  1500. app_log_handler (void *user_data, const char *quote, const char *fmt,
  1501. va_list ap)
  1502. {
  1503. // We certainly don't want to end up in a possibly infinite recursion
  1504. static bool in_processing;
  1505. if (in_processing)
  1506. return;
  1507. in_processing = true;
  1508. struct str message = str_make ();
  1509. str_append (&message, quote);
  1510. str_append_vprintf (&message, fmt, ap);
  1511. // If the standard error output isn't redirected, try our best at showing
  1512. // the message to the user
  1513. if (!isatty (STDERR_FILENO))
  1514. fprintf (stderr, "%s\n", message.str);
  1515. else
  1516. {
  1517. free (g_ctx.message);
  1518. g_ctx.message = xstrdup (message.str);
  1519. g_ctx.message_attr = (intptr_t) user_data;
  1520. app_invalidate ();
  1521. }
  1522. str_free (&message);
  1523. in_processing = false;
  1524. }
  1525. static void
  1526. app_init_poller_events (void)
  1527. {
  1528. g_ctx.signal_event = poller_fd_make (&g_ctx.poller, g_signal_pipe[0]);
  1529. g_ctx.signal_event.dispatcher = app_on_signal_pipe_readable;
  1530. poller_fd_set (&g_ctx.signal_event, POLLIN);
  1531. g_ctx.tty_event = poller_fd_make (&g_ctx.poller, STDIN_FILENO);
  1532. g_ctx.tty_event.dispatcher = app_on_tty_readable;
  1533. poller_fd_set (&g_ctx.tty_event, POLLIN);
  1534. g_ctx.tk_timer = poller_timer_make (&g_ctx.poller);
  1535. g_ctx.tk_timer.dispatcher = app_on_key_timer;
  1536. g_ctx.refresh_event = poller_idle_make (&g_ctx.poller);
  1537. g_ctx.refresh_event.dispatcher = app_on_refresh;
  1538. }
  1539. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1540. /// Decode size arguments according to similar rules to those that dd(1) uses;
  1541. /// we support octal and hexadecimal numbers but they clash with suffixes
  1542. static bool
  1543. decode_size (const char *s, int64_t *out)
  1544. {
  1545. char *end;
  1546. errno = 0;
  1547. int64_t n = strtol (s, &end, 0);
  1548. if (errno != 0 || end == s || n < 0)
  1549. return false;
  1550. int64_t f = 1;
  1551. switch (*end)
  1552. {
  1553. case 'c': f = 1 << 0; end++; break;
  1554. case 'w': f = 1 << 1; end++; break;
  1555. case 'b': f = 1 << 9; end++; break;
  1556. case 'K': f = 1 << 10; if (*++end == 'B') { f = 1e3; end++; } break;
  1557. case 'M': f = 1 << 20; if (*++end == 'B') { f = 1e6; end++; } break;
  1558. case 'G': f = 1 << 30; if (*++end == 'B') { f = 1e9; end++; } break;
  1559. }
  1560. if (*end || n > INT64_MAX / f)
  1561. return false;
  1562. *out = n * f;
  1563. return true;
  1564. }
  1565. int
  1566. main (int argc, char *argv[])
  1567. {
  1568. static const struct opt opts[] =
  1569. {
  1570. { 'd', "debug", NULL, 0, "run in debug mode" },
  1571. { 'h', "help", NULL, 0, "display this help and exit" },
  1572. { 'V', "version", NULL, 0, "output version information and exit" },
  1573. { 'o', "offset", "OFFSET", 0, "offset within the file" },
  1574. { 's', "size", "SIZE", 0, "size limit (1G by default)" },
  1575. #ifdef HAVE_LUA
  1576. { 't', "type", "TYPE", 0, "force interpretation as the given type" },
  1577. #endif // HAVE_LUA
  1578. { 0, NULL, NULL, 0, NULL }
  1579. };
  1580. struct opt_handler oh =
  1581. opt_handler_make (argc, argv, opts, "[FILE]", "Hex viewer.");
  1582. int64_t size_limit = 1 << 30;
  1583. const char *forced_type = NULL;
  1584. int c;
  1585. while ((c = opt_handler_get (&oh)) != -1)
  1586. switch (c)
  1587. {
  1588. case 'd':
  1589. g_debug_mode = true;
  1590. break;
  1591. case 'h':
  1592. opt_handler_usage (&oh, stdout);
  1593. exit (EXIT_SUCCESS);
  1594. case 'V':
  1595. printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
  1596. exit (EXIT_SUCCESS);
  1597. case 'o':
  1598. if (!decode_size (optarg, &g_ctx.data_offset))
  1599. exit_fatal ("invalid offset specified");
  1600. break;
  1601. case 's':
  1602. if (!decode_size (optarg, &size_limit))
  1603. exit_fatal ("invalid size limit specified");
  1604. break;
  1605. case 't':
  1606. forced_type = optarg;
  1607. break;
  1608. default:
  1609. print_error ("wrong options");
  1610. opt_handler_usage (&oh, stderr);
  1611. exit (EXIT_FAILURE);
  1612. }
  1613. argc -= optind;
  1614. argv += optind;
  1615. #ifdef HAVE_LUA
  1616. // We do it at this questionable location to catch plugin failure before
  1617. // we read potentially hundreds of megabytes of data in
  1618. app_lua_init ();
  1619. if (forced_type && !strcmp (forced_type, "list"))
  1620. {
  1621. struct str_map_iter iter = str_map_iter_make (&g_ctx.coders);
  1622. while (str_map_iter_next (&iter))
  1623. puts (iter.link->key);
  1624. exit (EXIT_SUCCESS);
  1625. }
  1626. #endif // HAVE_LUA
  1627. // When no filename is given, read from stdin and replace it with the tty
  1628. int input_fd;
  1629. if (argc == 0)
  1630. {
  1631. if ((input_fd = dup (STDIN_FILENO)) < 0)
  1632. exit_fatal ("cannot read input: %s", strerror (errno));
  1633. close (STDIN_FILENO);
  1634. if (open ("/dev/tty", O_RDWR) != STDIN_FILENO)
  1635. exit_fatal ("cannot open the terminal: %s", strerror (errno));
  1636. }
  1637. else if (argc == 1)
  1638. {
  1639. g_ctx.filename = xstrdup (argv[0]);
  1640. if ((input_fd = open (argv[0], O_RDONLY)) < 0)
  1641. exit_fatal ("cannot open `%s': %s", argv[0], strerror (errno));
  1642. }
  1643. else
  1644. {
  1645. opt_handler_usage (&oh, stderr);
  1646. exit (EXIT_FAILURE);
  1647. }
  1648. opt_handler_free (&oh);
  1649. // Seek in the file or pipe however we can
  1650. static char seek_buf[8192];
  1651. if (lseek (input_fd, g_ctx.data_offset, SEEK_SET) == (off_t) -1)
  1652. for (uint64_t remaining = g_ctx.data_offset; remaining; )
  1653. {
  1654. ssize_t n_read = read (input_fd,
  1655. seek_buf, MIN (remaining, sizeof seek_buf));
  1656. if (n_read <= 0)
  1657. exit_fatal ("cannot seek: %s", strerror (errno));
  1658. remaining -= n_read;
  1659. }
  1660. // Read up to "size_limit" bytes of data into a buffer
  1661. struct str buf = str_make ();
  1662. while (buf.len < (size_t) size_limit)
  1663. {
  1664. str_reserve (&buf, 8192);
  1665. ssize_t n_read = read (input_fd, buf.str + buf.len,
  1666. MIN (size_limit - buf.len, buf.alloc - buf.len));
  1667. if (!n_read)
  1668. break;
  1669. if (n_read == -1)
  1670. exit_fatal ("cannot read input: %s", strerror (errno));
  1671. buf.len += n_read;
  1672. }
  1673. g_ctx.data = (uint8_t *) buf.str;
  1674. g_ctx.data_len = buf.len;
  1675. g_ctx.view_top = g_ctx.data_offset / ROW_SIZE * ROW_SIZE;
  1676. g_ctx.view_cursor = g_ctx.data_offset;
  1677. // We only need to convert to and from the terminal encoding
  1678. if (!setlocale (LC_CTYPE, ""))
  1679. print_warning ("failed to set the locale");
  1680. app_init_context ();
  1681. #ifdef HAVE_LUA
  1682. // TODO: eventually we should do this in a separate thread after load
  1683. // as it may take a long time (-> responsivity) and once we allow the user
  1684. // to edit the file, each change will need a background rescan
  1685. lua_pushcfunction (g_ctx.L, app_lua_error_handler);
  1686. lua_pushcfunction (g_ctx.L, app_lua_chunk_decode);
  1687. struct app_lua_chunk *chunk = app_lua_chunk_new (g_ctx.L);
  1688. chunk->offset = g_ctx.data_offset;
  1689. chunk->len = g_ctx.data_len;
  1690. if (forced_type)
  1691. lua_pushstring (g_ctx.L, forced_type);
  1692. else
  1693. lua_pushnil (g_ctx.L);
  1694. if (lua_pcall (g_ctx.L, 2, 0, -4))
  1695. exit_fatal ("Lua: decoding failed: %s", lua_tostring (g_ctx.L, -1));
  1696. lua_pop (g_ctx.L, 1);
  1697. #endif // HAVE_LUA
  1698. app_flatten_marks ();
  1699. app_load_configuration ();
  1700. app_init_terminal ();
  1701. signals_setup_handlers ();
  1702. app_init_poller_events ();
  1703. app_invalidate ();
  1704. app_init_bindings ();
  1705. // Redirect all messages from liberty so that they don't disrupt display
  1706. g_log_message_real = app_log_handler;
  1707. g_ctx.polling = true;
  1708. while (g_ctx.polling)
  1709. poller_run (&g_ctx.poller);
  1710. endwin ();
  1711. g_log_message_real = log_message_stdio;
  1712. app_free_context ();
  1713. #ifdef HAVE_LUA
  1714. str_map_free (&g_ctx.coders);
  1715. lua_close (g_ctx.L);
  1716. #endif // HAVE_LUA
  1717. return 0;
  1718. }