From 6f6e127095318dc699c314b691feeca040ad0d1b Mon Sep 17 00:00:00 2001 From: Niellune Date: Fri, 30 May 2025 14:35:48 +0300 Subject: [PATCH] New UX, More settings, reorganise code --- Makefile | 2 +- settings.txt | 101 ++++++++----- src/connector.cpp | 115 +++++++++------ src/connector.h | 21 ++- src/decoder.cpp | 6 +- src/helper/error.h | 9 +- src/helper/functions.h | 34 ++++- src/helper/iprotocol.h | 6 +- src/helper/protocol_const.h | 7 + src/helper/ufont.h | 65 -------- src/helper/uimage.h | 52 ------- src/interface.cpp | 51 +++++++ src/interface.h | 23 +++ src/main.cpp | 285 +++++++++++++++--------------------- src/pcm_audio.h | 1 - src/protocol.cpp | 37 +++-- src/protocol.h | 7 +- src/renderer.cpp | 200 +++++++++++++++++++++---- src/renderer.h | 47 +++++- src/resource/background.bmp | Bin 92876 -> 40906 bytes src/resource/colors.h | 16 ++ src/settings.h | 40 +++-- src/struct/video_buffer.h | 7 + 23 files changed, 678 insertions(+), 454 deletions(-) delete mode 100644 src/helper/ufont.h delete mode 100644 src/helper/uimage.h create mode 100644 src/interface.cpp create mode 100644 src/interface.h create mode 100644 src/resource/colors.h diff --git a/Makefile b/Makefile index e5f8394..4cbf1bf 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ BUILD_DIR := $(OUT_DIR)/$(BUILD_TYPE) SRCS := $(shell find $(SRC_DIR) -type f -name '*.cpp') OBJS=$(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(SRCS)) -RES := $(shell find $(RES_DIR) -type f ! -name '*.h' -name '*.*') +RES := $(shell find $(RES_DIR) -type f ! -name '*.h' ! -name '.*' -name '*.*') RES_SRC := $(patsubst $(RES_DIR)/%,$(GEN_DIR)/%.cpp,$(RES)) # Targets diff --git a/settings.txt b/settings.txt index 0626488..1670341 100644 --- a/settings.txt +++ b/settings.txt @@ -1,28 +1,73 @@ -# Dongle configuration +############################################################################## +# 1.General settings +############################################################################## + +# Dongle configuration (use lsusb to find yours) # Note that vendor id and product id are in decimals, not hex here #vendor-id = 4884 # 0x1314 #product-id = 5408 # 0x1520 -# Application starts in full screen -#fullscreen = true - - -# Application drawing settings widthxheight -#width = 720 -#height = 576 -#fps = 50 - -# Enable vsync. This reduce tearing but can dramatically affect performance on low end systems -#vsync = false - -# Target DPI reported to device. Set 0 for default -#dpi = 0 - # Requested image from phone #source-width = 720 #source-height = 576 #source-fps = 50 +# Application drawing settings widthxheight +#width = 720 +#height = 576 + +# Application drawing target framerate. This can responsiveness. +# If the setting is lower than source-fps the framse will be dropped +# If the setting is not multiple of source-fps can still cause frame drops cause out-of-sync +# source images and drawing times +#fps = 50 + +# Application starts in full screen +#fullscreen = true + +# Enable generic console logging. Can reduce performace +#logging = false + +############################################################################## +# 2.Device configurations +############################################################################## + +# USB communication protocol encryption +# From 2024 Carlinkit has an optinal USB protocol encryption +# It can happened that it will become mandatory and device will not work withou it +# So if you have updated your device and it stop working try to enable encryption +#encryption = false + +# Enable automatic connection to wireless devices +#autoconnect = true + +# Eanble weak charging current (standart usb 0.5A). +# If you have a powerfull poswer supply you can try to disable this. +# If you have troubles with wired connection try to enable this, cause it might be cause of power lack. +#weak-charge = true + +# Driving position, true for left hand drive, false for right hand drive +#left-hand-drive = true + +# Nigh mode. 0 for day mode, 1 for night mode, 2 for automatic +#night-mode = 2 + +# Target DPI reported to device. Set 0 for default. Not sure if it affects anything +#dpi = 0 + +############################################################################## +# 3.Application configuration +############################################################################## + +# Font size for messgaes on screen. Set to 0 is you do not want any +#font-size = 30 + +# Enable vsync. This reduce tearing but can dramatically affect performance on low end systems +#vsync = false + +# Corrects aspect of UI +#aspect-correction = 1 + # Scaler algorithm if application drawing is differen from source image # It's recommended to keep application and source values same cause scaling # takes a lot of CPU and can cause artifacts on slow devices @@ -45,9 +90,6 @@ # Select faster method of scaling image to window size (nearest) or better quality (linear) #fast-render-scale = false -# Enable logging -#logging = false - # Size of video and audio buffers. Increase if you see artifacts #video-buffer-size = 32 #audio-buffer-size = 16 @@ -61,18 +103,6 @@ # Reduction level from 1 (no reduction) to 0 (fully silenced) #audio-fade = 0.3 -# Font size for messgaes on screen. Set to 0 is you do not want any -#font-size = 30 - -# USB communication protocol encryption -# From 2024 Carlinkit has an optinal USB protocol encryption -# It can happened that it will become mandatory and device will not work withou it -# So if you have updated your device and it stop working try to enable encryption -#encryption = false - -# Enable automatic connection to wireless devices -#autoconnect = true - # Run script or app on phone connected and disconnected. # This script/app should be fast, otherwise it will block system. # If you need to start application in background use scripts with fork @@ -90,10 +120,15 @@ #on-connect-script = #on-disconnect-script = -# Protocol debug level. Works on debug builds only with PROTOCOL_DEBUG flag set. +############################################################################## +# 4.Debug +############################################################################## + +# Protocol debug level. Works only on builds only with PROTOCOL_DEBUG flag set. +# Add -DPROTOCOL_DEBUG to CXXCOMMON to enable protocol debugging and rebuild. # 0 - nothing # 1 - unknown commands # 2 - all commands except data streams # 3 - include outgoing commands # 4 - log everything -#protocol-debug = 0 \ No newline at end of file +# protocol-debug = 0 \ No newline at end of file diff --git a/src/connector.cpp b/src/connector.cpp index 4baa275..d92874e 100644 --- a/src/connector.cpp +++ b/src/connector.cpp @@ -21,6 +21,7 @@ Connector::Connector(uint16_t videoPadding) _cipher = nullptr; } + _state = PROTOCOL_STATUS_INITIALISING; int result = libusb_init(&_context); if (result < 0) throw std::runtime_error(std::string("Can't initialise USB: ") + libusb_error_name(result)); @@ -67,8 +68,8 @@ void Connector::stop() return; _active = false; - if (_read_thread.joinable()) - _read_thread.join(); + + state(PROTOCOL_STATUS_INITIALISING); if (_write_thread.joinable()) _write_thread.join(); @@ -76,13 +77,11 @@ void Connector::stop() bool Connector::connect(uint16_t vendor_id, uint16_t product_id) { - status("Searching for dongle"); - _device = libusb_open_device_with_vid_pid(_context, vendor_id, product_id); if (!_device) { std::cout << "[Connection] Failed to create device handle - no device" << std::endl; - status("Can't find dongle"); + state(PROTOCOL_STATUS_NO_DEVICE); return false; } @@ -97,50 +96,29 @@ bool Connector::connect(uint16_t vendor_id, uint16_t product_id) bool Connector::link() { - int usbres = 0; - status("Linking dongle"); + state(PROTOCOL_STATUS_LINKING); - usbres = libusb_reset_device(_device); - if (usbres < 0) - { - std::cout << "[Connection] Can't reset device: " << libusb_error_name(usbres) << std::endl; + if (linkFail(libusb_reset_device(_device), " Can't reset device")) return false; - } - usbres = libusb_set_configuration(_device, 1); - if (usbres < 0) - { - std::cout << "[Connection] Can't set configuration: " << libusb_error_name(usbres) << std::endl; + if (linkFail(libusb_set_configuration(_device, 1), "Can't set configuration")) return false; - } - usbres = libusb_claim_interface(_device, 0); - if (usbres < 0) - { - std::cout << "[Connection] Can't claim interface: " << libusb_error_name(usbres) << std::endl; + if (linkFail(libusb_claim_interface(_device, 0), "Can't claim interface")) return false; - } libusb_device *dev = libusb_get_device(_device); struct libusb_config_descriptor *config = nullptr; - usbres = libusb_get_active_config_descriptor(dev, &config); - if (usbres < 0) - { - std::cout << "[Connection] Can't get config: " << libusb_error_name(usbres) << std::endl; + if (linkFail(libusb_get_active_config_descriptor(dev, &config), "Can't get config")) return false; - } for (int i = 0; i < config->interface[0].altsetting[0].bNumEndpoints; i++) { const struct libusb_endpoint_descriptor *ep = &config->interface[0].altsetting[0].endpoint[i]; if ((ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) - { _endpoint_in = ep->bEndpointAddress; - } else - { _endpoint_out = ep->bEndpointAddress; - } } libusb_free_config_descriptor(config); @@ -157,10 +135,57 @@ void Connector::release() } } -void Connector::status(const char *status) +bool Connector::nextState(u_int8_t state) { - if (_protocol) - _protocol->onStatus(status); + if (state == _state) + return false; + + if (state > _state) + { + _nodeviceCount = 0; + _failCount = 0; + _state = state; + return true; + } + + switch (state) + { + case PROTOCOL_STATUS_INITIALISING: + break; + + case PROTOCOL_STATUS_ERROR: + _nodeviceCount = 0; + if (_failCount++ < 10) + return false; + break; + + case PROTOCOL_STATUS_NO_DEVICE: + if (_nodeviceCount++ < 10 && _state < PROTOCOL_STATUS_ONLINE) + return false; + break; + + default: + return false; + } + + _state = state; + return true; +} + +void Connector::state(u_int8_t state) +{ + if (nextState(state) && _protocol) + _protocol->onStatus(state); +} + +bool Connector::linkFail(int status, const char *msg) +{ + if (status == 0) + return false; + _lastError = msg; + std::cout << "[Connection] " << msg << ": " << libusb_error_name(status) << std::endl; + state(PROTOCOL_STATUS_ERROR); + return true; } int Connector::send(int cmd, bool encrypt, uint8_t *data, uint32_t size) @@ -213,7 +238,7 @@ void Connector::setEncryption(bool enabled) _ecnrypt = true; } -void Connector::printInts(uint32_t length, uint8_t *data, uint16_t max) +void Connector::printInts(uint8_t *data, uint32_t length, uint16_t max) { if (data && length >= 4) { @@ -232,7 +257,7 @@ void Connector::printInts(uint32_t length, uint8_t *data, uint16_t max) } } -void Connector::printBytes(uint32_t length, uint8_t *data, uint16_t max) +void Connector::printBytes(uint8_t *data, uint32_t length, uint16_t max) { if (data && length >= 4) { @@ -311,8 +336,6 @@ void Connector::read_loop() if (result == LIBUSB_ERROR_NO_DEVICE) { std::cout << "[Connection] Device disconnected" << std::endl; - if (_protocol) - _protocol->onDevice(false); _connected = false; continue; } @@ -357,9 +380,6 @@ void Connector::read_loop() #ifdef PROTOCOL_DEBUG printMessage(header.type, header.length, data, header.magic == MAGIC_ENC, false); - - if (header.type == 7 && header.length < 100) - printBytes(header.length, data, 30); #endif if (padding > 0) @@ -375,20 +395,20 @@ void Connector::write_loop() // Set thread name setThreadName("protocol-writer"); + state(PROTOCOL_STATUS_LINKING); while (_active) { _connected = connect(Settings::vendorid, Settings::productid); if (_connected) { - status("Initialising dongle"); std::cout << "[Connection] Device connected" << std::endl; _read_thread = std::thread(&Connector::read_loop, this); if (_protocol) _protocol->onDevice(true); - status("Waiting for connecton"); + state(PROTOCOL_STATUS_ONLINE); while (_connected && _active) { send(170); @@ -397,7 +417,14 @@ void Connector::write_loop() { return !_active.load(); }); } - if(_read_thread.joinable()) + if (_active) + { + state(PROTOCOL_STATUS_NO_DEVICE); + if (_protocol) + _protocol->onDevice(false); + } + + if (_read_thread.joinable()) _read_thread.join(); } std::unique_lock lock(mtx); diff --git a/src/connector.h b/src/connector.h index 159b128..4cf1aca 100644 --- a/src/connector.h +++ b/src/connector.h @@ -11,7 +11,7 @@ #include "helper/iprotocol.h" #include "aes_cipher.h" -#define READ_TIMEOUT 5000 +#define READ_TIMEOUT 3000 #define ENCRYPTION_BASE "SkBRDy3gmrw1ieH0" #define PROTOCOL_DEBUG_NONE 0 @@ -45,6 +45,11 @@ public: AESCipher *Cypher() const { return _cipher; }; + static void printMessage(uint32_t cmd, uint32_t length, uint8_t *data, bool encrypted, bool out); + static void printInts(uint8_t *data, uint32_t length, uint16_t max); + static void printBytes(uint8_t *data, uint32_t length, uint16_t max); + static const char *cmdString(int cmd); + private: void read_loop(); void write_loop(); @@ -53,12 +58,9 @@ private: bool link(); void release(); - void status(const char *status); - - static void printInts(uint32_t length, uint8_t *data, uint16_t max); - static void printBytes(uint32_t length, uint8_t *data, uint16_t max); - static const char *cmdString(int cmd); - static void printMessage(uint32_t cmd, uint32_t length, uint8_t *data, bool encrypted, bool out); + void state(u_int8_t state); + bool nextState(u_int8_t state); + bool linkFail(int status, const char *msg); libusb_context *_context = nullptr; libusb_device_handle *_device = nullptr; @@ -67,6 +69,11 @@ private: bool _connected; std::atomic _ecnrypt = false; + uint8_t _state; + uint8_t _failCount; + uint8_t _nodeviceCount; + std::string _lastError; + std::thread _read_thread; std::thread _write_thread; std::mutex _write_mutex; diff --git a/src/decoder.cpp b/src/decoder.cpp index 6454007..49d6694 100644 --- a/src/decoder.cpp +++ b/src/decoder.cpp @@ -63,7 +63,7 @@ AVCodecContext *Decoder::load_codec(AVCodecID codec_id) return result; } - std::cout << "[Video] Can't load HW codec " << codec->name << ": " << Error::avErrorText(ret) << std::endl; + std::cout << "[Video] Can't load HW codec " << codec->name << ": " << avErrorText(ret) << std::endl; avcodec_free_context(&result); } @@ -85,7 +85,7 @@ AVCodecContext *Decoder::load_codec(AVCodecID codec_id) int ret = avcodec_open2(result, codec, nullptr); if (ret < 0) { - std::cout << "[Video] Failed to open software decoder " << codec->name << ": " << Error::avErrorText(ret) << std::endl; + std::cout << "[Video] Failed to open software decoder " << codec->name << ": " << avErrorText(ret) << std::endl; avcodec_free_context(&result); return nullptr; } @@ -177,7 +177,7 @@ void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPack int send_ret = avcodec_send_packet(context, packet); if (send_ret != 0) { - std::cout << "[Video] Can't decode packet: " << Error::avErrorText(send_ret) << std::endl; + std::cout << "[Video] Can't decode packet: " << avErrorText(send_ret) << std::endl; continue; } diff --git a/src/helper/error.h b/src/helper/error.h index 879e42b..7882ac3 100644 --- a/src/helper/error.h +++ b/src/helper/error.h @@ -3,6 +3,7 @@ #include #include +#include "helper/functions.h" extern "C" { @@ -47,14 +48,6 @@ public: return false; } - static const std::string avErrorText(int code) - { - char buf[AV_ERROR_MAX_STRING_SIZE] = {0}; - if (av_strerror(code, buf, sizeof(buf)) == 0) - return buf; - return "Unknown error"; - } - bool avFail(int code, const std::string &message = "") { if (code == 0) diff --git a/src/helper/functions.h b/src/helper/functions.h index df017cc..abaf5b9 100644 --- a/src/helper/functions.h +++ b/src/helper/functions.h @@ -1,10 +1,18 @@ #ifndef SRC_HELPER_FUNCTIONS #define SRC_HELPER_FUNCTIONS +#include +#include + #if defined(__linux__) || defined(__APPLE__) #include #endif +extern "C" +{ +#include +} + inline void setThreadName(const char *name) { #if defined(__linux__) @@ -29,12 +37,34 @@ inline void write_uint32_le(uint8_t *dst, uint32_t value) dst[3] = (value >> 24) & 0xFF; } -inline void execute(const char* path) { - if (!path || *path == '\0') { +inline void execute(const char *path) +{ + if (!path || *path == '\0') + { throw std::invalid_argument("Program path cannot be empty"); } std::system(path); } +inline const std::string avErrorText(int code) +{ + char buf[AV_ERROR_MAX_STRING_SIZE] = {0}; + if (av_strerror(code, buf, sizeof(buf)) == 0) + return buf; + return "Unknown error"; +} + +inline void pushEvent(Uint32 evt, int code) +{ + if (evt == (Uint32)-1) + return; + SDL_Event event; + SDL_memset(&event, 0, sizeof(event)); + event.type = evt; + event.user.type = evt; + event.user.code = code; + SDL_PushEvent(&event); +} + #endif /* SRC_HELPER_FUNCTIONS */ diff --git a/src/helper/iprotocol.h b/src/helper/iprotocol.h index 0535772..92d00de 100644 --- a/src/helper/iprotocol.h +++ b/src/helper/iprotocol.h @@ -4,13 +4,11 @@ #include #include -using StatusCallback = void (*)(const char* status); - class IProtocol { public: - virtual void onData(uint32_t cmd, uint32_t length, uint8_t* data) = 0; - virtual void onStatus(const char* status) = 0; + virtual void onData(uint32_t cmd, uint32_t length, uint8_t *data) = 0; + virtual void onStatus(u_int8_t status) = 0; virtual void onDevice(bool connected) = 0; }; #endif /* SRC_HELPER_IPROTOCOL */ diff --git a/src/helper/protocol_const.h b/src/helper/protocol_const.h index 97c2e85..23b3481 100644 --- a/src/helper/protocol_const.h +++ b/src/helper/protocol_const.h @@ -1,6 +1,13 @@ #ifndef SRC_HELPER_PROTOCOL_CONST #define SRC_HELPER_PROTOCOL_CONST +#define PROTOCOL_STATUS_INITIALISING 0 // Initialised > 1 +#define PROTOCOL_STATUS_NO_DEVICE 1 // Start linking > 3 +#define PROTOCOL_STATUS_ERROR 2 // Linked > 4, no device in sequence > 1 +#define PROTOCOL_STATUS_LINKING 3 // Linked > 4, Failed in sequence > 2 +#define PROTOCOL_STATUS_ONLINE 4 // Phone connected > 5, no device > 1 +#define PROTOCOL_STATUS_CONNECTED 5 // Phone disconnected > 4, no device > 1 + #define MAGIC 0x55aa55aa #define MAGIC_ENC 0x55bb55bb diff --git a/src/helper/ufont.h b/src/helper/ufont.h deleted file mode 100644 index 9a92f9c..0000000 --- a/src/helper/ufont.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef SRC_HELPER_UFONT -#define SRC_HELPER_UFONT - -#include -#include - -class UFont -{ -public: - UFont(const void *font_data, int data_size, int ptsize) - { - if(ptsize < 1) - return; - - SDL_RWops *font_rw = SDL_RWFromConstMem(font_data, data_size); - if (!font_rw) - { - std::cerr << "[UX] SDL can't open font: " << SDL_GetError() << std::endl; - return; - } - - _font = TTF_OpenFontRW(font_rw, 1, ptsize); - if (!_font) - { - std::cerr << "[UX] SDL can't load font: " << TTF_GetError() << std::endl; - } - }; - - ~UFont() - { - if (_font) - { - TTF_CloseFont(_font); - _font = nullptr; - } - }; - - SDL_Texture *GetText(SDL_Renderer *renderer, const char *text, SDL_Color color) - { - if (!_font) - return nullptr; - - SDL_Surface *textSurface = TTF_RenderText_Blended(_font, text, color); - if (!textSurface) - { - std::cerr << "[UX] Failed to create text surface: " << TTF_GetError() << std::endl; - return nullptr; - } - - SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, textSurface); - SDL_FreeSurface(textSurface); - if (!textTexture) - { - std::cerr << "[UX] Failed to create text texture: " << TTF_GetError() << std::endl; - return nullptr; - } - - return textTexture; - } - -private: - TTF_Font *_font = nullptr; -}; - -#endif /* SRC_HELPER_UFONT */ diff --git a/src/helper/uimage.h b/src/helper/uimage.h deleted file mode 100644 index 975c357..0000000 --- a/src/helper/uimage.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef SRC_HELPER_UIMAGE -#define SRC_HELPER_UIMAGE - -#include -#include - -class UImage -{ -public: - UImage(const void *img_data, int img_size) - { - SDL_RWops *img_rw = SDL_RWFromConstMem(img_data, img_size); - if (!img_rw) - { - std::cerr << "[UX] SDL can't open image: " << SDL_GetError() << std::endl; - return; - } - - _surface = SDL_LoadBMP_RW(img_rw, 1); - if (!_surface) - { - std::cerr << "[UX] Failed to create image surface: " << SDL_GetError() << std::endl; - return; - } - - Width = _surface->w; - Height = _surface->h; - }; - - ~UImage() - { - if (_surface) - { - SDL_FreeSurface(_surface); - _surface = nullptr; - } - }; - - SDL_Texture *GetImage(SDL_Renderer *renderer) - { - return SDL_CreateTextureFromSurface(renderer, _surface); - } - - int Width = 0; - - int Height = 0; - -private: - SDL_Surface *_surface = nullptr; -}; - -#endif /* SRC_HELPER_UIMAGE */ diff --git a/src/interface.cpp b/src/interface.cpp new file mode 100644 index 0000000..6deef72 --- /dev/null +++ b/src/interface.cpp @@ -0,0 +1,51 @@ +#include "interface.h" +#include "resource/background.h" +#include "resource/font.h" +#include "resource/colors.h" +#include "settings.h" +#include "helper/protocol_const.h" +#include + +Interface::Interface(SDL_Renderer *renderer) + : Renderer(renderer), _state(0), + _textDongle(font, font_len, Settings::fontSize), + _textInit(font, font_len, Settings::fontSize), + _textConnect(font, font_len, Settings::fontSize), + _textLaunch(font, font_len, Settings::fontSize), + _mainImage(background, background_len) +{ +} + +Interface::~Interface() +{ +} + +bool Interface::drawHome(bool force, int state) +{ + if (state == _state && !force) + return false; + + _state = state; + int width, height; + SDL_GetRendererOutputSize(_renderer, &width, &height); + SDL_RenderClear(_renderer); + + _mainImage.draw(_renderer, width, height); + if (state == PROTOCOL_STATUS_ERROR) + { + if (_textDongle.prepare(_renderer, "Connection error", colorError)) + _textDongle.draw(_renderer, 0.05 * width, 0.2 * height - _textDongle.height / 2); + } + if (_textDongle.prepare(_renderer, "Insert dongle", state == PROTOCOL_STATUS_NO_DEVICE ? color1 : color1_inactive)) + _textDongle.draw(_renderer, 0.05 * width, 0.2 * height - _textDongle.height / 2); + if (_textInit.prepare(_renderer, "Initialising", state == PROTOCOL_STATUS_LINKING ? color2 : color2_inactive)) + _textInit.draw(_renderer, 0.05 * width, 0.4 * height - _textInit.height / 2); + if (_textConnect.prepare(_renderer, "Connect phone", state == PROTOCOL_STATUS_ONLINE ? color3 : color3_inactive)) + _textConnect.draw(_renderer, 0.05 * width, 0.6 * height - _textConnect.height / 2); + if (_textLaunch.prepare(_renderer, "Launching", state == PROTOCOL_STATUS_CONNECTED? color4 : color4_inactive)) + _textLaunch.draw(_renderer, 0.05 * width, 0.8 * height - _textLaunch.height / 2); + + SDL_RenderPresent(_renderer); + return true; +} + diff --git a/src/interface.h b/src/interface.h new file mode 100644 index 0000000..2aafc25 --- /dev/null +++ b/src/interface.h @@ -0,0 +1,23 @@ +#ifndef SRC_INTERFACE +#define SRC_INTERFACE + +#include "renderer.h" +#include + +class Interface : public Renderer +{ +public: + Interface(SDL_Renderer *renderer); + ~Interface(); + bool drawHome(bool force, int state); + +private: + int _state; + RendererText _textDongle; + RendererText _textInit; + RendererText _textConnect; + RendererText _textLaunch; + RendererImage _mainImage; +}; + +#endif /* SRC_INTERFACE */ diff --git a/src/main.cpp b/src/main.cpp index 66c55fa..dbcca54 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,100 +12,44 @@ extern "C" #include // FFmpeg utility functions for image handling } -#include "resource/background.h" -#include "resource/font.h" - #include "helper/functions.h" -#include "helper/ufont.h" -#include "helper/uimage.h" - +#include "helper/protocol_const.h" #include "struct/video_buffer.h" #include "protocol.h" #include "decoder.h" #include "pcm_audio.h" -#include "renderer.h" +#include "interface.h" -static const char *title = "Fast Car Play v0.2"; +#define FRAME_DELAY_INACTIVE 200 + +static const char *title = "Fast Car Play v0.3"; static SDL_Window *window = nullptr; static SDL_Renderer *renderer = nullptr; -bool active = false; +Uint32 evtStatus = (Uint32)-1; +Uint32 evtConnected = (Uint32)-1; +bool active = true; -static SDL_Texture *textTexture = nullptr; -static std::string textureText = ""; -static SDL_Texture *imgTexture = nullptr; - -static bool mouseDown = false; -static bool fullscreen = false; - -std::mutex statusMutex; -std::string statusText; - -void onStatus(const char *status) +struct RunParams { - std::lock_guard lock(statusMutex); - statusText = status; -} + bool connected; + bool videoRendered; + bool dirty; + bool fullscreen; + bool mouseDown; + uint8_t deviceStatus; + uint32_t frameDelay; + int activeDelay; +}; -void DrawText(UFont &font, std::string text) -{ - if (!textTexture || textureText.compare(text) != 0) - { - if (textTexture) - SDL_DestroyTexture(textTexture); - textTexture = font.GetText(renderer, text.c_str(), {255, 255, 255, 255}); - textureText = text; - } - - if (!textTexture) - return; - - int textW, textH; - SDL_QueryTexture(textTexture, nullptr, nullptr, &textW, &textH); - - int windowW, windowH; - SDL_GetRendererOutputSize(renderer, &windowW, &windowH); - - // Center text - SDL_Rect dstRect = { - (windowW - textW) / 2, - (windowH - textH) * 9 / 10, - textW, - textH}; - - SDL_RenderCopy(renderer, textTexture, nullptr, &dstRect); -} - -void DrawImage(UImage &img) -{ - if (!imgTexture) - imgTexture = img.GetImage(renderer); - - if (!imgTexture) - return; - - int windowW, windowH; - SDL_GetRendererOutputSize(renderer, &windowW, &windowH); - - // Compute destination rectangle to center image - SDL_Rect dst = { - (windowW - img.Width) / 2, // x: center horizontally - (windowH - img.Height) / 2, // y: center vertically - img.Width, // width: original image width - img.Height // height: original image height - }; - - SDL_RenderCopy(renderer, imgTexture, nullptr, &dst); -} - -void processKey(Protocol &protocol, SDL_Keysym key) +void processKey(Protocol &protocol, SDL_Keysym key, RunParams ¶ms) { switch (key.sym) { case SDLK_f: - fullscreen = !fullscreen; // Toggle fullscreen mode - SDL_SetWindowFullscreen(window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); - SDL_SetWindowBordered(window, fullscreen ? SDL_FALSE : SDL_TRUE); + params.fullscreen = !params.fullscreen; // Toggle fullscreen mode + SDL_SetWindowFullscreen(window, params.fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + SDL_SetWindowBordered(window, params.fullscreen ? SDL_FALSE : SDL_TRUE); break; case SDLK_q: @@ -131,7 +75,7 @@ void processKey(Protocol &protocol, SDL_Keysym key) } } -void processEvents(Protocol &protocol, bool processMouse) +void processEvents(Protocol &protocol, RunParams ¶ms, VideoBuffer &vb) { SDL_Event e; int motionX = -1; @@ -140,6 +84,7 @@ void processEvents(Protocol &protocol, bool processMouse) int downY = -1; int upX = -1; int upY = -1; + while (SDL_PollEvent(&e)) { switch (e.type) @@ -148,9 +93,16 @@ void processEvents(Protocol &protocol, bool processMouse) active = false; break; + case SDL_WINDOWEVENT: + if (e.window.event == SDL_WINDOWEVENT_RESIZED) + { + params.dirty = true; + } + break; + case SDL_MOUSEBUTTONDOWN: { - mouseDown = true; + params.mouseDown = true; downX = e.button.x; downY = e.button.y; break; @@ -158,14 +110,14 @@ void processEvents(Protocol &protocol, bool processMouse) case SDL_MOUSEBUTTONUP: { - mouseDown = false; + params.mouseDown = false; upX = e.button.x; upY = e.button.y; break; } case SDL_MOUSEMOTION: { - if (!mouseDown) + if (!params.mouseDown) break; motionX = e.motion.x; motionY = e.motion.y; @@ -173,13 +125,30 @@ void processEvents(Protocol &protocol, bool processMouse) } case SDL_KEYDOWN: { - processKey(protocol, e.key.keysym); + processKey(protocol, e.key.keysym, params); break; } + default: + { + if (e.type == evtConnected) + { + printf("\nEvt connected %d\n", e.user.code); + params.connected = e.user.code != 0; + params.dirty = true; + params.videoRendered = false; + params.frameDelay = params.connected ? params.activeDelay : FRAME_DELAY_INACTIVE; + if (!params.connected) + vb.reset(); + } + else if (e.type == evtStatus) + { + params.deviceStatus = e.user.code; + } + } } } - if (processMouse && (downX >= 0 || upX >= 0 || motionX >= 0)) + if (params.videoRendered && (downX >= 0 || upX >= 0 || motionX >= 0)) { int window_width, window_height; SDL_GetWindowSize(window, &window_width, &window_height); @@ -194,8 +163,17 @@ void processEvents(Protocol &protocol, bool processMouse) void application() { - fullscreen = Settings::fullscreen; - if (fullscreen) + RunParams p; + p.activeDelay = 1000 / Settings::fps; + p.connected = false; + p.deviceStatus = PROTOCOL_STATUS_INITIALISING; + p.dirty = false; + p.frameDelay = FRAME_DELAY_INACTIVE; + p.videoRendered = false; + p.fullscreen = Settings::fullscreen; + p.mouseDown = false; + + if (p.fullscreen) { SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN); SDL_SetWindowBordered(window, SDL_FALSE); @@ -212,90 +190,46 @@ void application() decoder.start(&protocol.videoData, &videoBuffer, AV_CODEC_ID_H264); audioMain.start(&protocol.audioStreamMain); audioAux.start(&protocol.audioStreamAux, &audioMain); - protocol.start(onStatus); - - UFont textFont(font, font_len, Settings::fontSize); - std::string status = "Initialising"; - DrawText(textFont, status); - SDL_RenderPresent(renderer); - - UImage image(background, background_len); - SDL_RenderClear(renderer); - DrawImage(image); - DrawText(textFont, status); - SDL_RenderPresent(renderer); + protocol.start(evtStatus, evtConnected); + Interface interface(renderer); std::cout << "[Main] Loop" << std::endl; - Renderer videoRenderer(renderer); - bool dirty = true; - bool connected = false; - bool videoPrepared = false; - const int activeDelay = 1000 / Settings::fps; - const int inactiveDelay = 1000 / 5; // 5FPS - uint32_t frameDelay = inactiveDelay; - active = true; uint32_t latestid = 0; Uint32 frameStart = SDL_GetTicks(); while (active) { - processEvents(protocol, videoPrepared); + processEvents(protocol, p, videoBuffer); - if (connected != protocol.phoneConnected) - { - connected = protocol.phoneConnected; - SDL_RenderClear(renderer); - DrawImage(image); - SDL_RenderPresent(renderer); - dirty = true; - videoPrepared = false; - frameDelay = connected ? activeDelay : inactiveDelay; - } - - if (connected) + if (p.connected) { AVFrame *frame = nullptr; uint32_t frameid = 0; - if (videoBuffer.latest(&frame, &frameid) && frameid != latestid && frame) + if (videoBuffer.latest(&frame, &frameid) && (frameid != latestid || p.dirty) && frame) { - if (!videoPrepared) - videoPrepared = videoRenderer.prepare(frame, Settings::width, Settings::height, Settings::scaler); - if (videoPrepared && videoRenderer.render(frame)) + if (interface.render(frame)) { - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, videoRenderer.texture, nullptr, nullptr); - SDL_RenderPresent(renderer); + p.videoRendered = true; + if (!p.dirty && (frameid != latestid + 1)) + std::cout << "[Main] Frame drop " << frameid - latestid - 1 << " on " << frameid << std::endl; + latestid = frameid; + p.dirty = false; } - if (frameid != latestid + 1) - std::cout << "[Main] Frame drop " << frameid - latestid - 1 << " on " << frameid << std::endl; - latestid = frameid; videoBuffer.consume(); } } - else + + if (!p.videoRendered) { - { - std::lock_guard lock(statusMutex); - if (status != statusText) - { - status = statusText; - dirty = true; - } - } - if (dirty) - { - SDL_RenderClear(renderer); - DrawImage(image); - DrawText(textFont, status); - SDL_RenderPresent(renderer); - } + interface.drawHome(p.dirty, p.connected ? PROTOCOL_STATUS_CONNECTED : p.deviceStatus); + p.dirty = false; } Uint32 frameEnd = SDL_GetTicks(); Uint32 frameTime = frameEnd - frameStart; - if (frameTime < frameDelay) + if (active && frameTime < p.frameDelay) { - SDL_Delay(frameDelay - frameTime); // Sleep only the remaining time - frameStart = frameStart + frameDelay; + SDL_Delay(p.frameDelay - frameTime); // Sleep only the remaining time + frameStart = frameStart + p.frameDelay; } else frameStart = frameEnd; @@ -304,20 +238,8 @@ void application() SDL_HideWindow(window); } -int main(int argc, char **argv) +int start() { - std::cout << title << std::endl; - - if (argc > 2) - { - std::cerr << " Usage: " << argv[0] << " [settings_file]" << std::endl; - return 1; - } - if (argc == 2) - { - Settings::load(argv[1]); - } - if (!Settings::logging) disable_cout(); else @@ -370,9 +292,19 @@ int main(int argc, char **argv) renderer = SDL_CreateRenderer(window, -1, (Settings::vsync ? (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC) : SDL_RENDERER_ACCELERATED)); if (renderer) { - std::cout << "[Main] Started" << std::endl; - application(); - std::cout << "[Main] Finish" << std::endl; + evtStatus = SDL_RegisterEvents(2); + if (evtStatus != (Uint32)-1) + { + evtConnected = evtStatus + 1; + std::cout << "[Main] Started" << std::endl; + application(); + std::cout << "[Main] Finish" << std::endl; + } + else + { + std::cerr << "[Main] Can't register custom events" << std::endl; + } + SDL_DestroyRenderer(renderer); } else @@ -380,12 +312,29 @@ int main(int argc, char **argv) std::cerr << "[Main] SDL can't create renderer: " << SDL_GetError() << std::endl; } - if (textTexture) - SDL_DestroyTexture(textTexture); - if (imgTexture) - SDL_DestroyTexture(imgTexture); SDL_DestroyWindow(window); TTF_Quit(); SDL_Quit(); return 0; +} + +int main(int argc, char **argv) +{ + std::cout << title << std::endl; + if (argc > 2) + { + std::cerr << " Usage: " << argv[0] << " [settings_file]" << std::endl; + return 0; + } + try + { + if (argc == 2) + Settings::load(argv[1]); + return start(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return 1; + } } \ No newline at end of file diff --git a/src/pcm_audio.h b/src/pcm_audio.h index 7cb3b80..03ce003 100644 --- a/src/pcm_audio.h +++ b/src/pcm_audio.h @@ -9,7 +9,6 @@ #include "struct/atomic_queue.h" #include "struct/message.h" -#include "helper/error.h" #define FADE_IN_SPEED 0.00001 #define FADE_OUT_SPEED 0.0001 diff --git a/src/protocol.cpp b/src/protocol.cpp index 93d5c69..875e484 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -23,9 +23,10 @@ Protocol::~Protocol() stop(); } -void Protocol::start(StatusCallback onStatus) +void Protocol::start(uint32_t evtStatus, uint32_t evtPhone) { - _statusCallback = onStatus; + _evtStatusId = evtStatus; + _evtPhoneId = evtPhone; connector.start(this); } @@ -146,10 +147,9 @@ void Protocol::sendEncryption() connector.send(CMD_ENCRYPTION, false, buf, 4); } -void Protocol::onStatus(const char *status) +void Protocol::onStatus(uint8_t status) { - if (_statusCallback) - _statusCallback(status); + pushEvent(_evtStatusId, status); } void Protocol::onDevice(bool connected) @@ -162,9 +162,12 @@ void Protocol::onDevice(bool connected) if (Settings::dpi > 0) sendFile("/tmp/screen_dpi", Settings::dpi); sendFile("/etc/android_work_mode", 1); - sendFile("/tmp/night_mode", 2); // 0==day, 1==night, 2==??? - sendFile("/tmp/hand_drive_mode", 0); // 0==left, 1==right - sendFile("/tmp/charge_mode", 0); + if (Settings::nightMode < 0 || Settings::nightMode > 2) // 0==day, 1==night, 2==auto + sendFile("/tmp/night_mode", 2); + else + sendFile("/tmp/night_mode", Settings::nightMode); + sendFile("/tmp/hand_drive_mode", Settings::leftDrive ? 0 : 1); // 0==left, 1==right + sendFile("/tmp/charge_mode", Settings::weakCharge ? 0 : 2); // Weak charge 0, other 2 sendFile("/etc/box_name", "CarPlay"); if (Settings::autoconnect) sendInt(CMD_CONTROL, 1002); @@ -186,6 +189,8 @@ void Protocol::onPhone(bool connected) std::cout << (connected ? "[Protocol] Phone connected" : "[Protocol] Phone disconnected") << std::endl; + pushEvent(_evtPhoneId, connected ? 1 : 0); + if (connected && Settings::onConnect.value.length() > 1) execute(Settings::onConnect.value.c_str()); @@ -198,6 +203,14 @@ void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data) bool dispose = true; switch (cmd) { + case CMD_PLUGGED: + onPhone(true); + break; + + case CMD_UNPLUGGED: + onPhone(false); + break; + case CMD_VIDEO_DATA: { if (length <= 20) @@ -228,12 +241,8 @@ void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data) } break; } - case CMD_PLUGGED: - onPhone(true); - break; - - case CMD_UNPLUGGED: - onPhone(false); + case CMD_CONTROL: + Connector::printBytes(data, length, 20); break; case CMD_ENCRYPTION: diff --git a/src/protocol.h b/src/protocol.h index 157cbc1..4ebe573 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -19,7 +19,7 @@ public: static const char *cmdString(int cmd); - void start(StatusCallback onStatus); + void start(uint32_t evtStatus, uint32_t evtPhone); void stop(); void sendKey(int key); @@ -40,7 +40,7 @@ private: void sendInt(uint32_t cmd, uint32_t value, bool encryption = true); void sendEncryption(); - void onStatus(const char *status) override; + void onStatus(uint8_t status) override; void onDevice(bool connected) override; void onData(uint32_t cmd, uint32_t length, uint8_t *data) override; @@ -50,7 +50,8 @@ private: uint16_t _height; uint16_t _fps; - StatusCallback _statusCallback = nullptr; + uint32_t _evtStatusId = (uint32_t) -1; + uint32_t _evtPhoneId = (uint32_t) -1; }; #endif /* SRC_PROTOCOL */ diff --git a/src/renderer.cpp b/src/renderer.cpp index 8929511..68c31dc 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,18 +1,156 @@ #include "renderer.h" -#include "helper/error.h" #include +#include "settings.h" +#include "helper/functions.h" +#include + +RendererText::RendererText(const void *font_data, int data_size, int ptsize) + : width(0), + height(0), + _font(nullptr), + _texture(nullptr), + _text(" "), + _color({0, 0, 0, 0}) +{ + if (ptsize < 1) + return; + + SDL_RWops *font_rw = SDL_RWFromConstMem(font_data, data_size); + if (!font_rw) + { + std::cerr << "[UX] SDL can't open font: " << SDL_GetError() << std::endl; + return; + } + + _font = TTF_OpenFontRW(font_rw, 1, ptsize); + if (!_font) + { + std::cerr << "[UX] SDL can't load font: " << TTF_GetError() << std::endl; + } +}; + +RendererText::~RendererText() +{ + if (_texture) + { + SDL_DestroyTexture(_texture); + _texture = nullptr; + } + + if (_font) + { + TTF_CloseFont(_font); + _font = nullptr; + } +}; + +bool RendererText::prepare(SDL_Renderer *renderer, std::string text, SDL_Color color) +{ + if (!_texture || _text.compare(text) != 0 || !sameColor(_color, color)) + { + if (_texture) + SDL_DestroyTexture(_texture); + _texture = getText(renderer, text.c_str(), color); + _text = text; + _color = color; + SDL_QueryTexture(_texture, nullptr, nullptr, &width, &height); + } + return _texture; +} + +SDL_Rect RendererText::draw(SDL_Renderer *renderer, int x, int y) +{ + if (!_texture) + return {0, 0, 0, 0}; + + SDL_Rect dstRect = {x, y, (int)(width * Settings::aspectCorrection), height}; + SDL_RenderCopy(renderer, _texture, nullptr, &dstRect); + return dstRect; +} + +SDL_Texture *RendererText::getText(SDL_Renderer *renderer, const char *text, SDL_Color color) +{ + if (!_font) + return nullptr; + + SDL_Surface *textSurface = TTF_RenderText_Blended(_font, text, color); + if (!textSurface) + { + std::cerr << "[UX] Failed to create text surface: " << TTF_GetError() << std::endl; + return nullptr; + } + + SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, textSurface); + SDL_FreeSurface(textSurface); + if (!textTexture) + { + std::cerr << "[UX] Failed to create text texture: " << TTF_GetError() << std::endl; + return nullptr; + } + + return textTexture; +} + +RendererImage::RendererImage(const void *img_data, int img_size) + : width(0), height(0), _surface(nullptr), _aspect(0) +{ + SDL_RWops *img_rw = SDL_RWFromConstMem(img_data, img_size); + if (!img_rw) + { + std::cerr << "[UX] SDL can't open image: " << SDL_GetError() << std::endl; + return; + } + + _surface = SDL_LoadBMP_RW(img_rw, 1); + if (!_surface) + { + std::cerr << "[UX] Failed to create image surface: " << SDL_GetError() << std::endl; + return; + } + + width = _surface->w; + height = _surface->h; +}; + +RendererImage::~RendererImage() +{ + if (_surface) + { + SDL_FreeSurface(_surface); + _surface = nullptr; + } +}; + +SDL_Rect RendererImage::draw(SDL_Renderer *renderer, int w, int h) +{ + if (!_texture) + { + _texture = SDL_CreateTextureFromSurface(renderer, _surface); + if (!_texture) + return {0, 0, 0, 0}; + SDL_GetRendererOutputSize(renderer, &width, &height); + _aspect = 1.0 * height / width; + } + + float scale = 1.0 * h / height; + int imgw = width * scale * Settings::aspectCorrection; + + SDL_Rect dst = {w - imgw, 0, imgw, h}; + SDL_RenderCopy(renderer, _texture, nullptr, &dst); + return dst; +} const Renderer::FormatMapping Renderer::_mapping[] = { {AV_PIX_FMT_RGB24, SDL_PIXELFORMAT_RGB24, &Renderer::rgb, "RGB24"}, {AV_PIX_FMT_YUV420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, "YUV420P"}, - {AV_PIX_FMT_YUVJ420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, "YUVJ420P"}, + {AV_PIX_FMT_YUVJ420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, "YUVJ420P"}, {AV_PIX_FMT_NV12, SDL_PIXELFORMAT_NV12, &Renderer::nv, "NV12"}}; Renderer::Renderer(SDL_Renderer *renderer) - : texture(nullptr), - textureWidth(0), - textureHeight(0), - _renderer(renderer), + : _renderer(renderer), + _texture(nullptr), + _textureWidth(0), + _textureHeight(0), _render(nullptr), _sws(nullptr), _swsWidth(0), @@ -23,10 +161,10 @@ Renderer::Renderer(SDL_Renderer *renderer) Renderer::~Renderer() { - if (texture) + if (_texture) { - SDL_DestroyTexture(texture); - texture = nullptr; + SDL_DestroyTexture(_texture); + _texture = nullptr; } if (_sws) { @@ -43,27 +181,31 @@ Renderer::~Renderer() bool Renderer::render(AVFrame *frame) { if (_render == nullptr) - return false; + if (!prepare(frame, Settings::width, Settings::height, Settings::scaler)) + return false; (this->*_render)(frame); + SDL_RenderClear(_renderer); + SDL_RenderCopy(_renderer, _texture, nullptr, nullptr); + SDL_RenderPresent(_renderer); return true; } bool Renderer::prepareTexture(uint32_t format, int width, int height) { - if (texture) + if (_texture) { - if (textureWidth == width && textureHeight == height) + if (_textureWidth == width && _textureHeight == height) return true; - SDL_DestroyTexture(texture); - texture = nullptr; + SDL_DestroyTexture(_texture); + _texture = nullptr; } - texture = SDL_CreateTexture(_renderer, format, - SDL_TEXTUREACCESS_STREAMING, - width, height); - if (!texture) + _texture = SDL_CreateTexture(_renderer, format, + SDL_TEXTUREACCESS_STREAMING, + width, height); + if (!_texture) { - std::cerr << "[Render] SDL can't create video texture: " << SDL_GetError() << std::endl; + std::cerr << "[UX] SDL can't create video texture: " << SDL_GetError() << std::endl; return false; } return true; @@ -80,7 +222,7 @@ bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32 { if (prepareTexture(mapping.sdlFormat, targetWidth, targetHeight)) { - std::cout << "[Render] Direct rendering " << mapping.name << std::endl; + std::cout << "[UX] Direct rendering " << mapping.name << std::endl; _render = mapping.function; return true; } @@ -89,7 +231,7 @@ bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32 } else { - std::cout << "[Render] Scaling required from " << frame->width << "x" << frame->height << " to " << targetWidth << "x" << targetHeight << std::endl; + std::cout << "[UX] Scaling required from " << frame->width << "x" << frame->height << " to " << targetWidth << "x" << targetHeight << std::endl; } if (!prepareTexture(SDL_PIXELFORMAT_IYUV, targetWidth, targetHeight)) @@ -108,7 +250,7 @@ bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32 scaler, nullptr, nullptr, nullptr); if (!_sws) { - std::cerr << "[Render] Can't create sws context" << std::endl; + std::cerr << "[UX] Can't create sws context" << std::endl; return false; } _swsWidth = frame->width; @@ -120,7 +262,7 @@ bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32 _frame = av_frame_alloc(); if (!_frame) { - std::cerr << "[Render] Can't allocate AVFrame" << std::endl; + std::cerr << "[UX] Can't allocate AVFrame" << std::endl; return false; } _frame->format = AV_PIX_FMT_YUV420P; @@ -130,12 +272,12 @@ bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32 int avRes = av_frame_get_buffer(_frame, 32); if (avRes != 0) { - std::cerr << "[Render] Can't allocate AVFrame buffer: " << Error::avErrorText(avRes) << std::endl; + std::cerr << "[UX] Can't allocate AVFrame buffer: " << avErrorText(avRes) << std::endl; return false; } } - std::cout << "[Render] Scaling rendering source format " << frame->format << std::endl; + std::cout << "[UX] Scaling rendering source format " << frame->format << std::endl; _render = &Renderer::scale; return true; } @@ -143,7 +285,7 @@ bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32 void Renderer::rgb(AVFrame *frame) { SDL_UpdateTexture( - texture, + _texture, nullptr, frame->data[0], frame->linesize[0]); } @@ -151,7 +293,7 @@ void Renderer::rgb(AVFrame *frame) void Renderer::nv(AVFrame *frame) { SDL_UpdateNVTexture( - texture, + _texture, nullptr, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1]); @@ -159,7 +301,7 @@ void Renderer::nv(AVFrame *frame) void Renderer::yuv(AVFrame *frame) { SDL_UpdateYUVTexture( - texture, + _texture, nullptr, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], @@ -176,7 +318,7 @@ void Renderer::scale(AVFrame *frame) _frame->linesize); // Update SDL texture with YUV frame data - SDL_UpdateYUVTexture(texture, nullptr, + SDL_UpdateYUVTexture(_texture, nullptr, _frame->data[0], _frame->linesize[0], _frame->data[1], _frame->linesize[1], _frame->data[2], _frame->linesize[2]); diff --git a/src/renderer.h b/src/renderer.h index 6d0bf2a..e88da5b 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -8,20 +8,53 @@ extern "C" } #include +#include #include +class RendererText +{ +public: + RendererText(const void *font_data, int data_size, int ptsize); + ~RendererText(); + bool prepare(SDL_Renderer *renderer, std::string text, SDL_Color color); + SDL_Rect draw(SDL_Renderer *renderer, int x, int y); + int width; + int height; + +private: + SDL_Texture *getText(SDL_Renderer *renderer, const char *text, SDL_Color color); + static int sameColor(SDL_Color c1, SDL_Color c2) { return (c1.r == c2.r) && (c1.g == c2.g) && (c1.b == c2.b) && (c1.a == c2.a); } + TTF_Font *_font = nullptr; + SDL_Texture *_texture = nullptr; + std::string _text; + SDL_Color _color; +}; + +class RendererImage +{ +public: + RendererImage(const void *img_data, int img_size); + ~RendererImage(); + SDL_Rect draw(SDL_Renderer *renderer, int w, int h); + int width; + int height; + +private: + SDL_Surface *_surface = nullptr; + SDL_Texture *_texture = nullptr; + float _aspect; +}; + class Renderer { public: Renderer(SDL_Renderer *renderer); ~Renderer(); - bool prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32_t scaler); bool render(AVFrame *frame); - SDL_Texture *texture; - int textureWidth; - int textureHeight; +protected: + SDL_Renderer *_renderer; private: using DrawFuncType = void (Renderer::*)(AVFrame *); @@ -34,6 +67,7 @@ private: std::string name; }; + bool prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32_t scaler); bool prepareTexture(uint32_t format, int width, int height); void rgb(AVFrame *frame); @@ -41,7 +75,10 @@ private: void yuv(AVFrame *frame); void scale(AVFrame *frame); - SDL_Renderer *_renderer; + SDL_Texture *_texture; + int _textureWidth; + int _textureHeight; + DrawFuncType _render; SwsContext *_sws; int _swsWidth; diff --git a/src/resource/background.bmp b/src/resource/background.bmp index a7207730304a2010ed5a330f9f68a989ef258349..6d94d00e0e6f096e794f546e49fbe67227240b5c 100644 GIT binary patch literal 40906 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%0DFWSdh<{p7*pXL4BcZVVr#g&nHNY7 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 D!-Mxu literal 92876 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 AV*mgE diff --git a/src/resource/colors.h b/src/resource/colors.h new file mode 100644 index 0000000..9c4609a --- /dev/null +++ b/src/resource/colors.h @@ -0,0 +1,16 @@ +#ifndef SRC_RESOURCE_COLORS +#define SRC_RESOURCE_COLORS + +#include + +const SDL_Color color1_inactive = {255, 255, 255, 80}; +const SDL_Color color2_inactive = {255, 255, 255, 80}; +const SDL_Color color3_inactive = {255, 255, 255, 80}; +const SDL_Color color4_inactive = {255, 255, 255, 80}; +const SDL_Color color1 = {255, 87, 137, 255}; +const SDL_Color color2 = {135, 98, 255, 255}; +const SDL_Color color3 = {94, 228, 255, 255}; +const SDL_Color color4 = {93, 255, 197, 255}; +const SDL_Color colorError = {255, 83, 64, 255}; + +#endif /* SRC_RESOURCE_COLORS */ diff --git a/src/settings.h b/src/settings.h index e79cec7..3bf720d 100644 --- a/src/settings.h +++ b/src/settings.h @@ -7,31 +7,41 @@ class Settings { public: + // General section static inline Setting vendorid{"vendor-id", 4884}; static inline Setting productid{"product-id ", 5408}; - static inline Setting fullscreen{"fullscreen", true}; - static inline Setting dpi{"dpi", 0}; - static inline Setting width{"width", 720}; - static inline Setting height{"height", 576}; - static inline Setting fps{"fps", 50}; - static inline Setting vsync{"vsync", false}; static inline Setting sourceWidth{"source-width", 720}; static inline Setting sourceHeight{"source-height", 576}; static inline Setting sourceFps{"source-fps", 50}; + static inline Setting width{"width", 720}; + static inline Setting height{"height", 576}; + static inline Setting fps{"fps", 50}; + static inline Setting fullscreen{"fullscreen", true}; static inline Setting logging{"logging", false}; + + // Device configurations section + static inline Setting encryption{"encryption", false}; + static inline Setting autoconnect{"autoconnect", true}; + static inline Setting weakCharge{"weak-charge", true}; + static inline Setting leftDrive{"left-hand-drive", true}; + static inline Setting nightMode{"night-mode", 2}; + static inline Setting dpi{"dpi", 0}; + + // Application configuration section + static inline Setting fontSize{"font-size", 30}; + static inline Setting vsync{"vsync", false}; + static inline Setting aspectCorrection{"aspect-correction", 1}; static inline Setting scaler{"scaler", 2}; static inline Setting fastScale{"fast-render-scale", false}; static inline Setting videoQueue{"video-buffer-size", 32}; - static inline Setting audioQueue{"audio-buffer-size", 16}; - static inline Setting audioDelay{"audio-buffer-wait", 2}; - static inline Setting audioFade{"audio-fade", 0.3}; - static inline Setting fontSize{"font-size", 30}; - static inline Setting encryption{"encryption", false}; - static inline Setting autoconnect{"autoconnect", true}; + static inline Setting audioQueue{"audio-buffer-size", 16}; + static inline Setting audioDelay{"audio-buffer-wait", 2}; + static inline Setting audioFade{"audio-fade", 0.3}; static inline Setting onConnect{"on-connect-script", ""}; - static inline Setting onDisconnect{"on-disconnect-script", ""}; - static inline Setting protocolDebug{"protocol-debug", 0}; - + static inline Setting onDisconnect{"on-disconnect-script", ""}; + + // Debug section + static inline Setting protocolDebug{"protocol-debug", 0}; static void load(const std::string &filename); static void print(); diff --git a/src/struct/video_buffer.h b/src/struct/video_buffer.h index bf88e93..76ba814 100644 --- a/src/struct/video_buffer.h +++ b/src/struct/video_buffer.h @@ -75,6 +75,13 @@ public: _latest.store(_writing.load()); } + void reset() + { + _writing.store(0); + _reading.store(-1); + _latest.store(-1); + } + private: std::atomic _latest; std::atomic _reading;