Compare commits

..

7 Commits

Author SHA1 Message Date
d9dbee9993 Adapt help to terminal size
If the error stream is a terminal, and we can retrieve the terminal
size, wrap the lines at terminal boundaries.
2021-11-11 11:34:39 +01:00
740445f425 Add util function to get terminal size 2021-11-11 11:34:35 +01:00
f849e85e9f Generate getopt params from option structures
Use the option descriptions to generate the optstring and longopts
parameters for the getopt_long() command.

That way, the options are completely described in a single place.
2021-11-11 11:34:35 +01:00
628b131741 Structure shortcuts help 2021-11-11 11:34:35 +01:00
fb3d6c319b Structure command line options help
It will allow to correctly print the help for the current terminal size
(even if for now it is hardcoded to 80 columns).
2021-11-07 23:08:09 +01:00
04b8a6dcda Add line wrapper
Add a tool to wrap lines at words boundaries (spaces) to fit in a
specified number of columns, left-indented by a specified number of
spaces.
2021-11-07 23:08:08 +01:00
0c462c1a3f Add string buffer util
This will help to build strings incrementally.
2021-11-07 21:35:57 +01:00
18 changed files with 195 additions and 301 deletions

View File

@ -24,7 +24,6 @@ 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',
@ -36,15 +35,9 @@ src = [
]
if host_machine.system() == 'windows'
src += [
'src/sys/win/file.c',
'src/sys/win/process.c',
]
src += [ 'src/sys/win/process.c' ]
else
src += [
'src/sys/unix/file.c',
'src/sys/unix/process.c',
]
src += [ 'src/sys/unix/process.c' ]
endif
v4l2_support = host_machine.system() == 'linux'

View File

@ -5,7 +5,6 @@
#include <stdlib.h>
#include <string.h>
#include "util/file.h"
#include "util/log.h"
#include "util/str_util.h"
@ -69,7 +68,7 @@ show_adb_installation_msg() {
{"pacman", "pacman -S android-tools"},
};
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
if (sc_file_executable_exists(pkg_managers[i].binary)) {
if (search_executable(pkg_managers[i].binary)) {
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
return;
}

View File

@ -635,6 +635,7 @@ print_option_usage_header(const struct sc_option *opt) {
ok = sc_strbuf_append_char(&buf, '-');
assert(ok);
ok = sc_strbuf_append_char(&buf, opt->shortopt);
assert(ok);
@ -681,7 +682,7 @@ error:
static void
print_option_usage(const struct sc_option *opt, unsigned cols) {
assert(cols > 8); // sc_str_wrap_lines() requires indent < columns
assert(cols > 8); // wrap_lines() requires indent < columns
if (!opt->text) {
// Option not documented in help (for example because it is deprecated)
@ -690,7 +691,7 @@ print_option_usage(const struct sc_option *opt, unsigned cols) {
print_option_usage_header(opt);
char *text = sc_str_wrap_lines(opt->text, cols, 8);
char *text = wrap_lines(opt->text, cols, 8);
if (!text) {
fprintf(stderr, "<ERROR>\n");
return;
@ -702,7 +703,7 @@ print_option_usage(const struct sc_option *opt, unsigned cols) {
static void
print_shortcuts_intro(unsigned cols) {
char *intro = sc_str_wrap_lines(
char *intro = wrap_lines(
"In the following list, MOD is the shortcut modifier. By default, it's "
"(left) Alt or (left) Super, but it can be configured by "
"--shortcut-mod (see above).", cols, 4);
@ -717,7 +718,7 @@ print_shortcuts_intro(unsigned cols) {
static void
print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) {
assert(cols > 8); // sc_str_wrap_lines() requires indent < columns
assert(cols > 8); // wrap_lines() requires indent < columns
assert(shortcut->shortcuts[0]); // At least one shortcut
assert(shortcut->text);
@ -729,7 +730,7 @@ print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) {
++i;
};
char *text = sc_str_wrap_lines(shortcut->text, cols, 8);
char *text = wrap_lines(shortcut->text, cols, 8);
if (!text) {
fprintf(stderr, "<ERROR>\n");
return;
@ -745,17 +746,12 @@ scrcpy_print_usage(const char *arg0) {
unsigned cols;
if (!isatty(STDERR_FILENO)) {
// Not a tty
// Not a tty, use a default value
cols = SC_TERM_COLS_DEFAULT;
} else {
bool ok = sc_term_get_size(NULL, &cols);
if (!ok) {
// Could not get the terminal size
cols = SC_TERM_COLS_DEFAULT;
}
if (cols < 20) {
// Do not accept a too small value
cols = 20;
cols = SC_TERM_COLS_DEFAULT; // default value
}
}

View File

@ -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 = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
char *icon_path = get_local_file_path(SCRCPY_PORTABLE_ICON_FILENAME);
if (!icon_path) {
LOGE("Could not get icon path");
return NULL;

View File

@ -8,7 +8,6 @@
#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"
@ -49,7 +48,7 @@ get_server_path(void) {
return NULL;
}
#else
char *server_path = sc_file_get_local_path(SERVER_FILENAME);
char *server_path = get_local_file_path(SERVER_FILENAME);
if (!server_path) {
LOGE("Could not get local file path, "
"using " SERVER_FILENAME " from current directory");
@ -68,7 +67,7 @@ push_server(const char *serial) {
if (!server_path) {
return false;
}
if (!sc_file_is_regular(server_path)) {
if (!is_regular_file(server_path)) {
LOGE("'%s' does not exist or is not a regular file\n", server_path);
free(server_path);
return false;

View File

@ -1,75 +0,0 @@
#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);
}

View File

@ -3,13 +3,53 @@
#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) {
@ -192,6 +232,37 @@ 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);

View File

@ -1,43 +0,0 @@
#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);
}

View File

@ -1,6 +1,7 @@
#include "util/process.h"
#include <assert.h>
#include <sys/stat.h>
#include "util/log.h"
#include "util/str_util.h"
@ -173,6 +174,40 @@ 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;

View File

@ -1,48 +0,0 @@
#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;
}

View File

@ -1,49 +0,0 @@
#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

View File

@ -21,6 +21,47 @@ 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;

View File

@ -10,6 +10,7 @@
// 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"
@ -22,6 +23,7 @@
#else
# include <sys/types.h>
# define PATH_SEPARATOR '/'
# define PRIsizet "zu"
# define PRIexitcode "d"
# define PROCESS_NONE -1
@ -71,6 +73,26 @@ 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);

View File

@ -212,14 +212,14 @@ utf8_from_wide_char(const wchar_t *ws) {
#endif
char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) {
char *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
// 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)) {

View File

@ -62,12 +62,8 @@ 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);
// 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

View File

@ -1,8 +1,6 @@
#ifndef SC_STRBUF_H
#define SC_STRBUF_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
@ -13,60 +11,29 @@ struct sc_strbuf {
size_t cap;
};
/**
* Initialize the string buffer
*
* `buf->s` must be manually freed by the caller.
*/
// 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).
*/
// 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)
/**
* Shrink the buffer capacity to its current length
*
* This resizes `buf->s` to fit the content.
*/
void
sc_strbuf_shrink(struct sc_strbuf *buf);

View File

@ -5,16 +5,6 @@
#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);

View File

@ -329,7 +329,7 @@ static void test_wrap_lines(void) {
" | |\n"
" +----+\n";
char *formatted = sc_str_wrap_lines(s, 24, 4);
char *formatted = wrap_lines(s, 24, 4);
assert(formatted);
assert(!strcmp(formatted, expected));