From 230afd8966d3bb4d792a41e4d87dbdd1cf4e9347 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Dec 2020 14:54:04 +0100 Subject: [PATCH 1/4] Unify release makefile Before this change, release.sh built some native stuff, and Makefile.CrossWindows built the Windows releases. Instead, use a single release.make to build the whole release. It also avoids to build the server one more time. --- Makefile.CrossWindows => release.make | 38 +++++++++++++++-------- release.sh | 44 +-------------------------- 2 files changed, 27 insertions(+), 55 deletions(-) rename Makefile.CrossWindows => release.make (82%) diff --git a/Makefile.CrossWindows b/release.make similarity index 82% rename from Makefile.CrossWindows rename to release.make index 8c093a2fa5..873762b457 100644 --- a/Makefile.CrossWindows +++ b/release.make @@ -9,15 +9,17 @@ # the server to the device. .PHONY: default clean \ + test \ build-server \ prepare-deps-win32 prepare-deps-win64 \ build-win32 build-win64 \ dist-win32 dist-win64 \ zip-win32 zip-win64 \ - sums release + release GRADLE ?= ./gradlew +TEST_BUILD_DIR := build-test SERVER_BUILD_DIR := build-server WIN32_BUILD_DIR := build-win32 WIN64_BUILD_DIR := build-win64 @@ -30,19 +32,35 @@ VERSION := $(shell git describe --tags --always) WIN32_TARGET := $(WIN32_TARGET_DIR)-$(VERSION).zip WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip -release: clean zip-win32 zip-win64 sums - @echo "Windows archives generated in $(DIST)/" +RELEASE_DIR := release-$(VERSION) + +release: clean test build-server zip-win32 zip-win64 + mkdir -p "$(RELEASE_DIR)" + cp "$(SERVER_BUILD_DIR)/server/scrcpy-server" \ + "$(RELEASE_DIR)/scrcpy-server-$(VERSION)" + cp "$(DIST)/$(WIN32_TARGET)" "$(RELEASE_DIR)" + cp "$(DIST)/$(WIN64_TARGET)" "$(RELEASE_DIR)" + cd "$(RELEASE_DIR)" && \ + sha256sum "scrcpy-server-$(VERSION)" \ + "scrcpy-win32-$(VERSION).zip" \ + "scrcpy-win64-$(VERSION).zip" > SHA256SUMS.txt + @echo "Release generated in $(RELEASE_DIR)/" clean: $(GRADLE) clean - rm -rf "$(SERVER_BUILD_DIR)" "$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)" \ - "$(DIST)" + rm -rf "$(DIST)" "$(TEST_BUILD_DIR)" "$(SERVER_BUILD_DIR)" \ + "$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)" + +test: + [ -d "$(TEST_BUILD_DIR)" ] || ( mkdir "$(TEST_BUILD_DIR)" && \ + meson "$(TEST_BUILD_DIR)" -Db_sanitize=address ) + ninja -C "$(TEST_BUILD_DIR)" + $(GRADLE) -p server check build-server: [ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \ - meson "$(SERVER_BUILD_DIR)" \ - --buildtype release -Dcompile_app=false ) - ninja -C "$(SERVER_BUILD_DIR)" + meson "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false ) + ninja -C "$(SERVER_BUILD_DIR)" prepare-deps-win32: -$(MAKE) -C prebuilt-deps prepare-win32 @@ -109,7 +127,3 @@ zip-win32: dist-win32 zip-win64: dist-win64 cd "$(DIST)/$(WIN64_TARGET_DIR)"; \ zip -r "../$(WIN64_TARGET)" . - -sums: - cd "$(DIST)"; \ - sha256sum *.zip > SHA256SUMS.txt diff --git a/release.sh b/release.sh index 4c5afbf138..a824e95898 100755 --- a/release.sh +++ b/release.sh @@ -1,44 +1,2 @@ #!/bin/bash -set -e - -# test locally -TESTDIR=build_test -rm -rf "$TESTDIR" -# run client tests with ASAN enabled -meson "$TESTDIR" -Db_sanitize=address -ninja -C"$TESTDIR" test - -# test server -GRADLE=${GRADLE:-./gradlew} -$GRADLE -p server check - -BUILDDIR=build_release -rm -rf "$BUILDDIR" -meson "$BUILDDIR" --buildtype release --strip -Db_lto=true -cd "$BUILDDIR" -ninja -cd - - -# build Windows releases -make -f Makefile.CrossWindows - -# the generated server must be the same everywhere -cmp "$BUILDDIR/server/scrcpy-server" dist/scrcpy-win32/scrcpy-server -cmp "$BUILDDIR/server/scrcpy-server" dist/scrcpy-win64/scrcpy-server - -# get version name -TAG=$(git describe --tags --always) - -# create release directory -mkdir -p "release-$TAG" -cp "$BUILDDIR/server/scrcpy-server" "release-$TAG/scrcpy-server-$TAG" -cp "dist/scrcpy-win32-$TAG.zip" "release-$TAG/" -cp "dist/scrcpy-win64-$TAG.zip" "release-$TAG/" - -# generate checksums -cd "release-$TAG" -sha256sum "scrcpy-server-$TAG" \ - "scrcpy-win32-$TAG.zip" \ - "scrcpy-win64-$TAG.zip" > SHA256SUMS.txt - -echo "Release generated in release-$TAG/" +make -f release.make From a7991323e4a966b88bf28c35c852ac4b9c870ed5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Dec 2020 01:15:59 +0100 Subject: [PATCH 2/4] Extract util function to build a local file path Finding a local file in the scrcpy directory may be useful for files other than scrcpy-server in the future. --- app/src/command.c | 29 +++++++++++++++++++++++++++++ app/src/command.h | 5 +++++ app/src/server.c | 27 ++------------------------- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/app/src/command.c b/app/src/command.c index 81047b7a32..56feba118f 100644 --- a/app/src/command.c +++ b/app/src/command.c @@ -1,6 +1,7 @@ #include "command.h" #include +#include #include #include #include @@ -229,3 +230,31 @@ process_check_success(process_t proc, const char *name) { } return true; } + +char * +get_local_file_path(const char *name) { + char *executable_path = get_executable_path(); + if (!executable_path) { + return NULL; + } + + char *dir = dirname(executable_path); + size_t dirlen = strlen(dir); + size_t namelen = strlen(name); + + size_t len = dirlen + namelen + 2; // +2: '/' and '\0` + char *file_path = SDL_malloc(len); + if (!file_path) { + LOGE("Could not alloc path"); + SDL_free(executable_path); + } + + 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); + + SDL_free(executable_path); + + return file_path; +} diff --git a/app/src/command.h b/app/src/command.h index 7035139b60..d1979e38aa 100644 --- a/app/src/command.h +++ b/app/src/command.h @@ -90,4 +90,9 @@ get_executable_path(void); bool is_regular_file(const char *path); +// return the absolute path of a file in the same directory as the executable +// may be NULL on error; to be freed by SDL_free +char * +get_local_file_path(const char *name); + #endif diff --git a/app/src/server.c b/app/src/server.c index 9267356b28..33d8dae0d2 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -53,35 +52,13 @@ get_server_path(void) { // the absolute path is hardcoded return server_path; #else - - // use scrcpy-server in the same directory as the executable - char *executable_path = get_executable_path(); - if (!executable_path) { - LOGE("Could not get executable path, " - "using " SERVER_FILENAME " from current directory"); - // not found, use current directory - return SERVER_FILENAME; - } - char *dir = dirname(executable_path); - size_t dirlen = strlen(dir); - - // sizeof(SERVER_FILENAME) gives statically the size including the null byte - size_t len = dirlen + 1 + sizeof(SERVER_FILENAME); - char *server_path = SDL_malloc(len); + char *server_path = get_local_file_path(SERVER_FILENAME); if (!server_path) { - LOGE("Could not alloc server path string, " + LOGE("Could not get local file path, " "using " SERVER_FILENAME " from current directory"); - SDL_free(executable_path); return SERVER_FILENAME; } - memcpy(server_path, dir, dirlen); - server_path[dirlen] = PATH_SEPARATOR; - memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME)); - // the final null byte has been copied with SERVER_FILENAME - - SDL_free(executable_path); - LOGD("Using server (portable): %s", server_path); return server_path; #endif From 24e1005a0767900b106b3ca10eba9d722d1798ee Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Dec 2020 15:16:02 +0100 Subject: [PATCH 3/4] [WIP] icon --- app/meson.build | 5 +- app/src/icon.c | 248 +++++++++++++++++++++++++++++++++++++++++++++ app/src/icon.h | 16 +++ app/src/icon.xpm | 53 ---------- app/src/scrcpy.c | 1 - app/src/screen.c | 7 +- app/src/tiny_xpm.c | 120 ---------------------- app/src/tiny_xpm.h | 11 -- data/icon.png | Bin 0 -> 6540 bytes release.make | 2 + run | 4 +- 11 files changed, 276 insertions(+), 191 deletions(-) create mode 100644 app/src/icon.c create mode 100644 app/src/icon.h delete mode 100644 app/src/icon.xpm delete mode 100644 app/src/tiny_xpm.c delete mode 100644 app/src/tiny_xpm.h create mode 100644 data/icon.png diff --git a/app/meson.build b/app/meson.build index 28b9d14186..ca65ca3855 100644 --- a/app/meson.build +++ b/app/meson.build @@ -8,6 +8,7 @@ src = [ 'src/device.c', 'src/device_msg.c', 'src/event_converter.c', + 'src/icon.c', 'src/file_handler.c', 'src/fps_counter.c', 'src/input_manager.c', @@ -18,7 +19,6 @@ src = [ 'src/screen.c', 'src/server.c', 'src/stream.c', - 'src/tiny_xpm.c', 'src/video_buffer.c', 'src/util/net.c', 'src/util/str_util.c' @@ -136,6 +136,9 @@ executable('scrcpy', src, c_args: []) install_man('scrcpy.1') +install_data('../data/icon.png', + rename: 'scrcpy.png', + install_dir: 'share/icons/hicolor/512x512/apps') ### TESTS diff --git a/app/src/icon.c b/app/src/icon.c new file mode 100644 index 0000000000..f5988c187e --- /dev/null +++ b/app/src/icon.c @@ -0,0 +1,248 @@ +#include "icon.h" + +#include +#include +#include +#include +#include + +#include "config.h" +#include "command.h" +#include "compat.h" +#include "util/log.h" +#include "util/str_util.h" + +#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png" +#define SCRCPY_DEFAULT_ICON_PATH PREFIX \ + "/share/icons/hicolor/512x512/apps/scrcpy.png" + +static char * +get_icon_path(void) { +#ifdef __WINDOWS__ + const wchar_t *icon_path_env = _wgetenv(L"SCRCPY_ICON_PATH"); +#else + const char *icon_path_env = getenv("SCRCPY_ICON_PATH"); +#endif + if (icon_path_env) { + // if the envvar is set, use it +#ifdef __WINDOWS__ + char *icon_path = utf8_from_wide_char(icon_path_env); +#else + char *icon_path = SDL_strdup(icon_path_env); +#endif + if (!icon_path) { + LOGE("Could not allocate memory"); + return NULL; + } + LOGD("Using SCRCPY_ICON_PATH: %s", icon_path); + return icon_path; + } + +#ifndef PORTABLE + char *icon_path = SDL_strdup(SCRCPY_DEFAULT_ICON_PATH); + if (!icon_path) { + LOGE("Could not allocate memory"); + return NULL; + } +#else + char *icon_path = get_local_file_path(SCRCPY_PORTABLE_ICON_FILENAME); + if (!icon_path) { + LOGE("Could not get icon path"); + return NULL; + } +#endif + + return icon_path; +} + +static AVFrame * +decode_image(const char *path) { + AVFrame *result = NULL; + + AVFormatContext *ctx = avformat_alloc_context(); + if (!ctx) { + LOGE("Could not allocate image decoder context"); + return NULL; + } + + if (avformat_open_input(&ctx, path, NULL, NULL) < 0) { + LOGE("Could not open image codec"); + goto free_ctx; + } + + if (avformat_find_stream_info(ctx, NULL) < 0) { + LOGE("Could not find image stream info"); + goto close_input; + } + + int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); + if (stream < 0 ) { + LOGE("Could not find best image stream"); + goto close_input; + } + +#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API + AVCodecParameters *params = ctx->streams[stream]->codecpar; + + AVCodec *codec = avcodec_find_decoder(params->codec_id); + if (!codec) { + LOGE("Could not find image decoder"); + goto close_input; + } + + AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); + if (avcodec_parameters_to_context(codec_ctx, params) < 0) { + LOGE("Could not fill codec context"); + goto free_codec_ctx; + } +#else + AVCodecContext *codec_ctx = ctx->streams[stream]->codec; + + AVCodec *codec = avcodec_find_decoder(codec_ctx->codec_id); + if (!codec) { + LOGE("Could not find image decoder"); + goto close_input; + } +#endif + + if (avcodec_open2(codec_ctx, codec, NULL) < 0) { + LOGE("Could not open image codec"); + goto free_codec_ctx; + } + + AVFrame *frame = av_frame_alloc(); + if (!frame) { + LOGE("Could not allocate frame"); + goto close_codec; + } + + AVPacket packet; + av_init_packet(&packet); + + if (av_read_frame(ctx, &packet) < 0) { + LOGE("Could not read frame"); + av_frame_free(&frame); + goto close_input; + } + +#ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API + int ret; + if ((ret = avcodec_send_packet(codec_ctx, &packet)) < 0) { + LOGE("Could not send icon packet: %d", ret); + av_frame_free(&frame); + goto close_input; + } + + if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) { + LOGE("Could not receive icon frame: %d", ret); + av_frame_free(&frame); + goto close_input; + } + +#else + int got_picture; + int len = avcodec_decode_video2(codec_ctx, frame, &got_picture, &packet); + if (len < 0 || !got_picture) { + LOGE("Could not decode icon: %d", len); + av_frame_free(&frame); + goto close_input; + } +#endif + + av_packet_unref(&packet); + + result = frame; + +close_codec: + avcodec_close(codec_ctx); +free_codec_ctx: +#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API + avcodec_free_context(&codec_ctx); +#endif +close_input: + avformat_close_input(&ctx); +free_ctx: + avformat_free_context(ctx); + + return result; +} + +static SDL_PixelFormatEnum +to_sdl_pixel_format(enum AVPixelFormat fmt) { + switch (fmt) { + case AV_PIX_FMT_RGB24: return SDL_PIXELFORMAT_RGB24; + case AV_PIX_FMT_BGR24: return SDL_PIXELFORMAT_BGR24; + case AV_PIX_FMT_ARGB: return SDL_PIXELFORMAT_ARGB32; + case AV_PIX_FMT_RGBA: return SDL_PIXELFORMAT_RGBA32; + case AV_PIX_FMT_ABGR: return SDL_PIXELFORMAT_ABGR32; + case AV_PIX_FMT_BGRA: return SDL_PIXELFORMAT_BGRA32; + case AV_PIX_FMT_RGB565BE: return SDL_PIXELFORMAT_RGB565; + case AV_PIX_FMT_RGB555BE: return SDL_PIXELFORMAT_RGB555; + case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565; + case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555; + case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_RGB444; + case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444; + default: return SDL_PIXELFORMAT_UNKNOWN; + } +} + +SDL_Surface * +scrcpy_icon_load() { + char *icon_path = get_icon_path(); + assert(icon_path); + + AVFrame *frame = decode_image(icon_path); + SDL_free(icon_path); + if (!frame) { + return NULL; + } + + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format); + if (!desc) { + LOGE("Could not get icon format descriptor"); + goto error; + } + + bool is_packed_rgb = desc->flags & AV_PIX_FMT_FLAG_RGB + && !(desc->flags & AV_PIX_FMT_FLAG_PLANAR); + if (!is_packed_rgb) { + LOGE("Could not load non-RGB icon"); + goto error; + } + + SDL_PixelFormatEnum format = to_sdl_pixel_format(frame->format); + if (format == SDL_PIXELFORMAT_UNKNOWN) { + LOGE("Unsupported icon pixel format: %s (%d)", desc->name, + frame->format); + goto error; + } + + int bits_per_pixel = av_get_bits_per_pixel(desc); + SDL_Surface *surface = + SDL_CreateRGBSurfaceWithFormatFrom(frame->data[0], + frame->width, frame->height, + bits_per_pixel, + frame->linesize[0], + format); + + if (!surface) { + LOGE("Could not create icon surface"); + goto error; + } + + surface->userdata = frame; // frame owns the data + + return surface; + +error: + av_frame_free(&frame); + return NULL; +} + +void +scrcpy_icon_destroy(SDL_Surface *icon) { + AVFrame *frame = icon->userdata; + assert(frame); + av_frame_free(&frame); + SDL_FreeSurface(icon); +} diff --git a/app/src/icon.h b/app/src/icon.h new file mode 100644 index 0000000000..35c8ee43d0 --- /dev/null +++ b/app/src/icon.h @@ -0,0 +1,16 @@ +#ifndef ICON_H +#define ICON_H + +#include +#include +#include + +#include "config.h" + +SDL_Surface * +scrcpy_icon_load(void); + +void +scrcpy_icon_destroy(SDL_Surface *icon); + +#endif diff --git a/app/src/icon.xpm b/app/src/icon.xpm deleted file mode 100644 index 73b29da96a..0000000000 --- a/app/src/icon.xpm +++ /dev/null @@ -1,53 +0,0 @@ -/* XPM */ -static char * icon_xpm[] = { -"48 48 2 1", -" c None", -". c #96C13E", -" .. .. ", -" ... ... ", -" ... ...... ... ", -" ................ ", -" .............. ", -" ................ ", -" .................. ", -" .................... ", -" ..... ........ ..... ", -" ..... ........ ..... ", -" ...................... ", -" ........................ ", -" ........................ ", -" ........................ ", -" ", -" ", -" .... ........................ .... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" .... ........................ .... ", -" ........................ ", -" ...................... ", -" ...... ...... ", -" ...... ...... ", -" ...... ...... ", -" ...... ...... ", -" ...... ...... ", -" ...... ...... ", -" ...... ...... ", -" ...... ...... ", -" ...... ...... ", -" .... .... "}; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index a543e11c77..037046df8a 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -28,7 +28,6 @@ #include "screen.h" #include "server.h" #include "stream.h" -#include "tiny_xpm.h" #include "video_buffer.h" #include "util/lock.h" #include "util/log.h" diff --git a/app/src/screen.c b/app/src/screen.c index fe2bc867df..d2eedd4b71 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -7,9 +7,8 @@ #include "config.h" #include "common.h" #include "compat.h" -#include "icon.xpm" +#include "icon.h" #include "scrcpy.h" -#include "tiny_xpm.h" #include "video_buffer.h" #include "util/lock.h" #include "util/log.h" @@ -309,10 +308,10 @@ screen_init_rendering(struct screen *screen, const char *window_title, LOGD("Trilinear filtering disabled (not an OpenGL renderer)"); } - SDL_Surface *icon = read_xpm(icon_xpm); + SDL_Surface *icon = scrcpy_icon_load(); if (icon) { SDL_SetWindowIcon(screen->window, icon); - SDL_FreeSurface(icon); + scrcpy_icon_destroy(icon); } else { LOGW("Could not load icon"); } diff --git a/app/src/tiny_xpm.c b/app/src/tiny_xpm.c deleted file mode 100644 index feb3d1cb49..0000000000 --- a/app/src/tiny_xpm.c +++ /dev/null @@ -1,120 +0,0 @@ -#include "tiny_xpm.h" - -#include -#include -#include -#include -#include - -#include "config.h" -#include "util/log.h" - -struct index { - char c; - uint32_t color; -}; - -static bool -find_color(struct index *index, int len, char c, uint32_t *color) { - // there are typically very few color, so it's ok to iterate over the array - for (int i = 0; i < len; ++i) { - if (index[i].c == c) { - *color = index[i].color; - return true; - } - } - *color = 0; - return false; -} - -// We encounter some problems with SDL2_image on MSYS2 (Windows), -// so here is our own XPM parsing not to depend on SDL_image. -// -// We do not hardcode the binary image to keep some flexibility to replace the -// icon easily (just by replacing icon.xpm). -// -// Parameter is not "const char *" because XPM formats are generally stored in a -// (non-const) "char *" -SDL_Surface * -read_xpm(char *xpm[]) { -#ifndef NDEBUG - // patch the XPM to change the icon color in debug mode - xpm[2] = ". c #CC00CC"; -#endif - - char *endptr; - // *** No error handling, assume the XPM source is valid *** - // (it's in our source repo) - // Assertions are only checked in debug - int width = strtol(xpm[0], &endptr, 10); - int height = strtol(endptr + 1, &endptr, 10); - int colors = strtol(endptr + 1, &endptr, 10); - int chars = strtol(endptr + 1, &endptr, 10); - - // sanity checks - assert(0 <= width && width < 256); - assert(0 <= height && height < 256); - assert(0 <= colors && colors < 256); - assert(chars == 1); // this implementation does not support more - - (void) chars; - - // init index - struct index index[colors]; - for (int i = 0; i < colors; ++i) { - const char *line = xpm[1+i]; - index[i].c = line[0]; - assert(line[1] == '\t'); - assert(line[2] == 'c'); - assert(line[3] == ' '); - if (line[4] == '#') { - index[i].color = 0xff000000 | strtol(&line[5], &endptr, 0x10); - assert(*endptr == '\0'); - } else { - assert(!strcmp("None", &line[4])); - index[i].color = 0; - } - } - - // parse image - uint32_t *pixels = SDL_malloc(4 * width * height); - if (!pixels) { - LOGE("Could not allocate icon memory"); - return NULL; - } - for (int y = 0; y < height; ++y) { - const char *line = xpm[1 + colors + y]; - for (int x = 0; x < width; ++x) { - char c = line[x]; - uint32_t color; - bool color_found = find_color(index, colors, c, &color); - assert(color_found); - (void) color_found; - pixels[y * width + x] = color; - } - } - -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - uint32_t amask = 0x000000ff; - uint32_t rmask = 0x0000ff00; - uint32_t gmask = 0x00ff0000; - uint32_t bmask = 0xff000000; -#else // little endian, like x86 - uint32_t amask = 0xff000000; - uint32_t rmask = 0x00ff0000; - uint32_t gmask = 0x0000ff00; - uint32_t bmask = 0x000000ff; -#endif - - SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels, - width, height, - 32, 4 * width, - rmask, gmask, bmask, amask); - if (!surface) { - LOGE("Could not create icon surface"); - return NULL; - } - // make the surface own the raw pixels - surface->flags &= ~SDL_PREALLOC; - return surface; -} diff --git a/app/src/tiny_xpm.h b/app/src/tiny_xpm.h deleted file mode 100644 index 6e6f80353d..0000000000 --- a/app/src/tiny_xpm.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef TINYXPM_H -#define TINYXPM_H - -#include - -#include "config.h" - -SDL_Surface * -read_xpm(char *xpm[]); - -#endif diff --git a/data/icon.png b/data/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..06617be8a5496c8dc141dfaa898130275aa42918 GIT binary patch literal 6540 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 XSXn;^Pdjb~pvUZ_^@&Oo&-niVXicov literal 0 HcmV?d00001 diff --git a/release.make b/release.make index 873762b457..b30393d323 100644 --- a/release.make +++ b/release.make @@ -94,6 +94,7 @@ dist-win32: build-server build-win32 cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)" cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" + cp data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -110,6 +111,7 @@ dist-win64: build-server build-win64 cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)" cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" + cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" diff --git a/run b/run index 628c5c7ea6..fda3ea57a1 100755 --- a/run +++ b/run @@ -20,4 +20,6 @@ then exit 1 fi -SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" "$BUILDDIR/app/scrcpy" "$@" +SCRCPY_ICON_PATH="data/icon.png" \ +SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" \ +"$BUILDDIR/app/scrcpy" "$@" From 236cd4ff6fce68036e46b55985d1f473e02f5a53 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Dec 2020 12:21:56 +0100 Subject: [PATCH 4/4] [WIP] icon-palette --- app/src/icon.c | 42 ++++++++++++++++++++++++++++++++++++++---- data/icon.png | Bin 6540 -> 4156 bytes 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/app/src/icon.c b/app/src/icon.c index f5988c187e..b646b7bafd 100644 --- a/app/src/icon.c +++ b/app/src/icon.c @@ -182,6 +182,7 @@ to_sdl_pixel_format(enum AVPixelFormat fmt) { case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555; case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_RGB444; case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444; + case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8; default: return SDL_PIXELFORMAT_UNKNOWN; } } @@ -203,10 +204,9 @@ scrcpy_icon_load() { goto error; } - bool is_packed_rgb = desc->flags & AV_PIX_FMT_FLAG_RGB - && !(desc->flags & AV_PIX_FMT_FLAG_PLANAR); - if (!is_packed_rgb) { - LOGE("Could not load non-RGB icon"); + bool is_packed = !(desc->flags & AV_PIX_FMT_FLAG_PLANAR); + if (!is_packed) { + LOGE("Could not load non-packed icon"); goto error; } @@ -230,6 +230,40 @@ scrcpy_icon_load() { goto error; } + if (frame->format == AV_PIX_FMT_PAL8) { + // Initialize the SDL palette + uint8_t *data = frame->data[1]; + SDL_Color colors[256]; + for (int i = 0; i < 256; ++i) { + SDL_Color *color = &colors[i]; + + // The palette is transported in AVFrame.data[1], is 1024 bytes + // long (256 4-byte entries) and is formatted the same as in + // AV_PIX_FMT_RGB32 described above (i.e., it is also + // endian-specific). + // +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + color->a = data[i * 4]; + color->r = data[i * 4 + 1]; + color->g = data[i * 4 + 2]; + color->b = data[i * 4 + 3]; +#else + color->a = data[i * 4 + 3]; + color->r = data[i * 4 + 2]; + color->g = data[i * 4 + 1]; + color->b = data[i * 4]; +#endif + } + + SDL_Palette *palette = surface->format->palette; + assert(palette); + int ret = SDL_SetPaletteColors(palette, colors, 0, 256); + if (ret) { + LOGE("Could not set palette colors"); + goto error; + } + } + surface->userdata = frame; // frame owns the data return surface; diff --git a/data/icon.png b/data/icon.png index 06617be8a5496c8dc141dfaa898130275aa42918..9db8983a8c361bbf4018067679f62843d0b8d701 100644 GIT binary patch literal 4156 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 F|2x!i{0;yB literal 6540 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 XSXn;^Pdjb~pvUZ_^@&Oo&-niVXicov