mirror of
https://github.com/niellun/FastCarPlay.git
synced 2026-06-07 09:38:25 +02:00
New UX, More settings, reorganise code
This commit is contained in:
+71
-44
@@ -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<std::mutex> lock(mtx);
|
||||
|
||||
+14
-7
@@ -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<bool> _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;
|
||||
|
||||
+3
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
-8
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#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)
|
||||
|
||||
+32
-2
@@ -1,10 +1,18 @@
|
||||
#ifndef SRC_HELPER_FUNCTIONS
|
||||
#define SRC_HELPER_FUNCTIONS
|
||||
|
||||
#include <iostream>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <libavutil/error.h>
|
||||
}
|
||||
|
||||
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 */
|
||||
|
||||
@@ -4,13 +4,11 @@
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
#ifndef SRC_HELPER_UFONT
|
||||
#define SRC_HELPER_UFONT
|
||||
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <iostream>
|
||||
|
||||
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 */
|
||||
@@ -1,52 +0,0 @@
|
||||
#ifndef SRC_HELPER_UIMAGE
|
||||
#define SRC_HELPER_UIMAGE
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <iostream>
|
||||
|
||||
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 */
|
||||
@@ -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 <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef SRC_INTERFACE
|
||||
#define SRC_INTERFACE
|
||||
|
||||
#include "renderer.h"
|
||||
#include <string>
|
||||
|
||||
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 */
|
||||
+117
-168
@@ -12,100 +12,44 @@ extern "C"
|
||||
#include <libavutil/imgutils.h> // 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<std::mutex> 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<std::mutex> 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
+23
-14
@@ -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:
|
||||
|
||||
+4
-3
@@ -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 */
|
||||
|
||||
+171
-29
@@ -1,18 +1,156 @@
|
||||
#include "renderer.h"
|
||||
#include "helper/error.h"
|
||||
#include <iostream>
|
||||
#include "settings.h"
|
||||
#include "helper/functions.h"
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
|
||||
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]);
|
||||
|
||||
+42
-5
@@ -8,20 +8,53 @@ extern "C"
|
||||
}
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <string>
|
||||
|
||||
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;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 40 KiB |
@@ -0,0 +1,16 @@
|
||||
#ifndef SRC_RESOURCE_COLORS
|
||||
#define SRC_RESOURCE_COLORS
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
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 */
|
||||
+25
-15
@@ -7,31 +7,41 @@
|
||||
class Settings
|
||||
{
|
||||
public:
|
||||
// General section
|
||||
static inline Setting<int> vendorid{"vendor-id", 4884};
|
||||
static inline Setting<int> productid{"product-id ", 5408};
|
||||
static inline Setting<bool> fullscreen{"fullscreen", true};
|
||||
static inline Setting<int> dpi{"dpi", 0};
|
||||
static inline Setting<int> width{"width", 720};
|
||||
static inline Setting<int> height{"height", 576};
|
||||
static inline Setting<int> fps{"fps", 50};
|
||||
static inline Setting<bool> vsync{"vsync", false};
|
||||
static inline Setting<int> sourceWidth{"source-width", 720};
|
||||
static inline Setting<int> sourceHeight{"source-height", 576};
|
||||
static inline Setting<int> sourceFps{"source-fps", 50};
|
||||
static inline Setting<int> width{"width", 720};
|
||||
static inline Setting<int> height{"height", 576};
|
||||
static inline Setting<int> fps{"fps", 50};
|
||||
static inline Setting<bool> fullscreen{"fullscreen", true};
|
||||
static inline Setting<bool> logging{"logging", false};
|
||||
|
||||
// Device configurations section
|
||||
static inline Setting<bool> encryption{"encryption", false};
|
||||
static inline Setting<bool> autoconnect{"autoconnect", true};
|
||||
static inline Setting<bool> weakCharge{"weak-charge", true};
|
||||
static inline Setting<bool> leftDrive{"left-hand-drive", true};
|
||||
static inline Setting<int> nightMode{"night-mode", 2};
|
||||
static inline Setting<int> dpi{"dpi", 0};
|
||||
|
||||
// Application configuration section
|
||||
static inline Setting<int> fontSize{"font-size", 30};
|
||||
static inline Setting<bool> vsync{"vsync", false};
|
||||
static inline Setting<float> aspectCorrection{"aspect-correction", 1};
|
||||
static inline Setting<int> scaler{"scaler", 2};
|
||||
static inline Setting<bool> fastScale{"fast-render-scale", false};
|
||||
static inline Setting<int> videoQueue{"video-buffer-size", 32};
|
||||
static inline Setting<int> audioQueue{"audio-buffer-size", 16};
|
||||
static inline Setting<int> audioDelay{"audio-buffer-wait", 2};
|
||||
static inline Setting<float> audioFade{"audio-fade", 0.3};
|
||||
static inline Setting<int> fontSize{"font-size", 30};
|
||||
static inline Setting<bool> encryption{"encryption", false};
|
||||
static inline Setting<bool> autoconnect{"autoconnect", true};
|
||||
static inline Setting<int> audioQueue{"audio-buffer-size", 16};
|
||||
static inline Setting<int> audioDelay{"audio-buffer-wait", 2};
|
||||
static inline Setting<float> audioFade{"audio-fade", 0.3};
|
||||
static inline Setting<std::string> onConnect{"on-connect-script", ""};
|
||||
static inline Setting<std::string> onDisconnect{"on-disconnect-script", ""};
|
||||
static inline Setting<int> protocolDebug{"protocol-debug", 0};
|
||||
|
||||
static inline Setting<std::string> onDisconnect{"on-disconnect-script", ""};
|
||||
|
||||
// Debug section
|
||||
static inline Setting<int> protocolDebug{"protocol-debug", 0};
|
||||
|
||||
static void load(const std::string &filename);
|
||||
static void print();
|
||||
|
||||
@@ -75,6 +75,13 @@ public:
|
||||
_latest.store(_writing.load());
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
_writing.store(0);
|
||||
_reading.store(-1);
|
||||
_latest.store(-1);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<int8_t> _latest;
|
||||
std::atomic<int8_t> _reading;
|
||||
|
||||
Reference in New Issue
Block a user