Compare commits
3 Commits
printf_flo
...
strbuf
Author | SHA1 | Date | |
---|---|---|---|
aad561bcdd | |||
1bd8e1caa3 | |||
a41b39b23b |
@ -27,6 +27,7 @@ src = [
|
|||||||
'src/util/log.c',
|
'src/util/log.c',
|
||||||
'src/util/net.c',
|
'src/util/net.c',
|
||||||
'src/util/process.c',
|
'src/util/process.c',
|
||||||
|
'src/util/strbuf.c',
|
||||||
'src/util/str_util.c',
|
'src/util/str_util.c',
|
||||||
'src/util/thread.c',
|
'src/util/thread.c',
|
||||||
'src/util/tick.c',
|
'src/util/tick.c',
|
||||||
@ -186,6 +187,7 @@ if get_option('buildtype') == 'debug'
|
|||||||
'tests/test_cli.c',
|
'tests/test_cli.c',
|
||||||
'src/cli.c',
|
'src/cli.c',
|
||||||
'src/options.c',
|
'src/options.c',
|
||||||
|
'src/util/strbuf.c',
|
||||||
'src/util/str_util.c',
|
'src/util/str_util.c',
|
||||||
]],
|
]],
|
||||||
['test_clock', [
|
['test_clock', [
|
||||||
@ -195,6 +197,7 @@ if get_option('buildtype') == 'debug'
|
|||||||
['test_control_msg_serialize', [
|
['test_control_msg_serialize', [
|
||||||
'tests/test_control_msg_serialize.c',
|
'tests/test_control_msg_serialize.c',
|
||||||
'src/control_msg.c',
|
'src/control_msg.c',
|
||||||
|
'src/util/strbuf.c',
|
||||||
'src/util/str_util.c',
|
'src/util/str_util.c',
|
||||||
]],
|
]],
|
||||||
['test_device_msg_deserialize', [
|
['test_device_msg_deserialize', [
|
||||||
@ -204,8 +207,13 @@ if get_option('buildtype') == 'debug'
|
|||||||
['test_queue', [
|
['test_queue', [
|
||||||
'tests/test_queue.c',
|
'tests/test_queue.c',
|
||||||
]],
|
]],
|
||||||
|
['test_strbuf', [
|
||||||
|
'tests/test_strbuf.c',
|
||||||
|
'src/util/strbuf.c',
|
||||||
|
]],
|
||||||
['test_strutil', [
|
['test_strutil', [
|
||||||
'tests/test_strutil.c',
|
'tests/test_strutil.c',
|
||||||
|
'src/util/strbuf.c',
|
||||||
'src/util/str_util.c',
|
'src/util/str_util.c',
|
||||||
]],
|
]],
|
||||||
]
|
]
|
||||||
|
236
app/src/cli.c
236
app/src/cli.c
@ -8,13 +8,243 @@
|
|||||||
|
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
#include "util/strbuf.h"
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
#define STR_IMPL_(x) #x
|
#define STR_IMPL_(x) #x
|
||||||
#define STR(x) STR_IMPL_(x)
|
#define STR(x) STR_IMPL_(x)
|
||||||
|
|
||||||
|
struct sc_option {
|
||||||
|
char shortopt;
|
||||||
|
const char *longopt;
|
||||||
|
const char *argdesc;
|
||||||
|
bool optional_arg;
|
||||||
|
const char *text;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct sc_option options[] = {
|
||||||
|
{
|
||||||
|
.longopt = "always-on-top",
|
||||||
|
.text = "Make scrcpy window always on top (above other windows).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'b',
|
||||||
|
.longopt = "bit-rate",
|
||||||
|
.argdesc = "value",
|
||||||
|
.text = "Encode the video at the gitven bit-rate, expressed in bits/s. "
|
||||||
|
"Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
||||||
|
"Default is " STR(DEFAULT_BIT_RATE) ".",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt = "codec-options",
|
||||||
|
.argdesc = "key[:type]=value[,...]",
|
||||||
|
.text = "Set a list of comma-separated key:type=value options for the "
|
||||||
|
"device encoder.\n"
|
||||||
|
"The possible values for 'type' are 'int' (default), 'long', "
|
||||||
|
"'float' and 'string'.\n"
|
||||||
|
"The list of possible codec options is available in the "
|
||||||
|
"Android documentation: "
|
||||||
|
"<https://d.android.com/reference/android/media/MediaFormat>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt = "crop",
|
||||||
|
.argdesc = "width:height:x:y",
|
||||||
|
.text = "Crop the device screen on the server.\n"
|
||||||
|
"The values are expressed in the device natural orientation "
|
||||||
|
"(typically, portrait for a phone, landscape for a tablet). "
|
||||||
|
"Any --max-size value is cmoputed on the cropped size.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt = "disable-screensaver",
|
||||||
|
.text = "Disable screensaver while scrcpy is running.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt = "display",
|
||||||
|
.argdesc = "id",
|
||||||
|
.text = "Specify the display id to mirror.\n"
|
||||||
|
"\n"
|
||||||
|
"The list of possible display ids can be listed by:\n"
|
||||||
|
" adb shell dumpsys display\n"
|
||||||
|
"(search \"mDisplayId=\" in the output)\n"
|
||||||
|
"\n"
|
||||||
|
"Default is 0.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt = "display-buffer",
|
||||||
|
.argdesc = "ms",
|
||||||
|
.text = "Add a buffering delay (in milliseconds) before displaying. "
|
||||||
|
"This increases latency to compensate for jitter.\n"
|
||||||
|
"Default is 0 (no buffering).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt = "encoder",
|
||||||
|
.argdesc = "name",
|
||||||
|
.text = "Use a specific MediaCodec encoder (must be a H.264 encoder).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt = "force-adb-forward",
|
||||||
|
.text = "Do not attempt to use \"adb reverse\" to connect to the "
|
||||||
|
"device.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt = "forward-all-clicks",
|
||||||
|
.text = "By default, right-click triggers BACK (or POWER on) and "
|
||||||
|
"middle-click triggers HOME. This option disables these "
|
||||||
|
"shortcuts and forwards the clicks to the device instead.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'f',
|
||||||
|
.longopt = "fullscreen",
|
||||||
|
.text = "Start in fullscreen.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'K',
|
||||||
|
.longopt = "hid-keyboard",
|
||||||
|
.text = "Simulate a physical keyboard by using HID over AOAv2.\n"
|
||||||
|
"It provides a better experience for IME users, and allows to "
|
||||||
|
"generate non-ASCII characters, contrary to the default "
|
||||||
|
"injection method.\n"
|
||||||
|
"It may only work over USB, and is currently only supported "
|
||||||
|
"on Linux.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'h',
|
||||||
|
.longopt = "help",
|
||||||
|
.text = "Print this help.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt = "legacy-paste",
|
||||||
|
.text = "Inject computer clipboard text as a sequence of key events "
|
||||||
|
"on Ctrl+v (like MOD+Shift+v).\n"
|
||||||
|
"This is a workaround for some devices not behaving as "
|
||||||
|
"expected when setting the device clipboard programmatically.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt = "lock-video-orientation",
|
||||||
|
.argdesc = "value",
|
||||||
|
.optional_arg = true,
|
||||||
|
.text = "Lock video orientation to value.\n"
|
||||||
|
"Possible values are \"unlocked\", \"initial\" (locked to the "
|
||||||
|
"initial orientation), 0, 1, 2 and 3. Natural device "
|
||||||
|
"orientation is 0, and each increment adds a 90 degrees "
|
||||||
|
"rotation counterclockwise.\n"
|
||||||
|
"Default is \"unlocked\".\n"
|
||||||
|
"Passing the option without argument is equivalent to passing "
|
||||||
|
"\"initial\".",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt = "max-fps",
|
||||||
|
.argdesc = "value",
|
||||||
|
.text = "Limit the frame rate of screen capture (officially supported "
|
||||||
|
"since Android 10, but may work on earlier versions).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'm',
|
||||||
|
.longopt = "max-size",
|
||||||
|
.argdesc = "value",
|
||||||
|
.text = "Limit both the width and height of the video to value. The "
|
||||||
|
"other dimension is computed so that the device aspect-ratio "
|
||||||
|
"is preserved.\n"
|
||||||
|
"Default is 0 (unlimited).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'n',
|
||||||
|
.longopt = "no-control",
|
||||||
|
.text = "Disable device control (mirror the device in read-only).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'N',
|
||||||
|
.longopt = "no-display",
|
||||||
|
.text = "Do not display device (only when screen recording).",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_option_usage_header(const struct sc_option *opt) {
|
||||||
|
struct sc_strbuf buf;
|
||||||
|
if (!sc_strbuf_init(&buf, 128)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = true;
|
||||||
|
(void) ok; // only used for assertions
|
||||||
|
|
||||||
|
if (opt->shortopt) {
|
||||||
|
ok = sc_strbuf_append_char(&buf, '-');
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
|
||||||
|
ok = sc_strbuf_append_char(&buf, opt->shortopt);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
if (opt->longopt) {
|
||||||
|
ok = sc_strbuf_append_staticstr(&buf, ", ");
|
||||||
|
assert(ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt->longopt) {
|
||||||
|
ok = sc_strbuf_append_staticstr(&buf, "--");
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
if (!sc_strbuf_append_str(&buf, opt->longopt)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt->argdesc) {
|
||||||
|
if (opt->optional_arg && !sc_strbuf_append_char(&buf, '[')) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sc_strbuf_append_char(&buf, '=')) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sc_strbuf_append_str(&buf, opt->argdesc)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt->optional_arg && !sc_strbuf_append_char(&buf, ']')) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, " %s\n", buf.s);
|
||||||
|
free(buf.s);
|
||||||
|
return;
|
||||||
|
|
||||||
|
error:
|
||||||
|
fprintf(stderr, "<ERROR>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_option_usage(const struct sc_option *opt, unsigned cols) {
|
||||||
|
assert(cols > 20); // We need some space
|
||||||
|
assert(opt->text);
|
||||||
|
|
||||||
|
print_option_usage_header(opt);
|
||||||
|
|
||||||
|
char *text = wrap_lines(opt->text, cols, 8);
|
||||||
|
if (!text) {
|
||||||
|
fprintf(stderr, "<ERROR>\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "%s\n", text);
|
||||||
|
free(text);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
scrcpy_print_usage(const char *arg0) {
|
scrcpy_print_usage(const char *arg0) {
|
||||||
|
fprintf(stderr, "Usage: %s [options]\n"
|
||||||
|
"\n"
|
||||||
|
"Options:\n", arg0);
|
||||||
|
for (size_t i = 0; i < ARRAY_LEN(options); ++i) {
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
print_option_usage(&options[i], 80);
|
||||||
|
}
|
||||||
|
if (false)
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Usage: %s [options]\n"
|
"Usage: %s [options]\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -66,12 +296,12 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
" --force-adb-forward\n"
|
" --force-adb-forward\n"
|
||||||
" Do not attempt to use \"adb reverse\" to connect to the\n"
|
" Do not attempt to use \"adb reverse\" to connect to the\n"
|
||||||
" the device.\n"
|
" device.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --forward-all-clicks\n"
|
" --forward-all-clicks\n"
|
||||||
" By default, right-click triggers BACK (or POWER on) and\n"
|
" By default, right-click triggers BACK (or POWER on) and\n"
|
||||||
" middle-click triggers HOME. This option disables these\n"
|
" middle-click triggers HOME. This option disables these\n"
|
||||||
" shortcuts and forward the clicks to the device instead.\n"
|
" shortcuts and forwards the clicks to the device instead.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -f, --fullscreen\n"
|
" -f, --fullscreen\n"
|
||||||
" Start in fullscreen.\n"
|
" Start in fullscreen.\n"
|
||||||
@ -118,7 +348,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
" -N, --no-display\n"
|
" -N, --no-display\n"
|
||||||
" Do not display device (only when screen recording is\n"
|
" Do not display device (only when screen recording is\n"
|
||||||
" enabled).\n"
|
" enabled).\n" // or V4L2 sink
|
||||||
"\n"
|
"\n"
|
||||||
" --no-key-repeat\n"
|
" --no-key-repeat\n"
|
||||||
" Do not forward repeated key events when a key is held down.\n"
|
" Do not forward repeated key events when a key is held down.\n"
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
#include "str_util.h"
|
#include "str_util.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include "util/strbuf.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
@ -209,3 +211,76 @@ utf8_from_wide_char(const wchar_t *ws) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
char *wrap_lines(const char *input, unsigned columns, unsigned indent) {
|
||||||
|
struct sc_strbuf buf;
|
||||||
|
|
||||||
|
// The output string should not be a lot longer than the input string (just
|
||||||
|
// a few '\n' added), so this initial capacity should almost always avoid
|
||||||
|
// internal realloc() in string buffer
|
||||||
|
size_t cap = strlen(input) * 3 / 2;
|
||||||
|
|
||||||
|
if (!sc_strbuf_init(&buf, cap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(indent < columns);
|
||||||
|
|
||||||
|
#define APPEND(S,N) if (!sc_strbuf_append(&buf, S, N)) goto error
|
||||||
|
#define APPEND_CHAR(C) if (!sc_strbuf_append_char(&buf, C)) goto error
|
||||||
|
#define APPEND_N(C,N) if (!sc_strbuf_append_n(&buf, C, N)) goto error
|
||||||
|
#define APPEND_INDENT() if (indent) APPEND_N(' ', indent)
|
||||||
|
|
||||||
|
APPEND_INDENT();
|
||||||
|
|
||||||
|
// The last separator encountered, it must be inserted only conditionnaly,
|
||||||
|
// depending on the next token
|
||||||
|
char pending = 0;
|
||||||
|
|
||||||
|
// col tracks the current column in the current line
|
||||||
|
size_t col = indent;
|
||||||
|
while (*input) {
|
||||||
|
size_t sep_idx = strcspn(input, "\n ");
|
||||||
|
bool wrap = col + sep_idx > columns;
|
||||||
|
|
||||||
|
char sep = input[sep_idx];
|
||||||
|
if (sep == ' ')
|
||||||
|
sep = ' ';
|
||||||
|
|
||||||
|
if (wrap) {
|
||||||
|
APPEND_CHAR('\n');
|
||||||
|
APPEND_INDENT();
|
||||||
|
col = indent;
|
||||||
|
} else if (pending) {
|
||||||
|
APPEND_CHAR(pending);
|
||||||
|
++col;
|
||||||
|
if (pending == '\n')
|
||||||
|
{
|
||||||
|
APPEND_INDENT();
|
||||||
|
col = indent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sep_idx) {
|
||||||
|
APPEND(input, sep_idx);
|
||||||
|
col += sep_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
pending = sep;
|
||||||
|
|
||||||
|
input += sep_idx;
|
||||||
|
if (*input != '\0') {
|
||||||
|
// Skip the separator
|
||||||
|
++input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pending)
|
||||||
|
APPEND_CHAR(pending);
|
||||||
|
|
||||||
|
return buf.s;
|
||||||
|
|
||||||
|
error:
|
||||||
|
free(buf.s);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
@ -62,4 +62,8 @@ char *
|
|||||||
utf8_from_wide_char(const wchar_t *s);
|
utf8_from_wide_char(const wchar_t *s);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Wrap input lines at words boundaries (spaces) so that they fit in 'columns'
|
||||||
|
// columns, left-indented by 'indent' spaces
|
||||||
|
char *wrap_lines(const char *input, unsigned columns, unsigned indent);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
77
app/src/util/strbuf.c
Normal file
77
app/src/util/strbuf.c
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#include "strbuf.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap) {
|
||||||
|
buf->s = malloc(init_cap + 1); // +1 for '\0'
|
||||||
|
if (!buf->s) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->len = 0;
|
||||||
|
buf->cap = init_cap;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_strbuf_reserve(struct sc_strbuf *buf, size_t len) {
|
||||||
|
if (buf->len + len > buf->cap) {
|
||||||
|
fprintf(stderr, "realloc\n");
|
||||||
|
size_t new_cap = buf->cap * 3 / 2 + len;
|
||||||
|
char *s = realloc(buf->s, new_cap + 1); // +1 for '\0'
|
||||||
|
if (!s) {
|
||||||
|
// Leave the old buf->s
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
buf->s = s;
|
||||||
|
buf->cap = new_cap;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len) {
|
||||||
|
assert(s);
|
||||||
|
assert(*s);
|
||||||
|
assert(strlen(s) >= len);
|
||||||
|
if (!sc_strbuf_reserve(buf, len)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&buf->s[buf->len], s, len);
|
||||||
|
buf->len += len;
|
||||||
|
buf->s[buf->len] = '\0';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_strbuf_append_char(struct sc_strbuf *buf, const char c) {
|
||||||
|
if (!sc_strbuf_reserve(buf, 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->s[buf->len] = c;
|
||||||
|
buf->len ++;
|
||||||
|
buf->s[buf->len] = '\0';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n) {
|
||||||
|
if (!sc_strbuf_reserve(buf, n)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&buf->s[buf->len], c, n);
|
||||||
|
buf->len += n;
|
||||||
|
buf->s[buf->len] = '\0';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
37
app/src/util/strbuf.h
Normal file
37
app/src/util/strbuf.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#ifndef SC_STRBUF_H
|
||||||
|
#define SC_STRBUF_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
struct sc_strbuf {
|
||||||
|
char *s;
|
||||||
|
size_t len;
|
||||||
|
size_t cap;
|
||||||
|
};
|
||||||
|
|
||||||
|
// buf->s must be manually freed by the caller
|
||||||
|
bool
|
||||||
|
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_strbuf_append_char(struct sc_strbuf *buf, const char c);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n);
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
sc_strbuf_append_str(struct sc_strbuf *buf, const char *s) {
|
||||||
|
return sc_strbuf_append(buf, s, strlen(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append static string (i.e. the string size is known at compile time, for
|
||||||
|
// example a string literal)
|
||||||
|
#define sc_strbuf_append_staticstr(BUF, S) \
|
||||||
|
sc_strbuf_append(BUF, S, sizeof(S) - 1)
|
||||||
|
|
||||||
|
#endif
|
42
app/tests/test_strbuf.c
Normal file
42
app/tests/test_strbuf.c
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "util/strbuf.h"
|
||||||
|
|
||||||
|
static void test_strbuf_simple(void) {
|
||||||
|
struct sc_strbuf buf;
|
||||||
|
bool ok = sc_strbuf_init(&buf, 10);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
ok = sc_strbuf_append_staticstr(&buf, "Hello");
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
ok = sc_strbuf_append_char(&buf, ' ');
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
ok = sc_strbuf_append_staticstr(&buf, "world");
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
ok = sc_strbuf_append_staticstr(&buf, "!\n");
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
ok = sc_strbuf_append_staticstr(&buf, "This is a test");
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
ok = sc_strbuf_append_n(&buf, '.', 3);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
assert(!strcmp(buf.s, "Hello world!\nThis is a test..."));
|
||||||
|
free(buf.s);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
|
test_strbuf_simple();
|
||||||
|
return 0;
|
||||||
|
}
|
@ -299,6 +299,45 @@ static void test_strlist_contains(void) {
|
|||||||
assert(strlist_contains("xyz", '\0', "xyz"));
|
assert(strlist_contains("xyz", '\0', "xyz"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_wrap_lines(void) {
|
||||||
|
const char *s = "This is a text to test line wrapping. The lines must be "
|
||||||
|
"wrapped either at a space or a line break.\n"
|
||||||
|
"\n"
|
||||||
|
"This rectangle must remains a rectangle because it is "
|
||||||
|
"drawn in lines having lengths lower than the specified "
|
||||||
|
"number of columns:\n"
|
||||||
|
" +----+\n"
|
||||||
|
" | |\n"
|
||||||
|
" +----+\n";
|
||||||
|
|
||||||
|
// |---- 1 1 2 2|
|
||||||
|
// |0 5 0 5 0 3| <-- 24 columns
|
||||||
|
const char *expected = " This is a text to\n"
|
||||||
|
" test line wrapping.\n"
|
||||||
|
" The lines must be\n"
|
||||||
|
" wrapped either at a\n"
|
||||||
|
" space or a line\n"
|
||||||
|
" break.\n"
|
||||||
|
" \n"
|
||||||
|
" This rectangle must\n"
|
||||||
|
" remains a rectangle\n"
|
||||||
|
" because it is drawn\n"
|
||||||
|
" in lines having\n"
|
||||||
|
" lengths lower than\n"
|
||||||
|
" the specified number\n"
|
||||||
|
" of columns:\n"
|
||||||
|
" +----+\n"
|
||||||
|
" | |\n"
|
||||||
|
" +----+\n";
|
||||||
|
|
||||||
|
char *formatted = wrap_lines(s, 24, 4);
|
||||||
|
assert(formatted);
|
||||||
|
|
||||||
|
assert(!strcmp(formatted, expected));
|
||||||
|
|
||||||
|
free(formatted);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
(void) argc;
|
(void) argc;
|
||||||
(void) argv;
|
(void) argv;
|
||||||
@ -317,5 +356,6 @@ int main(int argc, char *argv[]) {
|
|||||||
test_parse_integers();
|
test_parse_integers();
|
||||||
test_parse_integer_with_suffix();
|
test_parse_integer_with_suffix();
|
||||||
test_strlist_contains();
|
test_strlist_contains();
|
||||||
|
test_wrap_lines();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user