redesign, broadcast server crash fix
This commit is contained in:
@@ -37,7 +37,7 @@ INCLUDES := include include/net lib/Plutonium/include
|
|||||||
EXEFS_SRC := exefs_src
|
EXEFS_SRC := exefs_src
|
||||||
APP_TITLE := NXST
|
APP_TITLE := NXST
|
||||||
APP_AUTHOR := DragonSpirit
|
APP_AUTHOR := DragonSpirit
|
||||||
APP_VERSION := 04.24.2026
|
APP_VERSION := 04.26.2026
|
||||||
ICON := icon.png
|
ICON := icon.png
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
@@ -45,8 +45,7 @@ ICON := icon.png
|
|||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
||||||
|
|
||||||
CFLAGS += -g -O2 -ffunction-sections \
|
CFLAGS += -g -O2 -ffunction-sections $(ARCH) $(DEFINES)
|
||||||
$(ARCH) $(DEFINES)
|
|
||||||
|
|
||||||
CFLAGS += $(INCLUDE) -D__SWITCH__ -D_GNU_SOURCE=1
|
CFLAGS += $(INCLUDE) -D__SWITCH__ -D_GNU_SOURCE=1
|
||||||
|
|
||||||
@@ -55,7 +54,9 @@ CXXFLAGS:= $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17 -g
|
|||||||
ASFLAGS := -g $(ARCH)
|
ASFLAGS := -g $(ARCH)
|
||||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||||
|
|
||||||
LIBS := -lpu -lz -lminizip -lfreetype -lSDL2_mixer -lopusfile -lopus -lmodplug -lmpg123 -lvorbisidec -logg -lSDL2_ttf -lSDL2_gfx -lSDL2_image -lSDL2 -lEGL -lGLESv2 -lglapi -ldrm_nouveau -lwebp -lpng -ljpeg `sdl2-config --libs` `freetype-config --libs` -lnx
|
PKGCONF := $(DEVKITPRO)/portlibs/switch/bin/aarch64-none-elf-pkg-config
|
||||||
|
PKG_LIBS := SDL2_ttf SDL2_gfx SDL2_image SDL2_mixer freetype2 harfbuzz minizip libpng libjpeg libwebp glesv2 egl glapi zlib
|
||||||
|
LIBS := -lpu $(shell $(PKGCONF) --libs $(PKG_LIBS)) -ldrm_nouveau -lharfbuzz -lfreetype -lz
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
# list of directories containing libraries, this must be the top level containing
|
# list of directories containing libraries, this must be the top level containing
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pu/Plutonium>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace theme {
|
||||||
|
using pu::ui::Color;
|
||||||
|
|
||||||
|
namespace color {
|
||||||
|
constexpr Color BgBase{0x10, 0x14, 0x1C, 0xFF};
|
||||||
|
constexpr Color BgSurface{0x18, 0x1F, 0x2A, 0xFF};
|
||||||
|
constexpr Color BgSurface2{0x22, 0x2B, 0x39, 0xFF};
|
||||||
|
constexpr Color Scrim{0x00, 0x00, 0x00, 0xB8};
|
||||||
|
|
||||||
|
constexpr Color Primary{0xE2, 0x4B, 0x55, 0xFF};
|
||||||
|
constexpr Color PrimaryDim{0x9C, 0x33, 0x3A, 0xFF};
|
||||||
|
constexpr Color Accent{0x4A, 0xC2, 0xE0, 0xFF};
|
||||||
|
|
||||||
|
constexpr Color TextPrimary{0xF2, 0xF4, 0xF8, 0xFF};
|
||||||
|
constexpr Color TextSecondary{0xB6, 0xBE, 0xCB, 0xFF};
|
||||||
|
constexpr Color TextMuted{0x70, 0x7A, 0x8C, 0xFF};
|
||||||
|
|
||||||
|
constexpr Color Success{0x55, 0xC8, 0x8A, 0xFF};
|
||||||
|
constexpr Color Error{0xE0, 0x6C, 0x6C, 0xFF};
|
||||||
|
constexpr Color Warning{0xE6, 0xB4, 0x55, 0xFF};
|
||||||
|
|
||||||
|
constexpr Color Divider{0x2A, 0x33, 0x42, 0xFF};
|
||||||
|
constexpr Color FocusRing{0xE2, 0x4B, 0x55, 0xFF};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace space {
|
||||||
|
constexpr int xs = 4;
|
||||||
|
constexpr int sm = 8;
|
||||||
|
constexpr int md = 16;
|
||||||
|
constexpr int lg = 24;
|
||||||
|
constexpr int xl = 32;
|
||||||
|
constexpr int xxl = 48;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace radius {
|
||||||
|
constexpr int sm = 6;
|
||||||
|
constexpr int md = 12;
|
||||||
|
constexpr int lg = 20;
|
||||||
|
constexpr int pill = 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace type {
|
||||||
|
constexpr int Display = 38;
|
||||||
|
constexpr int Title = 30;
|
||||||
|
constexpr int Body = 25;
|
||||||
|
constexpr int Label = 20;
|
||||||
|
constexpr int Caption = 18;
|
||||||
|
|
||||||
|
inline std::string font(int size) {
|
||||||
|
return "DefaultFont@" + std::to_string(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace layout {
|
||||||
|
constexpr int ScreenW = 1280;
|
||||||
|
constexpr int ScreenH = 720;
|
||||||
|
constexpr int HeaderH = 72;
|
||||||
|
constexpr int HintH = 56;
|
||||||
|
constexpr int ContentTop = HeaderH;
|
||||||
|
constexpr int ContentH = ScreenH - HeaderH - HintH;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace motion {
|
||||||
|
constexpr int FadeFrames = 20;
|
||||||
|
constexpr int SlideFrames = 14;
|
||||||
|
constexpr int SpinnerFrames = 72;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace font {
|
||||||
|
constexpr const char* Default = "Inter";
|
||||||
|
constexpr const char* Medium = "InterMedium";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,17 +3,49 @@
|
|||||||
#include <title.hpp>
|
#include <title.hpp>
|
||||||
#include <account.hpp>
|
#include <account.hpp>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <ui/HeaderBar.hpp>
|
||||||
|
#include <ui/HintBar.hpp>
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
|
enum class TitlesFocus { List, Actions };
|
||||||
|
enum class TitlesAction { Transfer, Receive };
|
||||||
|
|
||||||
class TitlesLayout : public pu::ui::Layout {
|
class TitlesLayout : public pu::ui::Layout {
|
||||||
private:
|
private:
|
||||||
|
|
||||||
pu::ui::elm::Menu::Ref titlesMenu;
|
pu::ui::elm::Menu::Ref titlesMenu;
|
||||||
std::unordered_map<AccountUid, pu::ui::elm::Menu::Ref> menuCache;
|
std::unordered_map<AccountUid, std::vector<pu::ui::elm::MenuItem::Ref>> menuCache;
|
||||||
bool m_inputLocked = false;
|
bool m_inputLocked = false;
|
||||||
|
std::unique_ptr<HeaderBar> header;
|
||||||
|
std::unique_ptr<HintBar> hints;
|
||||||
|
|
||||||
|
pu::ui::elm::Rectangle::Ref panelBg;
|
||||||
|
pu::ui::elm::TextBlock::Ref panelTitle;
|
||||||
|
pu::ui::elm::TextBlock::Ref panelHint;
|
||||||
|
pu::ui::elm::Rectangle::Ref btnTransferBg;
|
||||||
|
pu::ui::elm::TextBlock::Ref btnTransferText;
|
||||||
|
pu::ui::elm::Rectangle::Ref btnReceiveBg;
|
||||||
|
pu::ui::elm::TextBlock::Ref btnReceiveText;
|
||||||
|
pu::ui::elm::TextBlock::Ref panelFooter;
|
||||||
|
pu::ui::elm::TextBlock::Ref emptyText;
|
||||||
|
pu::ui::elm::TextBlock::Ref emptySub;
|
||||||
|
|
||||||
|
TitlesFocus focus = TitlesFocus::List;
|
||||||
|
TitlesAction action = TitlesAction::Transfer;
|
||||||
|
int lockedListIndex = 0;
|
||||||
|
|
||||||
|
void refreshPanel();
|
||||||
|
void refreshButtons();
|
||||||
|
void updateHints();
|
||||||
|
void runTransfer(int index, Title& title);
|
||||||
|
void runReceive(int index, Title& title);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
TitlesLayout();
|
||||||
void InitTitles();
|
void InitTitles();
|
||||||
void LockInput() { m_inputLocked = true; }
|
void LockInput() { m_inputLocked = true; }
|
||||||
void UnlockInput() { m_inputLocked = false; }
|
void UnlockInput() { m_inputLocked = false; }
|
||||||
|
|||||||
+55
-31
@@ -1,51 +1,83 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <pu/Plutonium>
|
#include <pu/Plutonium>
|
||||||
|
#include <Theme.hpp>
|
||||||
|
#include <util.hpp>
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
class TransferOverlay : public pu::ui::Overlay {
|
class TransferOverlay : public pu::ui::Overlay {
|
||||||
private:
|
private:
|
||||||
|
pu::ui::elm::Rectangle::Ref card;
|
||||||
pu::ui::elm::TextBlock::Ref titleText;
|
pu::ui::elm::TextBlock::Ref titleText;
|
||||||
pu::ui::elm::TextBlock::Ref statusText;
|
pu::ui::elm::TextBlock::Ref statusText;
|
||||||
|
pu::ui::elm::Rectangle::Ref progressTrack;
|
||||||
pu::ui::elm::ProgressBar::Ref progressBar;
|
pu::ui::elm::ProgressBar::Ref progressBar;
|
||||||
|
pu::ui::elm::TextBlock::Ref indeterminateText;
|
||||||
pu::ui::elm::TextBlock::Ref hintText;
|
pu::ui::elm::TextBlock::Ref hintText;
|
||||||
|
|
||||||
|
static constexpr int CardW = 720;
|
||||||
|
static constexpr int CardH = 360;
|
||||||
|
static constexpr int CardX = (theme::layout::ScreenW - CardW) / 2;
|
||||||
|
static constexpr int CardY = (theme::layout::ScreenH - CardH) / 2;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr int OvlX = 200;
|
|
||||||
static constexpr int OvlY = 240;
|
|
||||||
static constexpr int OvlW = 880;
|
|
||||||
static constexpr int OvlH = 240;
|
|
||||||
|
|
||||||
TransferOverlay(const std::string &title)
|
TransferOverlay(const std::string &title)
|
||||||
: Overlay(OvlX, OvlY, OvlW, OvlH, pu::ui::Color(30, 30, 30, 220))
|
: Overlay(0, 0, theme::layout::ScreenW, theme::layout::ScreenH, theme::color::Scrim)
|
||||||
{
|
{
|
||||||
titleText = pu::ui::elm::TextBlock::New(40, 30, title);
|
using namespace theme;
|
||||||
titleText->SetColor(pu::ui::Color(255, 255, 255, 255));
|
|
||||||
|
|
||||||
statusText = pu::ui::elm::TextBlock::New(40, 90, "");
|
card = pu::ui::elm::Rectangle::New(
|
||||||
statusText->SetColor(pu::ui::Color(180, 180, 180, 255));
|
CardX, CardY, CardW, CardH, color::BgSurface, radius::lg);
|
||||||
|
|
||||||
progressBar = pu::ui::elm::ProgressBar::New(40, 140, OvlW - 80, 20, 100.0);
|
titleText = pu::ui::elm::TextBlock::New(
|
||||||
progressBar->SetProgressColor(pu::ui::Color(100, 180, 255, 255));
|
CardX + space::lg, CardY + space::lg, title);
|
||||||
progressBar->SetBackgroundColor(pu::ui::Color(70, 70, 70, 255));
|
titleText->SetFont(type::font(type::Title));
|
||||||
|
titleText->SetColor(color::TextPrimary);
|
||||||
|
|
||||||
hintText = pu::ui::elm::TextBlock::New(40, 195, "Press B to cancel");
|
statusText = pu::ui::elm::TextBlock::New(
|
||||||
hintText->SetColor(pu::ui::Color(130, 130, 130, 255));
|
CardX + space::lg,
|
||||||
|
CardY + space::lg + 56,
|
||||||
|
"");
|
||||||
|
statusText->SetFont(type::font(type::Body));
|
||||||
|
statusText->SetColor(color::TextSecondary);
|
||||||
|
|
||||||
|
int barX = CardX + space::lg;
|
||||||
|
int barY = CardY + space::lg + 56 + 56;
|
||||||
|
int barW = CardW - 2 * space::lg;
|
||||||
|
|
||||||
|
progressTrack = pu::ui::elm::Rectangle::New(
|
||||||
|
barX, barY, barW, 8, color::Divider, radius::sm);
|
||||||
|
|
||||||
|
progressBar = pu::ui::elm::ProgressBar::New(
|
||||||
|
barX, barY, barW, 8, 100.0);
|
||||||
|
progressBar->SetProgressColor(color::Primary);
|
||||||
|
progressBar->SetBackgroundColor(color::Divider);
|
||||||
|
|
||||||
|
indeterminateText = pu::ui::elm::TextBlock::New(
|
||||||
|
barX, barY - 4, "Preparing transfer...");
|
||||||
|
indeterminateText->SetFont(type::font(type::Body));
|
||||||
|
indeterminateText->SetColor(color::TextMuted);
|
||||||
|
indeterminateText->SetVisible(false);
|
||||||
|
|
||||||
|
hintText = pu::ui::elm::TextBlock::New(
|
||||||
|
CardX + space::lg,
|
||||||
|
CardY + CardH - space::lg - 18,
|
||||||
|
"B to cancel");
|
||||||
|
hintText->SetFont(type::font(type::Caption));
|
||||||
|
hintText->SetColor(color::TextMuted);
|
||||||
|
|
||||||
|
this->Add(card);
|
||||||
this->Add(titleText);
|
this->Add(titleText);
|
||||||
this->Add(statusText);
|
this->Add(statusText);
|
||||||
|
this->Add(progressTrack);
|
||||||
this->Add(progressBar);
|
this->Add(progressBar);
|
||||||
|
this->Add(indeterminateText);
|
||||||
this->Add(hintText);
|
this->Add(hintText);
|
||||||
}
|
}
|
||||||
PU_SMART_CTOR(TransferOverlay)
|
PU_SMART_CTOR(TransferOverlay)
|
||||||
|
|
||||||
void SetStatus(const std::string &status) {
|
void SetStatus(const std::string &status) {
|
||||||
static constexpr size_t MaxChars = 48;
|
statusText->SetText(StringUtils::elide(status, 56));
|
||||||
if (status.size() > MaxChars) {
|
|
||||||
statusText->SetText(status.substr(0, MaxChars - 3) + "...");
|
|
||||||
} else {
|
|
||||||
statusText->SetText(status);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetProgress(double val) {
|
void SetProgress(double val) {
|
||||||
@@ -53,17 +85,9 @@ namespace ui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SetProgressVisible(bool visible) {
|
void SetProgressVisible(bool visible) {
|
||||||
|
progressTrack->SetVisible(visible);
|
||||||
progressBar->SetVisible(visible);
|
progressBar->SetVisible(visible);
|
||||||
if (visible) {
|
indeterminateText->SetVisible(!visible);
|
||||||
this->SetHeight(OvlH);
|
|
||||||
this->SetY(OvlY);
|
|
||||||
hintText->SetY(195);
|
|
||||||
} else {
|
|
||||||
static constexpr int SmallH = 160;
|
|
||||||
this->SetHeight(SmallH);
|
|
||||||
this->SetY((720 - SmallH) / 2);
|
|
||||||
hintText->SetY(120);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
#include <pu/Plutonium>
|
#include <pu/Plutonium>
|
||||||
#include <const.h>
|
#include <const.h>
|
||||||
|
#include <ui/HeaderBar.hpp>
|
||||||
|
#include <ui/HintBar.hpp>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
@@ -9,6 +12,8 @@ namespace ui {
|
|||||||
pu::ui::elm::Menu::Ref usersMenu;
|
pu::ui::elm::Menu::Ref usersMenu;
|
||||||
pu::ui::elm::Rectangle::Ref loadingBg;
|
pu::ui::elm::Rectangle::Ref loadingBg;
|
||||||
pu::ui::elm::TextBlock::Ref loadingText;
|
pu::ui::elm::TextBlock::Ref loadingText;
|
||||||
|
std::unique_ptr<HeaderBar> header;
|
||||||
|
std::unique_ptr<HintBar> hints;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ namespace Account {
|
|||||||
std::vector<AccountUid> ids(void);
|
std::vector<AccountUid> ids(void);
|
||||||
AccountUid selectAccount(void);
|
AccountUid selectAccount(void);
|
||||||
std::string username(AccountUid id);
|
std::string username(AccountUid id);
|
||||||
|
std::string iconPath(AccountUid id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
+5
-1
@@ -1,2 +1,6 @@
|
|||||||
#define BACKGROUND_COLOR COLOR("00FFFFFF")
|
#pragma once
|
||||||
|
|
||||||
|
#include <Theme.hpp>
|
||||||
|
|
||||||
#define COLOR(hex) pu::ui::Color::FromHex(hex)
|
#define COLOR(hex) pu::ui::Color::FromHex(hex)
|
||||||
|
#define BACKGROUND_COLOR theme::color::BgBase
|
||||||
|
|||||||
+2
-2
@@ -40,10 +40,10 @@ public:
|
|||||||
return mLogger;
|
return mLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline static const std::string INFO = "[ INFO]";
|
inline static const std::string INFO = "[INFO]";
|
||||||
inline static const std::string DEBUG = "[DEBUG]";
|
inline static const std::string DEBUG = "[DEBUG]";
|
||||||
inline static const std::string ERROR = "[ERROR]";
|
inline static const std::string ERROR = "[ERROR]";
|
||||||
inline static const std::string WARN = "[ WARN]";
|
inline static const std::string WARN = "[WARN]";
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
void log(const std::string& level, const std::string& format = {}, Args... args)
|
void log(const std::string& level, const std::string& format = {}, Args... args)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
int startSendingThread();
|
int startSendingThread();
|
||||||
bool isServerTransferDone();
|
bool isServerTransferDone();
|
||||||
bool isServerTransferCancelled();
|
bool isServerTransferCancelled();
|
||||||
|
bool isServerWorkersIdle();
|
||||||
void cancelServerTransfer();
|
void cancelServerTransfer();
|
||||||
double getServerProgress();
|
double getServerProgress();
|
||||||
std::string getServerStatusText();
|
std::string getServerStatusText();
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <pu/Plutonium>
|
||||||
|
#include <Theme.hpp>
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
class Card {
|
||||||
|
public:
|
||||||
|
pu::ui::elm::Rectangle::Ref bg;
|
||||||
|
|
||||||
|
Card(pu::ui::Layout* parent, int x, int y, int w, int h,
|
||||||
|
pu::ui::Color color = theme::color::BgSurface,
|
||||||
|
int rad = theme::radius::lg) {
|
||||||
|
bg = pu::ui::elm::Rectangle::New(x, y, w, h, color, rad);
|
||||||
|
parent->Add(bg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <pu/Plutonium>
|
||||||
|
#include <Theme.hpp>
|
||||||
|
#include <ui/UiContext.hpp>
|
||||||
|
#include <account.hpp>
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
class HeaderBar {
|
||||||
|
private:
|
||||||
|
pu::ui::elm::Rectangle::Ref bg;
|
||||||
|
pu::ui::elm::Rectangle::Ref divider;
|
||||||
|
pu::ui::elm::TextBlock::Ref appName;
|
||||||
|
pu::ui::elm::TextBlock::Ref subtitle;
|
||||||
|
pu::ui::elm::Rectangle::Ref chipBg;
|
||||||
|
pu::ui::elm::Image::Ref avatar;
|
||||||
|
pu::ui::elm::TextBlock::Ref userName;
|
||||||
|
|
||||||
|
public:
|
||||||
|
HeaderBar(pu::ui::Layout* parent, const std::string& sub = "Save Transfer") {
|
||||||
|
using namespace theme;
|
||||||
|
|
||||||
|
bg = pu::ui::elm::Rectangle::New(
|
||||||
|
0, 0, layout::ScreenW, layout::HeaderH, color::BgSurface);
|
||||||
|
divider = pu::ui::elm::Rectangle::New(
|
||||||
|
0, layout::HeaderH - 1, layout::ScreenW, 1, color::Divider);
|
||||||
|
|
||||||
|
appName = pu::ui::elm::TextBlock::New(space::lg, 8, "NXST");
|
||||||
|
appName->SetFont(type::font(type::Title));
|
||||||
|
appName->SetColor(color::TextPrimary);
|
||||||
|
|
||||||
|
subtitle = pu::ui::elm::TextBlock::New(space::lg, 46, sub);
|
||||||
|
subtitle->SetFont(type::font(type::Caption));
|
||||||
|
subtitle->SetColor(color::TextMuted);
|
||||||
|
|
||||||
|
const int chipW = 280;
|
||||||
|
const int chipX = layout::ScreenW - chipW - space::lg;
|
||||||
|
chipBg = pu::ui::elm::Rectangle::New(
|
||||||
|
chipX, 16, chipW, 40,
|
||||||
|
color::BgSurface2, radius::pill);
|
||||||
|
chipBg->SetVisible(false);
|
||||||
|
|
||||||
|
avatar = pu::ui::elm::Image::New(chipX + 4, 20, "");
|
||||||
|
avatar->SetWidth(32);
|
||||||
|
avatar->SetHeight(32);
|
||||||
|
avatar->SetVisible(false);
|
||||||
|
|
||||||
|
userName = pu::ui::elm::TextBlock::New(chipX + 44, 24, "");
|
||||||
|
userName->SetFont(type::font(type::Body));
|
||||||
|
userName->SetColor(color::TextPrimary);
|
||||||
|
userName->SetVisible(false);
|
||||||
|
|
||||||
|
parent->Add(bg);
|
||||||
|
parent->Add(divider);
|
||||||
|
parent->Add(appName);
|
||||||
|
parent->Add(subtitle);
|
||||||
|
parent->Add(chipBg);
|
||||||
|
parent->Add(avatar);
|
||||||
|
parent->Add(userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetUser(const std::optional<AccountUid>& uid, const std::string& name) {
|
||||||
|
const bool show = uid.has_value();
|
||||||
|
chipBg->SetVisible(show);
|
||||||
|
userName->SetVisible(show);
|
||||||
|
if (show) {
|
||||||
|
userName->SetText(name);
|
||||||
|
std::string path = Account::iconPath(*uid);
|
||||||
|
if (!path.empty()) {
|
||||||
|
avatar->SetImage(path);
|
||||||
|
avatar->SetWidth(32);
|
||||||
|
avatar->SetHeight(32);
|
||||||
|
avatar->SetVisible(avatar->IsImageValid());
|
||||||
|
} else {
|
||||||
|
avatar->SetVisible(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
avatar->SetVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetSubtitle(const std::string& text) {
|
||||||
|
subtitle->SetText(text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <pu/Plutonium>
|
||||||
|
#include <Theme.hpp>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
struct Hint {
|
||||||
|
std::string glyph;
|
||||||
|
std::string label;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HintBar {
|
||||||
|
private:
|
||||||
|
pu::ui::Layout* parent;
|
||||||
|
pu::ui::elm::Rectangle::Ref bg;
|
||||||
|
pu::ui::elm::Rectangle::Ref divider;
|
||||||
|
std::vector<pu::ui::elm::TextBlock::Ref> labels;
|
||||||
|
|
||||||
|
public:
|
||||||
|
HintBar(pu::ui::Layout* p) : parent(p) {
|
||||||
|
using namespace theme;
|
||||||
|
bg = pu::ui::elm::Rectangle::New(
|
||||||
|
0, layout::ScreenH - layout::HintH,
|
||||||
|
layout::ScreenW, layout::HintH, color::BgSurface);
|
||||||
|
divider = pu::ui::elm::Rectangle::New(
|
||||||
|
0, layout::ScreenH - layout::HintH,
|
||||||
|
layout::ScreenW, 1, color::Divider);
|
||||||
|
parent->Add(bg);
|
||||||
|
parent->Add(divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetHints(const std::vector<Hint>& hints) {
|
||||||
|
using namespace theme;
|
||||||
|
for (auto& l : labels) l->SetVisible(false);
|
||||||
|
labels.clear();
|
||||||
|
|
||||||
|
int x = layout::ScreenW - space::lg;
|
||||||
|
int y = layout::ScreenH - layout::HintH + 18;
|
||||||
|
for (auto it = hints.rbegin(); it != hints.rend(); ++it) {
|
||||||
|
std::string text = it->glyph + " " + it->label;
|
||||||
|
auto tb = pu::ui::elm::TextBlock::New(0, y, text);
|
||||||
|
tb->SetFont(type::font(type::Label));
|
||||||
|
tb->SetColor(color::TextSecondary);
|
||||||
|
int w = tb->GetWidth();
|
||||||
|
x -= w;
|
||||||
|
tb->SetX(x);
|
||||||
|
x -= space::xl;
|
||||||
|
parent->Add(tb);
|
||||||
|
labels.push_back(tb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <optional>
|
||||||
|
#include <switch.h>
|
||||||
|
#include <account.hpp>
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
struct UiContext {
|
||||||
|
std::optional<AccountUid> selectedUser;
|
||||||
|
std::string selectedUserName;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -47,6 +47,7 @@ namespace StringUtils {
|
|||||||
std::string removeAccents(std::string str);
|
std::string removeAccents(std::string str);
|
||||||
std::string removeNotAscii(std::string str);
|
std::string removeNotAscii(std::string str);
|
||||||
std::u16string UTF8toUTF16(const char* src);
|
std::u16string UTF8toUTF16(const char* src);
|
||||||
|
std::string elide(const std::string& s, size_t maxChars);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include <MainApplication.hpp>
|
#include <MainApplication.hpp>
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
#include "main.hpp"
|
#include "main.hpp"
|
||||||
|
#include <server.hpp>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
static int nxlink_sock = -1;
|
static int nxlink_sock = -1;
|
||||||
|
|
||||||
@@ -19,6 +21,10 @@ extern "C" void userAppInit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void userAppExit() {
|
extern "C" void userAppExit() {
|
||||||
|
cancelServerTransfer();
|
||||||
|
for (int i = 0; i < 150 && !isServerWorkersIdle(); i++) {
|
||||||
|
usleep(10000);
|
||||||
|
}
|
||||||
if (nxlink_sock != -1) {
|
if (nxlink_sock != -1) {
|
||||||
close(nxlink_sock);
|
close(nxlink_sock);
|
||||||
}
|
}
|
||||||
@@ -50,6 +56,11 @@ int main() {
|
|||||||
renderer_opts.UseImage(pu::ui::render::IMGAllFlags);
|
renderer_opts.UseImage(pu::ui::render::IMGAllFlags);
|
||||||
renderer_opts.UseAudio(pu::ui::render::MixerAllFlags);
|
renderer_opts.UseAudio(pu::ui::render::MixerAllFlags);
|
||||||
renderer_opts.UseTTF();
|
renderer_opts.UseTTF();
|
||||||
|
renderer_opts.SetExtraDefaultFontSize(theme::type::Caption);
|
||||||
|
renderer_opts.SetExtraDefaultFontSize(theme::type::Label);
|
||||||
|
renderer_opts.SetExtraDefaultFontSize(theme::type::Body);
|
||||||
|
renderer_opts.SetExtraDefaultFontSize(theme::type::Title);
|
||||||
|
renderer_opts.SetExtraDefaultFontSize(theme::type::Display);
|
||||||
|
|
||||||
auto renderer = pu::ui::render::Renderer::New(renderer_opts);
|
auto renderer = pu::ui::render::Renderer::New(renderer_opts);
|
||||||
|
|
||||||
|
|||||||
+273
-97
@@ -6,43 +6,256 @@
|
|||||||
#include <server.hpp>
|
#include <server.hpp>
|
||||||
#include <TransferOverlay.hpp>
|
#include <TransferOverlay.hpp>
|
||||||
|
|
||||||
static std::vector<uint64_t> accSids, devSids, bcatSids, cacheSids;
|
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
extern MainApplication *mainApp;
|
extern MainApplication *mainApp;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int ListX = theme::space::lg;
|
||||||
|
constexpr int ListW = 760;
|
||||||
|
constexpr int PanelX = ListX + ListW + theme::space::xl;
|
||||||
|
constexpr int PanelW = theme::layout::ScreenW - PanelX - theme::space::lg;
|
||||||
|
constexpr int ContentY = theme::layout::ContentTop + theme::space::md;
|
||||||
|
constexpr int ContentH = theme::layout::ContentH - 2 * theme::space::md;
|
||||||
|
constexpr int BtnH = 56;
|
||||||
|
constexpr int BtnW = PanelW - 2 * theme::space::lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
TitlesLayout::TitlesLayout() : Layout::Layout() {
|
||||||
|
using namespace theme;
|
||||||
|
|
||||||
|
this->titlesMenu = pu::ui::elm::Menu::New(
|
||||||
|
ListX, ContentY, ListW,
|
||||||
|
color::BgBase, color::BgSurface2,
|
||||||
|
88, 6);
|
||||||
|
this->titlesMenu->SetScrollbarColor(color::Primary);
|
||||||
|
this->titlesMenu->SetItemsFocusColor(color::BgSurface2);
|
||||||
|
this->titlesMenu->SetOnSelectionChanged([this]() { this->refreshPanel(); });
|
||||||
|
this->SetBackgroundColor(color::BgBase);
|
||||||
|
this->Add(this->titlesMenu);
|
||||||
|
|
||||||
|
this->panelBg = pu::ui::elm::Rectangle::New(
|
||||||
|
PanelX, ContentY, PanelW, ContentH, color::BgSurface, radius::lg);
|
||||||
|
this->Add(this->panelBg);
|
||||||
|
|
||||||
|
this->panelTitle = pu::ui::elm::TextBlock::New(
|
||||||
|
PanelX + space::lg, ContentY + space::lg, "");
|
||||||
|
this->panelTitle->SetFont(type::font(type::Title));
|
||||||
|
this->panelTitle->SetColor(color::TextPrimary);
|
||||||
|
this->Add(this->panelTitle);
|
||||||
|
|
||||||
|
this->panelHint = pu::ui::elm::TextBlock::New(
|
||||||
|
PanelX + space::lg, ContentY + space::lg + 48, "Pick an action:");
|
||||||
|
this->panelHint->SetFont(type::font(type::Body));
|
||||||
|
this->panelHint->SetColor(color::TextSecondary);
|
||||||
|
this->Add(this->panelHint);
|
||||||
|
|
||||||
|
int btnY = ContentY + 200;
|
||||||
|
this->btnTransferBg = pu::ui::elm::Rectangle::New(
|
||||||
|
PanelX + space::lg, btnY, BtnW, BtnH, color::BgSurface2, radius::md);
|
||||||
|
this->Add(this->btnTransferBg);
|
||||||
|
this->btnTransferText = pu::ui::elm::TextBlock::New(
|
||||||
|
PanelX + space::lg + space::md, btnY + 14, "Transfer to PC");
|
||||||
|
this->btnTransferText->SetFont(type::font(type::Body));
|
||||||
|
this->btnTransferText->SetColor(color::TextSecondary);
|
||||||
|
this->Add(this->btnTransferText);
|
||||||
|
|
||||||
|
int btnY2 = btnY + BtnH + space::md;
|
||||||
|
this->btnReceiveBg = pu::ui::elm::Rectangle::New(
|
||||||
|
PanelX + space::lg, btnY2, BtnW, BtnH, color::BgSurface2, radius::md);
|
||||||
|
this->Add(this->btnReceiveBg);
|
||||||
|
this->btnReceiveText = pu::ui::elm::TextBlock::New(
|
||||||
|
PanelX + space::lg + space::md, btnY2 + 14, "Receive from PC");
|
||||||
|
this->btnReceiveText->SetFont(type::font(type::Body));
|
||||||
|
this->btnReceiveText->SetColor(color::TextSecondary);
|
||||||
|
this->Add(this->btnReceiveText);
|
||||||
|
|
||||||
|
this->panelFooter = pu::ui::elm::TextBlock::New(
|
||||||
|
PanelX + space::lg,
|
||||||
|
ContentY + ContentH - space::lg - 18,
|
||||||
|
"Save data only");
|
||||||
|
this->panelFooter->SetFont(type::font(type::Caption));
|
||||||
|
this->panelFooter->SetColor(color::TextMuted);
|
||||||
|
this->Add(this->panelFooter);
|
||||||
|
|
||||||
|
this->emptyText = pu::ui::elm::TextBlock::New(
|
||||||
|
ListX + ListW / 2 - 280, ContentY + ContentH / 2 - 40,
|
||||||
|
"No save data on this profile");
|
||||||
|
this->emptyText->SetFont(type::font(type::Display));
|
||||||
|
this->emptyText->SetColor(color::TextPrimary);
|
||||||
|
this->emptyText->SetVisible(false);
|
||||||
|
this->Add(this->emptyText);
|
||||||
|
|
||||||
|
this->emptySub = pu::ui::elm::TextBlock::New(
|
||||||
|
ListX + ListW / 2 - 220, ContentY + ContentH / 2 + 16,
|
||||||
|
"Play something first, then come back.");
|
||||||
|
this->emptySub->SetFont(type::font(type::Body));
|
||||||
|
this->emptySub->SetColor(color::TextMuted);
|
||||||
|
this->emptySub->SetVisible(false);
|
||||||
|
this->Add(this->emptySub);
|
||||||
|
|
||||||
|
this->header = std::make_unique<HeaderBar>(this, "Save Transfer");
|
||||||
|
this->hints = std::make_unique<HintBar>(this);
|
||||||
|
this->updateHints();
|
||||||
|
}
|
||||||
|
|
||||||
void TitlesLayout::InitTitles() {
|
void TitlesLayout::InitTitles() {
|
||||||
|
using namespace theme;
|
||||||
Logger::getInstance().log(Logger::INFO, "InitTitles");
|
Logger::getInstance().log(Logger::INFO, "InitTitles");
|
||||||
|
|
||||||
auto it = this->menuCache.find(g_currentUId);
|
auto it = this->menuCache.find(g_currentUId);
|
||||||
|
std::vector<pu::ui::elm::MenuItem::Ref>* items;
|
||||||
if (it != this->menuCache.end()) {
|
if (it != this->menuCache.end()) {
|
||||||
this->titlesMenu = it->second;
|
items = &it->second;
|
||||||
} else {
|
} else {
|
||||||
auto menu = pu::ui::elm::Menu::New(0, 0, 1280, COLOR("#67000000"), COLOR("#170909FF"), 94, 7);
|
std::vector<pu::ui::elm::MenuItem::Ref> built;
|
||||||
for (size_t i = 0; i < getTitleCount(g_currentUId); i++) {
|
for (size_t i = 0; i < getTitleCount(g_currentUId); i++) {
|
||||||
Title title;
|
Title title;
|
||||||
getTitle(title, g_currentUId, i);
|
getTitle(title, g_currentUId, i);
|
||||||
auto titleItem = pu::ui::elm::MenuItem::New(title.name().c_str());
|
auto titleItem = pu::ui::elm::MenuItem::New(title.name().c_str());
|
||||||
titleItem->SetColor(COLOR("#FFFFFFFF"));
|
titleItem->SetColor(color::TextPrimary);
|
||||||
menu->AddItem(titleItem);
|
built.push_back(titleItem);
|
||||||
}
|
}
|
||||||
this->menuCache.emplace(g_currentUId, menu);
|
auto inserted = this->menuCache.emplace(g_currentUId, std::move(built));
|
||||||
this->titlesMenu = menu;
|
items = &inserted.first->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->Clear();
|
this->titlesMenu->ClearItems();
|
||||||
this->Add(this->titlesMenu);
|
for (auto& item : *items) {
|
||||||
|
this->titlesMenu->AddItem(item);
|
||||||
|
}
|
||||||
|
this->titlesMenu->SetSelectedIndex(0);
|
||||||
|
|
||||||
this->SetBackgroundColor(BACKGROUND_COLOR);
|
const bool empty = items->empty();
|
||||||
|
this->titlesMenu->SetVisible(!empty);
|
||||||
|
this->panelBg->SetVisible(!empty);
|
||||||
|
this->panelTitle->SetVisible(!empty);
|
||||||
|
this->panelHint->SetVisible(!empty);
|
||||||
|
this->btnTransferBg->SetVisible(!empty);
|
||||||
|
this->btnTransferText->SetVisible(!empty);
|
||||||
|
this->btnReceiveBg->SetVisible(!empty);
|
||||||
|
this->btnReceiveText->SetVisible(!empty);
|
||||||
|
this->panelFooter->SetVisible(!empty);
|
||||||
|
this->emptyText->SetVisible(empty);
|
||||||
|
this->emptySub->SetVisible(empty);
|
||||||
|
|
||||||
|
this->focus = TitlesFocus::List;
|
||||||
|
this->action = TitlesAction::Transfer;
|
||||||
|
this->refreshPanel();
|
||||||
|
this->refreshButtons();
|
||||||
|
this->updateHints();
|
||||||
|
|
||||||
|
this->header->SetUser(g_currentUId, Account::username(g_currentUId));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitlesLayout::refreshPanel() {
|
||||||
|
if (this->titlesMenu->GetItems().empty()) return;
|
||||||
|
int idx = this->titlesMenu->GetSelectedIndex();
|
||||||
|
Title title;
|
||||||
|
getTitle(title, g_currentUId, idx);
|
||||||
|
this->panelTitle->SetText(StringUtils::elide(title.name(), 24));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitlesLayout::refreshButtons() {
|
||||||
|
using namespace theme;
|
||||||
|
const bool active = (focus == TitlesFocus::Actions);
|
||||||
|
if (active && action == TitlesAction::Transfer) {
|
||||||
|
this->btnTransferBg->SetColor(color::Primary);
|
||||||
|
this->btnTransferText->SetColor(color::TextPrimary);
|
||||||
|
this->btnReceiveBg->SetColor(color::BgSurface2);
|
||||||
|
this->btnReceiveText->SetColor(color::TextSecondary);
|
||||||
|
} else if (active && action == TitlesAction::Receive) {
|
||||||
|
this->btnTransferBg->SetColor(color::BgSurface2);
|
||||||
|
this->btnTransferText->SetColor(color::TextSecondary);
|
||||||
|
this->btnReceiveBg->SetColor(color::Accent);
|
||||||
|
this->btnReceiveText->SetColor(color::BgBase);
|
||||||
|
} else {
|
||||||
|
this->btnTransferBg->SetColor(color::BgSurface2);
|
||||||
|
this->btnTransferText->SetColor(color::TextSecondary);
|
||||||
|
this->btnReceiveBg->SetColor(color::BgSurface2);
|
||||||
|
this->btnReceiveText->SetColor(color::TextSecondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitlesLayout::updateHints() {
|
||||||
|
if (focus == TitlesFocus::List) {
|
||||||
|
this->hints->SetHints({{"A", "Choose action"}, {"B", "Back"}, {"+", "Quit"}});
|
||||||
|
} else {
|
||||||
|
this->hints->SetHints({{"A", "Confirm"}, {"B", "Back"}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitlesLayout::runTransfer(int index, Title& title) {
|
||||||
|
auto ovl = TransferOverlay::New("Transferring save data...");
|
||||||
|
this->titlesMenu->SetVisible(false);
|
||||||
|
mainApp->StartOverlay(ovl);
|
||||||
|
this->LockInput();
|
||||||
|
if (transfer_files(index, g_currentUId) != 0) {
|
||||||
|
mainApp->EndOverlay();
|
||||||
|
this->titlesMenu->SetVisible(true);
|
||||||
|
this->UnlockInput();
|
||||||
|
mainApp->CreateShowDialog("Transfer", "Failed to start transfer.", {"OK"}, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (!isClientTransferDone()) {
|
||||||
|
ovl->SetStatus(getClientStatusText());
|
||||||
|
ovl->SetProgressVisible(isClientProgressKnown());
|
||||||
|
ovl->SetProgress(getClientProgress());
|
||||||
|
mainApp->CallForRender();
|
||||||
|
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
|
||||||
|
cancelClientTransfer();
|
||||||
|
}
|
||||||
|
svcSleepThread(16666666LL);
|
||||||
|
}
|
||||||
|
mainApp->EndOverlay();
|
||||||
|
this->titlesMenu->SetVisible(true);
|
||||||
|
this->UnlockInput();
|
||||||
|
|
||||||
|
if (isClientConnectionFailed()) {
|
||||||
|
mainApp->CreateShowDialog("Transfer", getClientFailReason(), {"OK"}, true);
|
||||||
|
} else if (isClientTransferCancelled()) {
|
||||||
|
mainApp->CreateShowDialog("Transfer", "Transfer cancelled.", {"OK"}, true);
|
||||||
|
} else {
|
||||||
|
mainApp->CreateShowDialog("Transfer", "Save data sent successfully!", {"OK"}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitlesLayout::runReceive(int index, Title& title) {
|
||||||
|
if (startSendingThread() != 0) {
|
||||||
|
mainApp->CreateShowDialog("Receive", "Failed to start receiver.\nCheck network connection.", {"OK"}, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto ovl = TransferOverlay::New("Receiving save data...");
|
||||||
|
this->titlesMenu->SetVisible(false);
|
||||||
|
mainApp->StartOverlay(ovl);
|
||||||
|
this->LockInput();
|
||||||
|
while (!isServerTransferDone()) {
|
||||||
|
ovl->SetStatus(getServerStatusText());
|
||||||
|
ovl->SetProgress(getServerProgress());
|
||||||
|
mainApp->CallForRender();
|
||||||
|
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
|
||||||
|
cancelServerTransfer();
|
||||||
|
}
|
||||||
|
svcSleepThread(16666666LL);
|
||||||
|
}
|
||||||
|
mainApp->EndOverlay();
|
||||||
|
this->titlesMenu->SetVisible(true);
|
||||||
|
this->UnlockInput();
|
||||||
|
|
||||||
|
if (isServerTransferCancelled()) {
|
||||||
|
mainApp->CreateShowDialog("Receive", "Transfer cancelled.", {"OK"}, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto restoreResult = io::restore(index, g_currentUId, 0, title.name());
|
||||||
|
if (std::get<0>(restoreResult)) {
|
||||||
|
mainApp->CreateShowDialog("Receive", "Save data received and restored successfully!", {"OK"}, true);
|
||||||
|
} else {
|
||||||
|
mainApp->CreateShowDialog("Receive", "Restore failed:\n" + std::get<2>(restoreResult), {"OK"}, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TitlesLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
|
void TitlesLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
|
||||||
if (m_inputLocked) return;
|
if (m_inputLocked) return;
|
||||||
|
|
||||||
if (Down & HidNpadButton_B) {
|
|
||||||
mainApp->LoadLayout(mainApp->usersLayout);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Down & HidNpadButton_Plus) {
|
if (Down & HidNpadButton_Plus) {
|
||||||
cancelClientTransfer();
|
cancelClientTransfer();
|
||||||
cancelServerTransfer();
|
cancelServerTransfer();
|
||||||
@@ -50,87 +263,50 @@ namespace ui {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Down & HidNpadButton_A) {
|
if (focus == TitlesFocus::List) {
|
||||||
auto index = this->titlesMenu->GetSelectedIndex();
|
if (Down & HidNpadButton_B) {
|
||||||
Title title;
|
this->header->SetUser(std::nullopt, "");
|
||||||
getTitle(title, g_currentUId, index);
|
mainApp->LoadLayout(mainApp->usersLayout);
|
||||||
printf("userid is 0x%lX%lX\n", title.userId().uid[1], title.userId().uid[0]);
|
return;
|
||||||
// printf("current game index is %i\n", index);
|
}
|
||||||
int opt = mainApp->CreateShowDialog(title.name().c_str(), "What do you want?", { "Transfer", "Receive" }, false);
|
if (Down & HidNpadButton_A) {
|
||||||
printf("opt is %i\n", opt);
|
if (this->titlesMenu->GetItems().empty()) return;
|
||||||
switch (opt) {
|
this->lockedListIndex = this->titlesMenu->GetSelectedIndex();
|
||||||
case 0: {
|
this->focus = TitlesFocus::Actions;
|
||||||
// Transfer selected
|
this->action = TitlesAction::Transfer;
|
||||||
{
|
this->refreshButtons();
|
||||||
auto ovl = TransferOverlay::New("Transferring save data...");
|
this->updateHints();
|
||||||
this->titlesMenu->SetVisible(false);
|
return;
|
||||||
mainApp->StartOverlay(ovl);
|
}
|
||||||
this->LockInput();
|
} else {
|
||||||
if (transfer_files(index, g_currentUId) != 0) {
|
if (this->titlesMenu->GetSelectedIndex() != this->lockedListIndex) {
|
||||||
mainApp->EndOverlay();
|
this->titlesMenu->SetSelectedIndex(this->lockedListIndex);
|
||||||
this->titlesMenu->SetVisible(true);
|
}
|
||||||
this->UnlockInput();
|
if (Down & HidNpadButton_B) {
|
||||||
mainApp->CreateShowDialog("Transfer", "Failed to start transfer.", {"OK"}, true);
|
this->focus = TitlesFocus::List;
|
||||||
break;
|
this->refreshButtons();
|
||||||
}
|
this->updateHints();
|
||||||
while (!isClientTransferDone()) {
|
return;
|
||||||
ovl->SetStatus(getClientStatusText());
|
}
|
||||||
ovl->SetProgressVisible(isClientProgressKnown());
|
if (Down & (HidNpadButton_Up | HidNpadButton_Down |
|
||||||
ovl->SetProgress(getClientProgress());
|
HidNpadButton_StickLUp | HidNpadButton_StickLDown)) {
|
||||||
mainApp->CallForRender();
|
this->action = (action == TitlesAction::Transfer)
|
||||||
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
|
? TitlesAction::Receive : TitlesAction::Transfer;
|
||||||
cancelClientTransfer();
|
this->refreshButtons();
|
||||||
}
|
return;
|
||||||
svcSleepThread(16666666LL);
|
}
|
||||||
}
|
if (Down & HidNpadButton_A) {
|
||||||
mainApp->EndOverlay();
|
int idx = this->titlesMenu->GetSelectedIndex();
|
||||||
this->titlesMenu->SetVisible(true);
|
Title title;
|
||||||
this->UnlockInput();
|
getTitle(title, g_currentUId, idx);
|
||||||
}
|
TitlesAction chosen = action;
|
||||||
if (isClientConnectionFailed()) {
|
this->focus = TitlesFocus::List;
|
||||||
mainApp->CreateShowDialog("Transfer", getClientFailReason(), {"OK"}, true);
|
this->refreshButtons();
|
||||||
} else if (isClientTransferCancelled()) {
|
this->updateHints();
|
||||||
mainApp->CreateShowDialog("Transfer", "Transfer cancelled.", {"OK"}, true);
|
if (chosen == TitlesAction::Transfer) {
|
||||||
} else {
|
this->runTransfer(idx, title);
|
||||||
mainApp->CreateShowDialog("Transfer", "Save data sent successfully!", {"OK"}, true);
|
} else {
|
||||||
}
|
this->runReceive(idx, title);
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 1: {
|
|
||||||
// Receive selected
|
|
||||||
if (startSendingThread() != 0) {
|
|
||||||
mainApp->CreateShowDialog("Receive", "Failed to start receiver.\nCheck network connection.", {"OK"}, true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
auto ovl = TransferOverlay::New("Receiving save data...");
|
|
||||||
this->titlesMenu->SetVisible(false);
|
|
||||||
mainApp->StartOverlay(ovl);
|
|
||||||
this->LockInput();
|
|
||||||
while (!isServerTransferDone()) {
|
|
||||||
ovl->SetStatus(getServerStatusText());
|
|
||||||
ovl->SetProgress(getServerProgress());
|
|
||||||
mainApp->CallForRender();
|
|
||||||
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
|
|
||||||
cancelServerTransfer();
|
|
||||||
}
|
|
||||||
svcSleepThread(16666666LL);
|
|
||||||
}
|
|
||||||
mainApp->EndOverlay();
|
|
||||||
this->titlesMenu->SetVisible(true);
|
|
||||||
this->UnlockInput();
|
|
||||||
}
|
|
||||||
if (isServerTransferCancelled()) {
|
|
||||||
mainApp->CreateShowDialog("Receive", "Transfer cancelled.", {"OK"}, true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
auto restoreResult = io::restore(index, g_currentUId, 0, title.name());
|
|
||||||
if (std::get<0>(restoreResult)) {
|
|
||||||
mainApp->CreateShowDialog("Receive", "Save data received and restored successfully!", {"OK"}, true);
|
|
||||||
} else {
|
|
||||||
mainApp->CreateShowDialog("Receive", "Restore failed:\n" + std::get<2>(restoreResult), {"OK"}, true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-9
@@ -6,26 +6,42 @@ namespace ui {
|
|||||||
extern MainApplication *mainApp;
|
extern MainApplication *mainApp;
|
||||||
|
|
||||||
UsersLayout::UsersLayout() : Layout::Layout() {
|
UsersLayout::UsersLayout() : Layout::Layout() {
|
||||||
this->usersMenu = pu::ui::elm::Menu::New(0, 0, 1280, COLOR("#67000000"), COLOR("#170909FF"), 94, 7);
|
using namespace theme;
|
||||||
this->usersMenu->SetScrollbarColor(COLOR("#170909FF"));
|
|
||||||
|
this->usersMenu = pu::ui::elm::Menu::New(
|
||||||
|
0, layout::ContentTop + space::md,
|
||||||
|
layout::ScreenW,
|
||||||
|
color::BgBase, color::BgSurface2,
|
||||||
|
88, 6);
|
||||||
|
this->usersMenu->SetScrollbarColor(color::Primary);
|
||||||
|
this->usersMenu->SetItemsFocusColor(color::BgSurface2);
|
||||||
|
|
||||||
for (AccountUid const& uid : Account::ids()) {
|
for (AccountUid const& uid : Account::ids()) {
|
||||||
auto item = pu::ui::elm::MenuItem::New(Account::username(uid));
|
auto item = pu::ui::elm::MenuItem::New(Account::username(uid));
|
||||||
item->SetColor(COLOR("#FFFFFFFF"));
|
item->SetColor(color::TextPrimary);
|
||||||
this->usersMenu->AddItem(item);
|
this->usersMenu->AddItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->loadingBg = pu::ui::elm::Rectangle::New(0, 0, 1280, 720, pu::ui::Color(30, 30, 30, 220));
|
this->loadingBg = pu::ui::elm::Rectangle::New(
|
||||||
|
0, 0, layout::ScreenW, layout::ScreenH, color::Scrim);
|
||||||
this->loadingBg->SetVisible(false);
|
this->loadingBg->SetVisible(false);
|
||||||
|
|
||||||
this->loadingText = pu::ui::elm::TextBlock::New(480, 340, "Reading game list...");
|
this->loadingText = pu::ui::elm::TextBlock::New(
|
||||||
this->loadingText->SetColor(pu::ui::Color(255, 255, 255, 255));
|
layout::ScreenW / 2 - 120,
|
||||||
|
layout::ScreenH / 2 - 12,
|
||||||
|
"Loading saves...");
|
||||||
|
this->loadingText->SetFont(type::font(type::Body));
|
||||||
|
this->loadingText->SetColor(color::TextSecondary);
|
||||||
this->loadingText->SetVisible(false);
|
this->loadingText->SetVisible(false);
|
||||||
|
|
||||||
this->SetBackgroundColor(BACKGROUND_COLOR);
|
this->SetBackgroundColor(color::BgBase);
|
||||||
this->Add(this->usersMenu);
|
this->Add(this->usersMenu);
|
||||||
this->Add(this->loadingBg);
|
this->Add(this->loadingBg);
|
||||||
this->Add(this->loadingText);
|
this->Add(this->loadingText);
|
||||||
|
|
||||||
|
this->header = std::make_unique<HeaderBar>(this, "Select a user");
|
||||||
|
this->hints = std::make_unique<HintBar>(this);
|
||||||
|
this->hints->SetHints({{"A", "Select"}, {"+", "Quit"}});
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t UsersLayout::GetCurrentIndex() {
|
int32_t UsersLayout::GetCurrentIndex() {
|
||||||
@@ -34,8 +50,7 @@ namespace ui {
|
|||||||
|
|
||||||
void UsersLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
|
void UsersLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
|
||||||
if (Down & HidNpadButton_Plus) {
|
if (Down & HidNpadButton_Plus) {
|
||||||
mainApp->Close();
|
svcExitProcess();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Down & HidNpadButton_A) {
|
if (Down & HidNpadButton_A) {
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
|
|
||||||
#include "account.hpp"
|
#include "account.hpp"
|
||||||
#include <main.hpp>
|
#include <main.hpp>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
static std::map<AccountUid, User> mUsers;
|
static std::map<AccountUid, User> mUsers;
|
||||||
|
|
||||||
@@ -85,6 +87,41 @@ std::string Account::username(AccountUid id)
|
|||||||
return got->second.name;
|
return got->second.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Account::iconPath(AccountUid id)
|
||||||
|
{
|
||||||
|
char path[128];
|
||||||
|
snprintf(path, sizeof(path), "sdmc:/switch/NXST/cache/%016lX%016lX.jpg",
|
||||||
|
id.uid[0], id.uid[1]);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(path, &st) == 0 && st.st_size > 0) return std::string(path);
|
||||||
|
|
||||||
|
mkdir("sdmc:/switch", 0755);
|
||||||
|
mkdir("sdmc:/switch/NXST", 0755);
|
||||||
|
mkdir("sdmc:/switch/NXST/cache", 0755);
|
||||||
|
|
||||||
|
AccountProfile profile;
|
||||||
|
if (R_FAILED(accountGetProfile(&profile, id))) return "";
|
||||||
|
|
||||||
|
u32 imgSize = 0;
|
||||||
|
if (R_FAILED(accountProfileGetImageSize(&profile, &imgSize)) || imgSize == 0) {
|
||||||
|
accountProfileClose(&profile);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> buf(imgSize);
|
||||||
|
u32 outSize = 0;
|
||||||
|
Result r = accountProfileLoadImage(&profile, buf.data(), imgSize, &outSize);
|
||||||
|
accountProfileClose(&profile);
|
||||||
|
if (R_FAILED(r) || outSize == 0) return "";
|
||||||
|
|
||||||
|
FILE* f = fopen(path, "wb");
|
||||||
|
if (!f) return "";
|
||||||
|
fwrite(buf.data(), 1, outSize, f);
|
||||||
|
fclose(f);
|
||||||
|
return std::string(path);
|
||||||
|
}
|
||||||
|
|
||||||
AccountUid Account::selectAccount(void)
|
AccountUid Account::selectAccount(void)
|
||||||
{
|
{
|
||||||
LibAppletArgs args;
|
LibAppletArgs args;
|
||||||
|
|||||||
+57
-20
@@ -26,20 +26,36 @@ static TransferState g_server_state;
|
|||||||
static std::atomic<int> g_server_client_sock{-1};
|
static std::atomic<int> g_server_client_sock{-1};
|
||||||
static std::atomic<int> g_server_listen_sock{-1};
|
static std::atomic<int> g_server_listen_sock{-1};
|
||||||
static std::atomic<int> g_broadcast_sock{-1};
|
static std::atomic<int> g_broadcast_sock{-1};
|
||||||
|
static std::atomic<bool> g_accept_thread_active{false};
|
||||||
|
static std::atomic<bool> g_broadcast_thread_active{false};
|
||||||
|
static pthread_t g_broadcast_thread{};
|
||||||
|
|
||||||
bool isServerTransferDone() { return g_server_state.done.load(); }
|
bool isServerTransferDone() { return g_server_state.done.load(); }
|
||||||
bool isServerTransferCancelled() { return g_server_state.cancelled.load(); }
|
bool isServerTransferCancelled() { return g_server_state.cancelled.load(); }
|
||||||
|
bool isServerWorkersIdle() { return !g_accept_thread_active.load() && !g_broadcast_thread_active.load(); }
|
||||||
double getServerProgress() { return g_server_state.progress(); }
|
double getServerProgress() { return g_server_state.progress(); }
|
||||||
std::string getServerStatusText() { return g_server_state.getStatus(); }
|
std::string getServerStatusText() { return g_server_state.getStatus(); }
|
||||||
|
|
||||||
void cancelServerTransfer() {
|
void cancelServerTransfer() {
|
||||||
g_server_state.cancelled.store(true);
|
g_server_state.cancelled.store(true);
|
||||||
int sock = g_server_client_sock.load();
|
int sock = g_server_client_sock.exchange(-1);
|
||||||
if (sock >= 0) shutdown(sock, SHUT_RDWR);
|
if (sock >= 0) {
|
||||||
int lsock = g_server_listen_sock.load();
|
shutdown(sock, SHUT_RDWR);
|
||||||
if (lsock >= 0) shutdown(lsock, SHUT_RDWR);
|
close(sock);
|
||||||
int bsock = g_broadcast_sock.load();
|
}
|
||||||
if (bsock >= 0) shutdown(bsock, SHUT_RDWR);
|
int lsock = g_server_listen_sock.exchange(-1);
|
||||||
|
if (lsock >= 0) {
|
||||||
|
shutdown(lsock, SHUT_RDWR);
|
||||||
|
close(lsock);
|
||||||
|
}
|
||||||
|
int bsock = g_broadcast_sock.exchange(-1);
|
||||||
|
if (bsock >= 0) {
|
||||||
|
shutdown(bsock, SHUT_RDWR);
|
||||||
|
close(bsock);
|
||||||
|
}
|
||||||
|
if (g_broadcast_thread_active.load()) {
|
||||||
|
pthread_cancel(g_broadcast_thread);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __SWITCH__
|
#ifdef __SWITCH__
|
||||||
@@ -166,13 +182,17 @@ static void* handle_client(void* socket_desc) {
|
|||||||
receive_file(client_socket, filename_str, file_size);
|
receive_file(client_socket, filename_str, file_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
close(client_socket);
|
int owned_client = g_server_client_sock.exchange(-1);
|
||||||
|
if (owned_client == client_socket) {
|
||||||
|
close(client_socket);
|
||||||
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AcceptArgs { int server_fd; };
|
struct AcceptArgs { int server_fd; };
|
||||||
|
|
||||||
static void* accept_and_handle(void* arg) {
|
static void* accept_and_handle(void* arg) {
|
||||||
|
g_accept_thread_active.store(true);
|
||||||
int server_fd = static_cast<AcceptArgs*>(arg)->server_fd;
|
int server_fd = static_cast<AcceptArgs*>(arg)->server_fd;
|
||||||
delete static_cast<AcceptArgs*>(arg);
|
delete static_cast<AcceptArgs*>(arg);
|
||||||
|
|
||||||
@@ -180,8 +200,10 @@ static void* accept_and_handle(void* arg) {
|
|||||||
sockaddr_in client_addr{};
|
sockaddr_in client_addr{};
|
||||||
socklen_t client_len = sizeof(client_addr);
|
socklen_t client_len = sizeof(client_addr);
|
||||||
int client_socket = accept(server_fd, (sockaddr*)&client_addr, &client_len);
|
int client_socket = accept(server_fd, (sockaddr*)&client_addr, &client_len);
|
||||||
g_server_listen_sock.store(-1);
|
int owned_listen = g_server_listen_sock.exchange(-1);
|
||||||
close(server_fd);
|
if (owned_listen == server_fd) {
|
||||||
|
close(server_fd);
|
||||||
|
}
|
||||||
|
|
||||||
if (client_socket >= 0) {
|
if (client_socket >= 0) {
|
||||||
g_server_client_sock.store(client_socket);
|
g_server_client_sock.store(client_socket);
|
||||||
@@ -191,23 +213,27 @@ static void* accept_and_handle(void* arg) {
|
|||||||
} else {
|
} else {
|
||||||
close(client_socket);
|
close(client_socket);
|
||||||
}
|
}
|
||||||
g_server_client_sock.store(-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g_server_state.done.store(true);
|
g_server_state.done.store(true);
|
||||||
|
g_accept_thread_active.store(false);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* broadcast_listener(void* arg) {
|
static void* broadcast_listener(void* arg) {
|
||||||
Socket udp(socket(AF_INET, SOCK_DGRAM, 0));
|
g_broadcast_thread_active.store(true);
|
||||||
if (!udp.valid()) {
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr);
|
||||||
|
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, nullptr);
|
||||||
|
int udp = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (udp < 0) {
|
||||||
perror("broadcast_listener: socket");
|
perror("broadcast_listener: socket");
|
||||||
|
g_broadcast_thread_active.store(false);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_broadcast_sock.store(udp.fd);
|
g_broadcast_sock.store(udp);
|
||||||
|
|
||||||
struct timeval tv{0, 100000}; // 100ms poll so cancel is detected quickly
|
struct timeval tv{0, 20000}; // 20ms poll so cancel/exit wins race with socketExit
|
||||||
setsockopt(udp, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
setsockopt(udp, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||||
|
|
||||||
sockaddr_in addr{};
|
sockaddr_in addr{};
|
||||||
@@ -217,7 +243,9 @@ static void* broadcast_listener(void* arg) {
|
|||||||
|
|
||||||
if (bind(udp, (sockaddr*)&addr, sizeof(addr)) < 0) {
|
if (bind(udp, (sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||||
perror("broadcast_listener: bind");
|
perror("broadcast_listener: bind");
|
||||||
g_broadcast_sock.store(-1);
|
int owned = g_broadcast_sock.exchange(-1);
|
||||||
|
if (owned == udp) close(udp);
|
||||||
|
g_broadcast_thread_active.store(false);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +254,9 @@ static void* broadcast_listener(void* arg) {
|
|||||||
group.imr_interface.s_addr = htonl(INADDR_ANY);
|
group.imr_interface.s_addr = htonl(INADDR_ANY);
|
||||||
if (setsockopt(udp, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)) < 0) {
|
if (setsockopt(udp, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)) < 0) {
|
||||||
perror("broadcast_listener: setsockopt");
|
perror("broadcast_listener: setsockopt");
|
||||||
g_broadcast_sock.store(-1);
|
int owned = g_broadcast_sock.exchange(-1);
|
||||||
|
if (owned == udp) close(udp);
|
||||||
|
g_broadcast_thread_active.store(false);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,21 +280,28 @@ static void* broadcast_listener(void* arg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g_broadcast_sock.store(-1);
|
int owned = g_broadcast_sock.exchange(-1);
|
||||||
|
if (owned == udp) close(udp);
|
||||||
|
g_broadcast_thread_active.store(false);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
int startSendingThread() {
|
int startSendingThread() {
|
||||||
|
g_server_state.reset();
|
||||||
|
g_server_state.setStatus("Waiting for connection...");
|
||||||
|
|
||||||
pthread_t broadcast_thread;
|
pthread_t broadcast_thread;
|
||||||
if (pthread_create(&broadcast_thread, nullptr, broadcast_listener, nullptr) != 0) {
|
if (pthread_create(&broadcast_thread, nullptr, broadcast_listener, nullptr) != 0) {
|
||||||
perror("startSendingThread: broadcast thread");
|
perror("startSendingThread: broadcast thread");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
g_broadcast_thread = broadcast_thread;
|
||||||
pthread_detach(broadcast_thread);
|
pthread_detach(broadcast_thread);
|
||||||
|
|
||||||
Socket server(socket(AF_INET, SOCK_STREAM, 0));
|
Socket server(socket(AF_INET, SOCK_STREAM, 0));
|
||||||
if (!server.valid()) {
|
if (!server.valid()) {
|
||||||
perror("startSendingThread: socket");
|
perror("startSendingThread: socket");
|
||||||
|
cancelServerTransfer();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,20 +315,20 @@ int startSendingThread() {
|
|||||||
|
|
||||||
if (bind(server, (sockaddr*)&addr, sizeof(addr)) < 0) {
|
if (bind(server, (sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||||
perror("startSendingThread: bind");
|
perror("startSendingThread: bind");
|
||||||
|
cancelServerTransfer();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (listen(server, 3) < 0) {
|
if (listen(server, 3) < 0) {
|
||||||
perror("startSendingThread: listen");
|
perror("startSendingThread: listen");
|
||||||
|
cancelServerTransfer();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_server_state.reset();
|
|
||||||
g_server_state.setStatus("Waiting for connection...");
|
|
||||||
|
|
||||||
AcceptArgs* acc_args = new AcceptArgs{server.fd};
|
AcceptArgs* acc_args = new AcceptArgs{server.fd};
|
||||||
pthread_t accept_thread;
|
pthread_t accept_thread;
|
||||||
if (pthread_create(&accept_thread, nullptr, accept_and_handle, acc_args) != 0) {
|
if (pthread_create(&accept_thread, nullptr, accept_and_handle, acc_args) != 0) {
|
||||||
delete acc_args;
|
delete acc_args;
|
||||||
|
cancelServerTransfer();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
pthread_detach(accept_thread);
|
pthread_detach(accept_thread);
|
||||||
|
|||||||
@@ -126,6 +126,16 @@ std::string StringUtils::removeNotAscii(std::string str)
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string StringUtils::elide(const std::string& s, size_t maxChars)
|
||||||
|
{
|
||||||
|
if (s.size() <= maxChars || maxChars < 6) return s;
|
||||||
|
constexpr const char* dots = "...";
|
||||||
|
size_t budget = maxChars - 3;
|
||||||
|
size_t head = (budget + 1) / 2;
|
||||||
|
size_t tail = budget - head;
|
||||||
|
return s.substr(0, head) + dots + s.substr(s.size() - tail);
|
||||||
|
}
|
||||||
|
|
||||||
HidsysNotificationLedPattern blinkLedPattern(u8 times)
|
HidsysNotificationLedPattern blinkLedPattern(u8 times)
|
||||||
{
|
{
|
||||||
HidsysNotificationLedPattern pattern;
|
HidsysNotificationLedPattern pattern;
|
||||||
|
|||||||
Reference in New Issue
Block a user