Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
b777c1181f | |||
f197c1181e | |||
7a733328bc | |||
38332f683c | |||
3f51a2ab43 | |||
74ab0a4df8 | |||
f59e9e3cb0 | |||
9ec3406568 | |||
6dba1922c1 |
@ -24,18 +24,27 @@ src = [
|
||||
'src/server.c',
|
||||
'src/stream.c',
|
||||
'src/video_buffer.c',
|
||||
'src/util/file.c',
|
||||
'src/util/log.c',
|
||||
'src/util/net.c',
|
||||
'src/util/process.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/str_util.c',
|
||||
'src/util/term.c',
|
||||
'src/util/thread.c',
|
||||
'src/util/tick.c',
|
||||
]
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
src += [ 'src/sys/win/process.c' ]
|
||||
src += [
|
||||
'src/sys/win/file.c',
|
||||
'src/sys/win/process.c',
|
||||
]
|
||||
else
|
||||
src += [ 'src/sys/unix/process.c' ]
|
||||
src += [
|
||||
'src/sys/unix/file.c',
|
||||
'src/sys/unix/process.c',
|
||||
]
|
||||
endif
|
||||
|
||||
v4l2_support = host_machine.system() == 'linux'
|
||||
@ -186,7 +195,9 @@ if get_option('buildtype') == 'debug'
|
||||
'tests/test_cli.c',
|
||||
'src/cli.c',
|
||||
'src/options.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/str_util.c',
|
||||
'src/util/term.c',
|
||||
]],
|
||||
['test_clock', [
|
||||
'tests/test_clock.c',
|
||||
@ -195,6 +206,7 @@ if get_option('buildtype') == 'debug'
|
||||
['test_control_msg_serialize', [
|
||||
'tests/test_control_msg_serialize.c',
|
||||
'src/control_msg.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/str_util.c',
|
||||
]],
|
||||
['test_device_msg_deserialize', [
|
||||
@ -204,8 +216,13 @@ if get_option('buildtype') == 'debug'
|
||||
['test_queue', [
|
||||
'tests/test_queue.c',
|
||||
]],
|
||||
['test_strbuf', [
|
||||
'tests/test_strbuf.c',
|
||||
'src/util/strbuf.c',
|
||||
]],
|
||||
['test_strutil', [
|
||||
'tests/test_strutil.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/str_util.c',
|
||||
]],
|
||||
]
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util/file.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str_util.h"
|
||||
|
||||
@ -68,7 +69,7 @@ show_adb_installation_msg() {
|
||||
{"pacman", "pacman -S android-tools"},
|
||||
};
|
||||
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
|
||||
if (search_executable(pkg_managers[i].binary)) {
|
||||
if (sc_file_executable_exists(pkg_managers[i].binary)) {
|
||||
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
|
||||
return;
|
||||
}
|
||||
|
1189
app/src/cli.c
1189
app/src/cli.c
File diff suppressed because it is too large
Load Diff
@ -8,8 +8,8 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "compat.h"
|
||||
#include "util/file.h"
|
||||
#include "util/log.h"
|
||||
#include "util/process.h"
|
||||
#include "util/str_util.h"
|
||||
|
||||
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
|
||||
@ -46,7 +46,7 @@ get_icon_path(void) {
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
char *icon_path = get_local_file_path(SCRCPY_PORTABLE_ICON_FILENAME);
|
||||
char *icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
|
||||
if (!icon_path) {
|
||||
LOGE("Could not get icon path");
|
||||
return NULL;
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <SDL2/SDL_platform.h>
|
||||
|
||||
#include "adb.h"
|
||||
#include "util/file.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
#include "util/str_util.h"
|
||||
@ -48,7 +49,7 @@ get_server_path(void) {
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
char *server_path = get_local_file_path(SERVER_FILENAME);
|
||||
char *server_path = sc_file_get_local_path(SERVER_FILENAME);
|
||||
if (!server_path) {
|
||||
LOGE("Could not get local file path, "
|
||||
"using " SERVER_FILENAME " from current directory");
|
||||
@ -67,7 +68,7 @@ push_server(const char *serial) {
|
||||
if (!server_path) {
|
||||
return false;
|
||||
}
|
||||
if (!is_regular_file(server_path)) {
|
||||
if (!sc_file_is_regular(server_path)) {
|
||||
LOGE("'%s' does not exist or is not a regular file\n", server_path);
|
||||
free(server_path);
|
||||
return false;
|
||||
|
75
app/src/sys/unix/file.c
Normal file
75
app/src/sys/unix/file.c
Normal file
@ -0,0 +1,75 @@
|
||||
#include "util/file.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
bool
|
||||
sc_file_executable_exists(const char *file) {
|
||||
char *path = getenv("PATH");
|
||||
if (!path)
|
||||
return false;
|
||||
path = strdup(path);
|
||||
if (!path)
|
||||
return false;
|
||||
|
||||
bool ret = false;
|
||||
size_t file_len = strlen(file);
|
||||
char *saveptr;
|
||||
for (char *dir = strtok_r(path, ":", &saveptr); dir;
|
||||
dir = strtok_r(NULL, ":", &saveptr)) {
|
||||
size_t dir_len = strlen(dir);
|
||||
char *fullpath = malloc(dir_len + file_len + 2);
|
||||
if (!fullpath)
|
||||
continue;
|
||||
memcpy(fullpath, dir, dir_len);
|
||||
fullpath[dir_len] = '/';
|
||||
memcpy(fullpath + dir_len + 1, file, file_len + 1);
|
||||
|
||||
struct stat sb;
|
||||
bool fullpath_executable = stat(fullpath, &sb) == 0 &&
|
||||
sb.st_mode & S_IXUSR;
|
||||
free(fullpath);
|
||||
if (fullpath_executable) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *
|
||||
sc_file_get_executable_path(void) {
|
||||
// <https://stackoverflow.com/a/1024937/1987178>
|
||||
#ifdef __linux__
|
||||
char buf[PATH_MAX + 1]; // +1 for the null byte
|
||||
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
|
||||
if (len == -1) {
|
||||
perror("readlink");
|
||||
return NULL;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
return strdup(buf);
|
||||
#else
|
||||
// in practice, we only need this feature for portable builds, only used on
|
||||
// Windows, so we don't care implementing it for every platform
|
||||
// (it's useful to have a working version on Linux for debugging though)
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
sc_file_is_regular(const char *path) {
|
||||
struct stat path_stat;
|
||||
|
||||
if (stat(path, &path_stat)) {
|
||||
perror("stat");
|
||||
return false;
|
||||
}
|
||||
return S_ISREG(path_stat.st_mode);
|
||||
}
|
||||
|
@ -3,53 +3,13 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
search_executable(const char *file) {
|
||||
char *path = getenv("PATH");
|
||||
if (!path)
|
||||
return false;
|
||||
path = strdup(path);
|
||||
if (!path)
|
||||
return false;
|
||||
|
||||
bool ret = false;
|
||||
size_t file_len = strlen(file);
|
||||
char *saveptr;
|
||||
for (char *dir = strtok_r(path, ":", &saveptr); dir;
|
||||
dir = strtok_r(NULL, ":", &saveptr)) {
|
||||
size_t dir_len = strlen(dir);
|
||||
char *fullpath = malloc(dir_len + file_len + 2);
|
||||
if (!fullpath)
|
||||
continue;
|
||||
memcpy(fullpath, dir, dir_len);
|
||||
fullpath[dir_len] = '/';
|
||||
memcpy(fullpath + dir_len + 1, file, file_len + 1);
|
||||
|
||||
struct stat sb;
|
||||
bool fullpath_executable = stat(fullpath, &sb) == 0 &&
|
||||
sb.st_mode & S_IXUSR;
|
||||
free(fullpath);
|
||||
if (fullpath_executable) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum process_result
|
||||
process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
|
||||
int *pipe_stdout, int *pipe_stderr) {
|
||||
@ -232,37 +192,6 @@ process_close(pid_t pid) {
|
||||
process_wait(pid, true); // ignore exit code
|
||||
}
|
||||
|
||||
char *
|
||||
get_executable_path(void) {
|
||||
// <https://stackoverflow.com/a/1024937/1987178>
|
||||
#ifdef __linux__
|
||||
char buf[PATH_MAX + 1]; // +1 for the null byte
|
||||
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
|
||||
if (len == -1) {
|
||||
perror("readlink");
|
||||
return NULL;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
return strdup(buf);
|
||||
#else
|
||||
// in practice, we only need this feature for portable builds, only used on
|
||||
// Windows, so we don't care implementing it for every platform
|
||||
// (it's useful to have a working version on Linux for debugging though)
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
is_regular_file(const char *path) {
|
||||
struct stat path_stat;
|
||||
|
||||
if (stat(path, &path_stat)) {
|
||||
perror("stat");
|
||||
return false;
|
||||
}
|
||||
return S_ISREG(path_stat.st_mode);
|
||||
}
|
||||
|
||||
ssize_t
|
||||
read_pipe(int pipe, char *data, size_t len) {
|
||||
return read(pipe, data, len);
|
||||
|
43
app/src/sys/win/file.c
Normal file
43
app/src/sys/win/file.c
Normal file
@ -0,0 +1,43 @@
|
||||
#include "util/file.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/str_util.h"
|
||||
|
||||
char *
|
||||
sc_file_get_executable_path(void) {
|
||||
HMODULE hModule = GetModuleHandleW(NULL);
|
||||
if (!hModule) {
|
||||
return NULL;
|
||||
}
|
||||
WCHAR buf[MAX_PATH + 1]; // +1 for the null byte
|
||||
int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
|
||||
if (!len) {
|
||||
return NULL;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
return utf8_from_wide_char(buf);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_file_is_regular(const char *path) {
|
||||
wchar_t *wide_path = utf8_to_wide_char(path);
|
||||
if (!wide_path) {
|
||||
LOGC("Could not allocate wide char string");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct _stat path_stat;
|
||||
int r = _wstat(wide_path, &path_stat);
|
||||
free(wide_path);
|
||||
|
||||
if (r) {
|
||||
perror("stat");
|
||||
return false;
|
||||
}
|
||||
return S_ISREG(path_stat.st_mode);
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
#include "util/process.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/str_util.h"
|
||||
@ -174,40 +173,6 @@ process_close(HANDLE handle) {
|
||||
(void) closed;
|
||||
}
|
||||
|
||||
char *
|
||||
get_executable_path(void) {
|
||||
HMODULE hModule = GetModuleHandleW(NULL);
|
||||
if (!hModule) {
|
||||
return NULL;
|
||||
}
|
||||
WCHAR buf[MAX_PATH + 1]; // +1 for the null byte
|
||||
int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
|
||||
if (!len) {
|
||||
return NULL;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
return utf8_from_wide_char(buf);
|
||||
}
|
||||
|
||||
bool
|
||||
is_regular_file(const char *path) {
|
||||
wchar_t *wide_path = utf8_to_wide_char(path);
|
||||
if (!wide_path) {
|
||||
LOGC("Could not allocate wide char string");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct _stat path_stat;
|
||||
int r = _wstat(wide_path, &path_stat);
|
||||
free(wide_path);
|
||||
|
||||
if (r) {
|
||||
perror("stat");
|
||||
return false;
|
||||
}
|
||||
return S_ISREG(path_stat.st_mode);
|
||||
}
|
||||
|
||||
ssize_t
|
||||
read_pipe(HANDLE pipe, char *data, size_t len) {
|
||||
DWORD r;
|
||||
|
48
app/src/util/file.c
Normal file
48
app/src/util/file.c
Normal file
@ -0,0 +1,48 @@
|
||||
#include "file.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
char *
|
||||
sc_file_get_local_path(const char *name) {
|
||||
char *executable_path = sc_file_get_executable_path();
|
||||
if (!executable_path) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// dirname() does not work correctly everywhere, so get the parent
|
||||
// directory manually.
|
||||
// See <https://github.com/Genymobile/scrcpy/issues/2619>
|
||||
char *p = strrchr(executable_path, SC_PATH_SEPARATOR);
|
||||
if (!p) {
|
||||
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
|
||||
executable_path, SC_PATH_SEPARATOR);
|
||||
free(executable_path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*p = '\0'; // modify executable_path in place
|
||||
char *dir = executable_path;
|
||||
size_t dirlen = strlen(dir);
|
||||
size_t namelen = strlen(name);
|
||||
|
||||
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
|
||||
char *file_path = malloc(len);
|
||||
if (!file_path) {
|
||||
LOGE("Could not alloc path");
|
||||
free(executable_path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(file_path, dir, dirlen);
|
||||
file_path[dirlen] = SC_PATH_SEPARATOR;
|
||||
// namelen + 1 to copy the final '\0'
|
||||
memcpy(&file_path[dirlen + 1], name, namelen + 1);
|
||||
|
||||
free(executable_path);
|
||||
|
||||
return file_path;
|
||||
}
|
||||
|
49
app/src/util/file.h
Normal file
49
app/src/util/file.h
Normal file
@ -0,0 +1,49 @@
|
||||
#ifndef SC_FILE_H
|
||||
#define SC_FILE_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
# define SC_PATH_SEPARATOR '\\'
|
||||
#else
|
||||
# define SC_PATH_SEPARATOR '/'
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
/**
|
||||
* Indicate if an executable exists using $PATH
|
||||
*
|
||||
* In practice, it is only used to know if a package manager is available on
|
||||
* the system. It is only implemented on Linux.
|
||||
*/
|
||||
bool
|
||||
sc_file_executable_exists(const char *file);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Return the absolute path of the executable (the scrcpy binary)
|
||||
*
|
||||
* The result must be freed by the caller using free(). It may return NULL on
|
||||
* error.
|
||||
*/
|
||||
char *
|
||||
sc_file_get_executable_path(void);
|
||||
|
||||
/**
|
||||
* Return the absolute path of a file in the same directory as the executable
|
||||
*
|
||||
* The result must be freed by the caller using free(). It may return NULL on
|
||||
* error.
|
||||
*/
|
||||
char *
|
||||
sc_file_get_local_path(const char *name);
|
||||
|
||||
/**
|
||||
* Indicate if the file exists and is not a directory
|
||||
*/
|
||||
bool
|
||||
sc_file_is_regular(const char *path);
|
||||
|
||||
#endif
|
@ -21,47 +21,6 @@ process_check_success(process_t proc, const char *name, bool close) {
|
||||
return true;
|
||||
}
|
||||
|
||||
char *
|
||||
get_local_file_path(const char *name) {
|
||||
char *executable_path = get_executable_path();
|
||||
if (!executable_path) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// dirname() does not work correctly everywhere, so get the parent
|
||||
// directory manually.
|
||||
// See <https://github.com/Genymobile/scrcpy/issues/2619>
|
||||
char *p = strrchr(executable_path, PATH_SEPARATOR);
|
||||
if (!p) {
|
||||
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
|
||||
executable_path, PATH_SEPARATOR);
|
||||
free(executable_path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*p = '\0'; // modify executable_path in place
|
||||
char *dir = executable_path;
|
||||
size_t dirlen = strlen(dir);
|
||||
size_t namelen = strlen(name);
|
||||
|
||||
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
|
||||
char *file_path = malloc(len);
|
||||
if (!file_path) {
|
||||
LOGE("Could not alloc path");
|
||||
free(executable_path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(file_path, dir, dirlen);
|
||||
file_path[dirlen] = PATH_SEPARATOR;
|
||||
// namelen + 1 to copy the final '\0'
|
||||
memcpy(&file_path[dirlen + 1], name, namelen + 1);
|
||||
|
||||
free(executable_path);
|
||||
|
||||
return file_path;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
read_pipe_all(pipe_t pipe, char *data, size_t len) {
|
||||
size_t copied = 0;
|
||||
|
@ -10,7 +10,6 @@
|
||||
// not needed here, but winsock2.h must never be included AFTER windows.h
|
||||
# include <winsock2.h>
|
||||
# include <windows.h>
|
||||
# define PATH_SEPARATOR '\\'
|
||||
# define PRIexitcode "lu"
|
||||
// <https://stackoverflow.com/a/44383330/1987178>
|
||||
# define PRIsizet "Iu"
|
||||
@ -23,7 +22,6 @@
|
||||
#else
|
||||
|
||||
# include <sys/types.h>
|
||||
# define PATH_SEPARATOR '/'
|
||||
# define PRIsizet "zu"
|
||||
# define PRIexitcode "d"
|
||||
# define PROCESS_NONE -1
|
||||
@ -73,26 +71,6 @@ process_close(process_t pid);
|
||||
bool
|
||||
process_check_success(process_t proc, const char *name, bool close);
|
||||
|
||||
#ifndef _WIN32
|
||||
// only used to find package manager, not implemented for Windows
|
||||
bool
|
||||
search_executable(const char *file);
|
||||
#endif
|
||||
|
||||
// return the absolute path of the executable (the scrcpy binary)
|
||||
// may be NULL on error; to be freed by free()
|
||||
char *
|
||||
get_executable_path(void);
|
||||
|
||||
// Return the absolute path of a file in the same directory as he executable.
|
||||
// May be NULL on error. To be freed by free().
|
||||
char *
|
||||
get_local_file_path(const char *name);
|
||||
|
||||
// returns true if the file exists and is not a directory
|
||||
bool
|
||||
is_regular_file(const char *path);
|
||||
|
||||
ssize_t
|
||||
read_pipe(pipe_t pipe, char *data, size_t len);
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
#include "str_util.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "util/strbuf.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
@ -209,3 +211,81 @@ utf8_from_wide_char(const wchar_t *ws) {
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) {
|
||||
assert(indent < columns);
|
||||
|
||||
struct sc_strbuf buf;
|
||||
|
||||
// The output string should not be much longer than the input string (just
|
||||
// a few '\n' added), so this initial capacity should hopefully almost
|
||||
// always avoid internal realloc() in string buffer
|
||||
size_t cap = strlen(input) * 3 / 2;
|
||||
|
||||
if (!sc_strbuf_init(&buf, cap)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#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 ");
|
||||
size_t new_col = col + sep_idx;
|
||||
if (pending == ' ') {
|
||||
// The pending space counts
|
||||
++new_col;
|
||||
}
|
||||
bool wrap = new_col > 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,12 @@ char *
|
||||
utf8_from_wide_char(const wchar_t *s);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Wrap input lines to fit in `columns` columns
|
||||
*
|
||||
* Break input lines at word boundaries (spaces) so that they fit in `columns`
|
||||
* columns, left-indented by `indent` spaces.
|
||||
*/
|
||||
char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent);
|
||||
|
||||
#endif
|
||||
|
87
app/src/util/strbuf.c
Normal file
87
app/src/util/strbuf.c
Normal file
@ -0,0 +1,87 @@
|
||||
#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) {
|
||||
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;
|
||||
}
|
||||
|
||||
void
|
||||
sc_strbuf_shrink(struct sc_strbuf *buf) {
|
||||
assert(buf->len <= buf->cap);
|
||||
if (buf->len != buf->cap) {
|
||||
char *s = realloc(buf->s, buf->len + 1); // +1 for '\0'
|
||||
assert(s); // decreasing the size may not fail
|
||||
buf->s = s;
|
||||
buf->cap = buf->len;
|
||||
}
|
||||
}
|
73
app/src/util/strbuf.h
Normal file
73
app/src/util/strbuf.h
Normal file
@ -0,0 +1,73 @@
|
||||
#ifndef SC_STRBUF_H
|
||||
#define SC_STRBUF_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
struct sc_strbuf {
|
||||
char *s;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the string buffer
|
||||
*
|
||||
* `buf->s` must be manually freed by the caller.
|
||||
*/
|
||||
bool
|
||||
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap);
|
||||
|
||||
/**
|
||||
* Append a string
|
||||
*
|
||||
* Append `len` characters from `s` to the buffer.
|
||||
*/
|
||||
bool
|
||||
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len);
|
||||
|
||||
/**
|
||||
* Append a char
|
||||
*
|
||||
* Append a single character to the buffer.
|
||||
*/
|
||||
bool
|
||||
sc_strbuf_append_char(struct sc_strbuf *buf, const char c);
|
||||
|
||||
/**
|
||||
* Append a char `n` times
|
||||
*
|
||||
* Append the same characters `n` times to the buffer.
|
||||
*/
|
||||
bool
|
||||
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n);
|
||||
|
||||
/**
|
||||
* Append a NUL-terminated string
|
||||
*/
|
||||
static inline bool
|
||||
sc_strbuf_append_str(struct sc_strbuf *buf, const char *s) {
|
||||
return sc_strbuf_append(buf, s, strlen(s));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a static string
|
||||
*
|
||||
* Append a string whose 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)
|
||||
|
||||
/**
|
||||
* Shrink the buffer capacity to its current length
|
||||
*
|
||||
* This resizes `buf->s` to fit the content.
|
||||
*/
|
||||
void
|
||||
sc_strbuf_shrink(struct sc_strbuf *buf);
|
||||
|
||||
#endif
|
51
app/src/util/term.c
Normal file
51
app/src/util/term.c
Normal file
@ -0,0 +1,51 @@
|
||||
#include "term.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#else
|
||||
# include <unistd.h>
|
||||
# include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
bool
|
||||
sc_term_get_size(unsigned *rows, unsigned *cols) {
|
||||
#ifdef _WIN32
|
||||
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
||||
|
||||
bool ok =
|
||||
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rows) {
|
||||
assert(csbi.srWindow.Bottom >= csbi.srWindow.Top);
|
||||
*rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
|
||||
}
|
||||
|
||||
if (cols) {
|
||||
assert(csbi.srWindow.Right >= csbi.srWindow.Left);
|
||||
*cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
struct winsize ws;
|
||||
int r = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
|
||||
if (r == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rows) {
|
||||
*rows = ws.ws_row;
|
||||
}
|
||||
|
||||
if (cols) {
|
||||
*cols = ws.ws_col;
|
||||
}
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
21
app/src/util/term.h
Normal file
21
app/src/util/term.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef SC_TERM_H
|
||||
#define SC_TERM_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* Return the terminal dimensions
|
||||
*
|
||||
* Return false if the dimensions could not be retrieved.
|
||||
*
|
||||
* Otherwise, return true, and:
|
||||
* - if `rows` is not NULL, then the number of rows is written to `*rows`.
|
||||
* - if `columns` is not NULL, then the number of columns is written to
|
||||
* `*columns`.
|
||||
*/
|
||||
bool
|
||||
sc_term_get_size(unsigned *rows, unsigned *cols);
|
||||
|
||||
#endif
|
47
app/tests/test_strbuf.c
Normal file
47
app/tests/test_strbuf.c
Normal file
@ -0,0 +1,47 @@
|
||||
#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..."));
|
||||
|
||||
sc_strbuf_shrink(&buf);
|
||||
assert(buf.len == buf.cap);
|
||||
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,44 @@ static void test_strlist_contains(void) {
|
||||
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 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 at a space\n"
|
||||
" or a line 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 = sc_str_wrap_lines(s, 24, 4);
|
||||
assert(formatted);
|
||||
|
||||
assert(!strcmp(formatted, expected));
|
||||
|
||||
free(formatted);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
@ -317,5 +355,6 @@ int main(int argc, char *argv[]) {
|
||||
test_parse_integers();
|
||||
test_parse_integer_with_suffix();
|
||||
test_strlist_contains();
|
||||
test_wrap_lines();
|
||||
return 0;
|
||||
}
|
||||
|
Reference in New Issue
Block a user