chore: merge hoc monitor and remove unnessesary files
This commit is contained in:
2577
Source/Horizon-OC-Monitor/source/Utils.hpp
Normal file
2577
Source/Horizon-OC-Monitor/source/Utils.hpp
Normal file
File diff suppressed because it is too large
Load Diff
35
Source/Horizon-OC-Monitor/source/audsnoop.c
Normal file
35
Source/Horizon-OC-Monitor/source/audsnoop.c
Normal file
@@ -0,0 +1,35 @@
|
||||
#define NX_SERVICE_ASSUME_NON_DOMAIN
|
||||
#include <switch.h>
|
||||
#include <service_guard.h>
|
||||
|
||||
static Service g_audsnoopSrv;
|
||||
|
||||
NX_GENERATE_SERVICE_GUARD(audsnoop);
|
||||
|
||||
Result _audsnoopInitialize(void) {
|
||||
return smGetService(&g_audsnoopSrv, "auddev");
|
||||
}
|
||||
|
||||
void _audsnoopCleanup(void) {
|
||||
serviceClose(&g_audsnoopSrv);
|
||||
}
|
||||
|
||||
Service* audsnoopGetServiceSession(void) {
|
||||
return &g_audsnoopSrv;
|
||||
}
|
||||
|
||||
Result audsnoopEnableDspUsageMeasurement(void) {
|
||||
return serviceDispatch(&g_audsnoopSrv, 0);
|
||||
}
|
||||
|
||||
Result audsnoopDisableDspUsageMeasurement(void) {
|
||||
return serviceDispatch(&g_audsnoopSrv, 1);
|
||||
}
|
||||
|
||||
Result audsnoopGetDspUsage(u32 *usage) {
|
||||
u32 tmp = 0;
|
||||
Result rc = serviceDispatchOut(&g_audsnoopSrv, 6, tmp);
|
||||
if (R_SUCCEEDED(rc) && usage)
|
||||
*usage = tmp;
|
||||
return rc;
|
||||
}
|
||||
1193
Source/Horizon-OC-Monitor/source/main.cpp
Normal file
1193
Source/Horizon-OC-Monitor/source/main.cpp
Normal file
File diff suppressed because it is too large
Load Diff
220
Source/Horizon-OC-Monitor/source/modes/Battery.hpp
Normal file
220
Source/Horizon-OC-Monitor/source/modes/Battery.hpp
Normal file
@@ -0,0 +1,220 @@
|
||||
class OtherMenu;
|
||||
|
||||
class BatteryOverlay : public tsl::Gui {
|
||||
private:
|
||||
// Separated value buffers
|
||||
char actualCapacity_c[32] = "";
|
||||
char designedCapacity_c[32] = "";
|
||||
char batteryTemp_c[32] = "";
|
||||
char rawCharge_c[32] = "";
|
||||
char batteryAge_c[32] = "";
|
||||
char voltageAvg_c[64] = "";
|
||||
char currentFlow_c[64] = "";
|
||||
char powerFlow_c[64] = "";
|
||||
char remainingTime_c[16] = "";
|
||||
char inputCurrentLimit_c[32] = "";
|
||||
char vbusCurrentLimit_c[32] = "";
|
||||
char chargeVoltageLimit_c[32] = "";
|
||||
char chargeCurrentLimit_c[32] = "";
|
||||
char chargerType_c[32] = "";
|
||||
char chargerMaxVoltage_c[32] = "";
|
||||
char chargerMaxCurrent_c[32] = "";
|
||||
|
||||
bool skipOnce = true;
|
||||
bool runOnce = true;
|
||||
bool isChargerConnected = false;
|
||||
FullSettings settings;
|
||||
public:
|
||||
BatteryOverlay() {
|
||||
GetConfigSettings(&settings);
|
||||
disableJumpTo = true;
|
||||
mutexInit(&mutex_BatteryChecker);
|
||||
StartBatteryThread();
|
||||
//tsl::elm::g_disableMenuCacheOnReturn.store(true, std::memory_order_release);
|
||||
}
|
||||
~BatteryOverlay() {
|
||||
CloseBatteryThread();
|
||||
fixForeground = true;
|
||||
}
|
||||
|
||||
virtual tsl::elm::Element* createUI() override {
|
||||
|
||||
auto* Status = new tsl::elm::CustomDrawer([this](tsl::gfx::Renderer *renderer, u16 x, u16 y, u16 w, u16 h) {
|
||||
static constexpr u16 Y_OFFSET = 40;
|
||||
static constexpr u16 X_OFFSET = 20;
|
||||
static const u16 LABEL_X = 20 + X_OFFSET;
|
||||
static const u16 VALUE_X = 240+ X_OFFSET;
|
||||
static const u16 START_Y = 155 + Y_OFFSET;
|
||||
static constexpr u16 LINE_HEIGHT = 18;
|
||||
static constexpr u8 FONT_SIZE = 18;
|
||||
static const tsl::Color LABEL_COLOR_1= settings.catColor1;
|
||||
static const tsl::Color LABEL_COLOR_2 = settings.catColor2;
|
||||
static const tsl::Color VALUE_COLOR = settings.textColor;
|
||||
|
||||
renderer->drawString("Battery Stats", false, LABEL_X, 120 + Y_OFFSET, 20, LABEL_COLOR_1);
|
||||
|
||||
u16 currentY = START_Y;
|
||||
|
||||
// Actual Capacity
|
||||
renderer->drawString("Actual Capacity", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(actualCapacity_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// Designed Capacity
|
||||
renderer->drawString("Designed Capacity", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(designedCapacity_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// Temperature
|
||||
renderer->drawString("Temperature", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(batteryTemp_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// Raw Charge
|
||||
renderer->drawString("Raw Charge", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(rawCharge_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// Age
|
||||
renderer->drawString("Age", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(batteryAge_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// Voltage
|
||||
renderer->drawString("Voltage", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(voltageAvg_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// Current Flow
|
||||
renderer->drawString("Current Flow", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(currentFlow_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// Power Flow
|
||||
renderer->drawString("Power Flow", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(powerFlow_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// Remaining Time
|
||||
renderer->drawString("Remaining Time", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(remainingTime_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// Charger-specific fields (only shown when charger is connected)
|
||||
if (isChargerConnected) {
|
||||
currentY += 3*LINE_HEIGHT;
|
||||
renderer->drawString("Charger Stats", false, LABEL_X, currentY, 20, LABEL_COLOR_1);
|
||||
currentY += 2*LINE_HEIGHT;
|
||||
// Input Current Limit
|
||||
renderer->drawString("Input Current Limit", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(inputCurrentLimit_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// VBUS Current Limit
|
||||
renderer->drawString("VBUS Current Limit", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(vbusCurrentLimit_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// Charge Voltage Limit
|
||||
renderer->drawString("Voltage Limit", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(chargeVoltageLimit_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// Charge Current Limit
|
||||
renderer->drawString("Current Limit", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(chargeCurrentLimit_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// Charger Type
|
||||
renderer->drawString("Type", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(chargerType_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// Charger Max Voltage
|
||||
renderer->drawString("Max Voltage", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(chargerMaxVoltage_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
currentY += LINE_HEIGHT;
|
||||
|
||||
// Charger Max Current
|
||||
renderer->drawString("Max Current", false, LABEL_X, currentY, FONT_SIZE, LABEL_COLOR_2);
|
||||
renderer->drawString(chargerMaxCurrent_c, false, VALUE_X, currentY, FONT_SIZE, VALUE_COLOR);
|
||||
}
|
||||
});
|
||||
|
||||
//tsl::elm::g_disableMenuCacheOnReturn.store(true, std::memory_order_release);
|
||||
tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("Status Monitor", APP_VERSION, true);
|
||||
rootFrame->setContent(Status);
|
||||
|
||||
return rootFrame;
|
||||
}
|
||||
|
||||
virtual void update() override {
|
||||
mutexLock(&mutex_BatteryChecker);
|
||||
|
||||
char tempBatTimeEstimate[8] = "--:--";
|
||||
if (batTimeEstimate >= 0) {
|
||||
snprintf(tempBatTimeEstimate, sizeof(tempBatTimeEstimate), "%d:%02d", batTimeEstimate / 60, batTimeEstimate % 60);
|
||||
}
|
||||
|
||||
const BatteryChargeInfoFieldsChargerType ChargerConnected = hosversionAtLeast(17,0,0) ?
|
||||
((BatteryChargeInfoFields17*)&_batteryChargeInfoFields)->ChargerType :
|
||||
_batteryChargeInfoFields.ChargerType;
|
||||
const int32_t ChargerVoltageLimit = hosversionAtLeast(17,0,0) ?
|
||||
((BatteryChargeInfoFields17*)&_batteryChargeInfoFields)->ChargerVoltageLimit :
|
||||
_batteryChargeInfoFields.ChargerVoltageLimit;
|
||||
const int32_t ChargerCurrentLimit = hosversionAtLeast(17,0,0) ?
|
||||
((BatteryChargeInfoFields17*)&_batteryChargeInfoFields)->ChargerCurrentLimit :
|
||||
_batteryChargeInfoFields.ChargerCurrentLimit;
|
||||
|
||||
isChargerConnected = (ChargerConnected != 0);
|
||||
|
||||
// Format all values
|
||||
snprintf(actualCapacity_c, sizeof(actualCapacity_c), "%.0f mAh", actualFullBatCapacity);
|
||||
snprintf(designedCapacity_c, sizeof(designedCapacity_c), "%.0f mAh", designedFullBatCapacity);
|
||||
snprintf(batteryTemp_c, sizeof(batteryTemp_c), "%.1f\u00B0C", (float)_batteryChargeInfoFields.BatteryTemperature / 1000);
|
||||
snprintf(rawCharge_c, sizeof(rawCharge_c), "%.1f%%", (float)_batteryChargeInfoFields.RawBatteryCharge / 1000);
|
||||
snprintf(batteryAge_c, sizeof(batteryAge_c), "%.1f%%", (float)_batteryChargeInfoFields.BatteryAge / 1000);
|
||||
snprintf(voltageAvg_c, sizeof(voltageAvg_c), "%.0f mV (%ds)", batVoltageAvg, batteryFiltered ? 45 : 5);
|
||||
snprintf(currentFlow_c, sizeof(currentFlow_c), "%+.0f mA (%ss)", batCurrentAvg, batteryFiltered ? "11.25" : "5");
|
||||
snprintf(powerFlow_c, sizeof(powerFlow_c), "%+.3f W%s", PowerConsumption, batteryFiltered ? "" : " (5s)");
|
||||
snprintf(remainingTime_c, sizeof(remainingTime_c), "%s", tempBatTimeEstimate);
|
||||
|
||||
if (isChargerConnected) {
|
||||
snprintf(inputCurrentLimit_c, sizeof(inputCurrentLimit_c), "%d mA", _batteryChargeInfoFields.InputCurrentLimit);
|
||||
snprintf(vbusCurrentLimit_c, sizeof(vbusCurrentLimit_c), "%d mA", _batteryChargeInfoFields.VBUSCurrentLimit);
|
||||
snprintf(chargeVoltageLimit_c, sizeof(chargeVoltageLimit_c), "%d mV", _batteryChargeInfoFields.ChargeVoltageLimit);
|
||||
snprintf(chargeCurrentLimit_c, sizeof(chargeCurrentLimit_c), "%d mA", _batteryChargeInfoFields.ChargeCurrentLimit);
|
||||
snprintf(chargerType_c, sizeof(chargerType_c), "%u", ChargerConnected);
|
||||
snprintf(chargerMaxVoltage_c, sizeof(chargerMaxVoltage_c), "%u mV", ChargerVoltageLimit);
|
||||
snprintf(chargerMaxCurrent_c, sizeof(chargerMaxCurrent_c), "%u mA", ChargerCurrentLimit);
|
||||
}
|
||||
|
||||
mutexUnlock(&mutex_BatteryChecker);
|
||||
|
||||
if (!skipOnce) {
|
||||
if (runOnce) {
|
||||
isRendering = true;
|
||||
leventClear(&renderingStopEvent);
|
||||
runOnce = false;
|
||||
}
|
||||
} else {
|
||||
skipOnce = false;
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool handleInput(uint64_t keysDown, uint64_t keysHeld, touchPosition touchInput, JoystickPosition leftJoyStick, JoystickPosition rightJoyStick) override {
|
||||
if (keysDown & KEY_B) {
|
||||
isRendering = false;
|
||||
leventSignal(&renderingStopEvent);
|
||||
triggerRumbleDoubleClick.store(true, std::memory_order_release);
|
||||
triggerExitSound.store(true, std::memory_order_release);
|
||||
skipOnce = true;
|
||||
runOnce = true;
|
||||
lastSelectedItem = "Battery/Charger";
|
||||
lastMode = "";
|
||||
tsl::swapTo<OtherMenu>();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
2215
Source/Horizon-OC-Monitor/source/modes/Configurator.hpp
Normal file
2215
Source/Horizon-OC-Monitor/source/modes/Configurator.hpp
Normal file
File diff suppressed because it is too large
Load Diff
525
Source/Horizon-OC-Monitor/source/modes/FPS_Counter.hpp
Normal file
525
Source/Horizon-OC-Monitor/source/modes/FPS_Counter.hpp
Normal file
@@ -0,0 +1,525 @@
|
||||
class MainMenu;
|
||||
|
||||
class com_FPS : public tsl::Gui {
|
||||
private:
|
||||
char FPSavg_c[8];
|
||||
FpsCounterSettings settings;
|
||||
size_t fontsize = 0;
|
||||
ApmPerformanceMode performanceMode = ApmPerformanceMode_Invalid;
|
||||
bool skipOnce = true;
|
||||
bool runOnce = true;
|
||||
|
||||
// Repositioning variables
|
||||
int frameOffsetX = 0;
|
||||
int frameOffsetY = 0;
|
||||
bool isDragging = false;
|
||||
size_t framePadding = 10;
|
||||
static constexpr int screenWidth = 1280;
|
||||
static constexpr int screenHeight = 720;
|
||||
static constexpr int border = 8;
|
||||
|
||||
bool originalUseRightAlignment = ult::useRightAlignment;
|
||||
|
||||
struct ButtonState {
|
||||
std::atomic<bool> minusDragActive{false};
|
||||
std::atomic<bool> plusDragActive{false};
|
||||
} buttonState;
|
||||
|
||||
Thread touchPollThread;
|
||||
std::atomic<bool> touchPollRunning{false};
|
||||
|
||||
// Store actual rendered dimensions
|
||||
size_t actualTextWidth = 0;
|
||||
size_t actualTotalWidth = 0;
|
||||
size_t actualTotalHeight = 0;
|
||||
|
||||
public:
|
||||
com_FPS() {
|
||||
tsl::hlp::requestForeground(false);
|
||||
disableJumpTo = true;
|
||||
GetConfigSettings(&settings);
|
||||
apmGetPerformanceMode(&performanceMode);
|
||||
if (performanceMode == ApmPerformanceMode_Normal) {
|
||||
fontsize = settings.handheldFontSize;
|
||||
}
|
||||
else if (performanceMode == ApmPerformanceMode_Boost) {
|
||||
fontsize = settings.dockedFontSize;
|
||||
}
|
||||
|
||||
// Load saved frame offsets
|
||||
frameOffsetX = settings.frameOffsetX;
|
||||
frameOffsetY = settings.frameOffsetY;
|
||||
framePadding = settings.framePadding;
|
||||
|
||||
if (ult::limitedMemory) {
|
||||
tsl::gfx::Renderer::get().setLayerPos(std::max(std::min((int)(frameOffsetX*1.5 + 0.5) - tsl::impl::currentUnderscanPixels.first, 1280-32 - tsl::impl::currentUnderscanPixels.first), 0), 0);
|
||||
}
|
||||
|
||||
FullMode = false;
|
||||
TeslaFPS = settings.refreshRate;
|
||||
if (settings.disableScreenshots) {
|
||||
tsl::gfx::Renderer::get().removeScreenshotStacks();
|
||||
}
|
||||
deactivateOriginalFooter = true;
|
||||
StartFPSCounterThread();
|
||||
|
||||
// Start touch polling thread for instant response at low FPS
|
||||
touchPollRunning.store(true, std::memory_order_release);
|
||||
threadCreate(&touchPollThread, [](void* arg) -> void {
|
||||
com_FPS* overlay = static_cast<com_FPS*>(arg);
|
||||
|
||||
// Allow only Player 1 and handheld mode
|
||||
const HidNpadIdType id_list[2] = { HidNpadIdType_No1, HidNpadIdType_Handheld };
|
||||
|
||||
// Configure HID system to only listen to these IDs
|
||||
hidSetSupportedNpadIdType(id_list, 2);
|
||||
|
||||
// Configure input for up to 2 supported controllers (P1 + Handheld)
|
||||
padConfigureInput(2, HidNpadStyleSet_NpadStandard | HidNpadStyleTag_NpadSystemExt);
|
||||
|
||||
// Initialize separate pad states for both controllers
|
||||
PadState pad_p1;
|
||||
PadState pad_handheld;
|
||||
padInitialize(&pad_p1, HidNpadIdType_No1);
|
||||
padInitialize(&pad_handheld, HidNpadIdType_Handheld);
|
||||
|
||||
u64 minusHoldStart = 0;
|
||||
u64 plusHoldStart = 0;
|
||||
static constexpr u64 HOLD_THRESHOLD_NS = 500'000'000ULL;
|
||||
|
||||
HidTouchScreenState state = {0};
|
||||
bool inputDetected;
|
||||
|
||||
while (overlay->touchPollRunning.load(std::memory_order_acquire)) {
|
||||
// Only poll when rendering and not dragging
|
||||
{
|
||||
inputDetected = false;
|
||||
|
||||
// Check touch in bounds
|
||||
if (hidGetTouchScreenStates(&state, 1) && state.count > 0) {
|
||||
const int touchX = state.touches[0].x;
|
||||
const int touchY = state.touches[0].y;
|
||||
|
||||
// Use actual dimensions, fallback to estimate if not yet rendered
|
||||
size_t totalWidth = overlay->actualTotalWidth;
|
||||
size_t totalHeight = overlay->actualTotalHeight;
|
||||
|
||||
if (totalWidth == 0) {
|
||||
// Fallback calculation
|
||||
size_t approxFontSize = overlay->fontsize;
|
||||
if (approxFontSize == 0) approxFontSize = 50;
|
||||
const size_t textWidth = approxFontSize * 4;
|
||||
const size_t margin = (approxFontSize / 8);
|
||||
const size_t innerWidth = textWidth + margin;
|
||||
const size_t innerHeight = approxFontSize;
|
||||
totalWidth = innerWidth + (2 * border);
|
||||
totalHeight = innerHeight + (2 * border);
|
||||
}
|
||||
|
||||
// Apply frame offsets
|
||||
const int overlayX = overlay->frameOffsetX;
|
||||
const int overlayY = overlay->frameOffsetY;
|
||||
|
||||
// Touch padding
|
||||
const int touchPadding = 4;
|
||||
const int touchableX = overlayX - touchPadding;
|
||||
const int touchableY = overlayY - touchPadding;
|
||||
const int touchableWidth = totalWidth + (touchPadding * 2);
|
||||
const int touchableHeight = totalHeight + (touchPadding * 2);
|
||||
|
||||
// Check if touch is within bounds
|
||||
if (touchX >= touchableX && touchX <= touchableX + touchableWidth &&
|
||||
touchY >= touchableY && touchY <= touchableY + touchableHeight) {
|
||||
inputDetected = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Poll buttons from both controllers
|
||||
padUpdate(&pad_p1);
|
||||
padUpdate(&pad_handheld);
|
||||
|
||||
// Combine input from both controllers
|
||||
const u64 keysHeld = padGetButtons(&pad_p1) | padGetButtons(&pad_handheld);
|
||||
const u64 now = armTicksToNs(armGetSystemTick());
|
||||
|
||||
// Track MINUS hold duration
|
||||
if ((keysHeld & KEY_MINUS) && !(keysHeld & ~KEY_MINUS & ALL_KEYS_MASK)) {
|
||||
if (minusHoldStart == 0) {
|
||||
minusHoldStart = now;
|
||||
}
|
||||
if (now - minusHoldStart >= HOLD_THRESHOLD_NS) {
|
||||
inputDetected = true;
|
||||
overlay->buttonState.minusDragActive.exchange(true, std::memory_order_acq_rel);
|
||||
}
|
||||
}
|
||||
|
||||
// Track PLUS hold duration
|
||||
else if ((keysHeld & KEY_PLUS) && !(keysHeld & ~KEY_PLUS & ALL_KEYS_MASK)) {
|
||||
if (plusHoldStart == 0) {
|
||||
plusHoldStart = now;
|
||||
}
|
||||
if (now - plusHoldStart >= HOLD_THRESHOLD_NS) {
|
||||
inputDetected = true;
|
||||
overlay->buttonState.plusDragActive.exchange(true, std::memory_order_acq_rel);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
minusHoldStart = plusHoldStart = 0;
|
||||
overlay->buttonState.minusDragActive.exchange(false, std::memory_order_acq_rel);
|
||||
overlay->buttonState.plusDragActive.exchange(false, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
// Disable rendering on any input, re-enable when no input
|
||||
static bool resetOnce = true;
|
||||
if (inputDetected) {
|
||||
if (resetOnce && isRendering) {
|
||||
isRendering = false;
|
||||
leventSignal(&renderingStopEvent);
|
||||
resetOnce = false;
|
||||
}
|
||||
} else {
|
||||
resetOnce = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ult::limitedMemory) {
|
||||
static auto lastUnderscanPixels = std::make_pair(0, 0);
|
||||
|
||||
if (lastUnderscanPixels != tsl::impl::currentUnderscanPixels) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
tsl::gfx::Renderer::get().updateLayerSize();
|
||||
tsl::gfx::Renderer::get().setLayerPos(std::max(std::min((int)(overlay->frameOffsetX*1.5 + 0.5) - tsl::impl::currentUnderscanPixels.first, 1280-32 - tsl::impl::currentUnderscanPixels.first), 0), 0);
|
||||
}
|
||||
}
|
||||
lastUnderscanPixels = tsl::impl::currentUnderscanPixels;
|
||||
}
|
||||
|
||||
svcSleepThread(16000000ULL*2); // 16ms polling
|
||||
}
|
||||
}, this, NULL, 0x1000, 0x2B, -2);
|
||||
threadStart(&touchPollThread);
|
||||
}
|
||||
|
||||
~com_FPS() {
|
||||
// Stop touch polling thread
|
||||
touchPollRunning.store(false, std::memory_order_release);
|
||||
threadWaitForExit(&touchPollThread);
|
||||
threadClose(&touchPollThread);
|
||||
|
||||
TeslaFPS = 60;
|
||||
EndFPSCounterThread();
|
||||
FullMode = true;
|
||||
fixForeground = true;
|
||||
ult::useRightAlignment = originalUseRightAlignment;
|
||||
if (settings.disableScreenshots) {
|
||||
tsl::gfx::Renderer::get().addScreenshotStacks();
|
||||
}
|
||||
deactivateOriginalFooter = false;
|
||||
}
|
||||
|
||||
virtual tsl::elm::Element* createUI() override {
|
||||
|
||||
auto* Status = new tsl::elm::CustomDrawer([this](tsl::gfx::Renderer *renderer, u16 x, u16 y, u16 w, u16 h) {
|
||||
// Calculate text dimensions
|
||||
const auto [textWidth, textHeight] = renderer->getTextDimensions(
|
||||
(FPSavg != 254.0) ? FPSavg_c : "--", false, fontsize
|
||||
);
|
||||
|
||||
const size_t margin = (fontsize / 8);
|
||||
|
||||
// Inner rectangle dimensions (content area)
|
||||
const size_t innerWidth = textWidth + margin;
|
||||
const size_t innerHeight = textHeight;
|
||||
|
||||
// Total dimensions including border
|
||||
const size_t totalWidth = innerWidth + (2 * border);
|
||||
const size_t totalHeight = innerHeight + (2 * border);
|
||||
|
||||
// Store actual dimensions for input handling
|
||||
actualTextWidth = textWidth;
|
||||
actualTotalWidth = totalWidth;
|
||||
actualTotalHeight = totalHeight;
|
||||
|
||||
// Calculate position with frame offsets
|
||||
//int posX = frameOffsetX;
|
||||
//int posY = frameOffsetY;
|
||||
|
||||
int _frameOffsetX = ult::limitedMemory ? std::max(0, frameOffsetX - (1280-448)) : frameOffsetX;
|
||||
|
||||
// Clamp to screen bounds (accounting for total size including border)
|
||||
const int posX = std::max(int(framePadding), std::min(_frameOffsetX, static_cast<int>(screenWidth - totalWidth - framePadding)));
|
||||
const int posY = std::max(int(framePadding), std::min(frameOffsetY, static_cast<int>(screenHeight - totalHeight - framePadding)));
|
||||
|
||||
// Draw the rounded rectangle (background)
|
||||
const tsl::Color bgColor = !isDragging
|
||||
? settings.backgroundColor
|
||||
: settings.focusBackgroundColor;
|
||||
|
||||
renderer->drawRoundedRectSingleThreaded(
|
||||
posX,
|
||||
posY,
|
||||
totalWidth,
|
||||
totalHeight,
|
||||
16,
|
||||
aWithOpacity(bgColor)
|
||||
);
|
||||
|
||||
// Calculate centered text position within the bordered area
|
||||
const int textX = posX + border + (margin / 2);
|
||||
const int textY = posY + border + (fontsize - margin);
|
||||
|
||||
// Draw the text
|
||||
renderer->drawString(
|
||||
(FPSavg != 254.0) ? FPSavg_c : "--",
|
||||
false,
|
||||
textX,
|
||||
textY,
|
||||
fontsize,
|
||||
settings.textColor
|
||||
);
|
||||
});
|
||||
|
||||
tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("", "");
|
||||
rootFrame->setContent(Status);
|
||||
|
||||
return rootFrame;
|
||||
}
|
||||
|
||||
virtual void update() override {
|
||||
apmGetPerformanceMode(&performanceMode);
|
||||
if (performanceMode == ApmPerformanceMode_Normal) {
|
||||
fontsize = settings.handheldFontSize;
|
||||
}
|
||||
else if (performanceMode == ApmPerformanceMode_Boost) {
|
||||
fontsize = settings.dockedFontSize;
|
||||
}
|
||||
if (settings.useIntegerCounter) {
|
||||
snprintf(FPSavg_c, sizeof FPSavg_c, "%d", (int)round(useOldFPSavg ? FPSavg_old : FPSavg));
|
||||
} else {
|
||||
snprintf(FPSavg_c, sizeof FPSavg_c, "%2.1f", useOldFPSavg ? FPSavg_old : FPSavg);
|
||||
}
|
||||
|
||||
if (!skipOnce) {
|
||||
if (runOnce) {
|
||||
isRendering = true;
|
||||
leventClear(&renderingStopEvent);
|
||||
runOnce = false;
|
||||
}
|
||||
} else {
|
||||
skipOnce = false;
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override {
|
||||
// Static variables to maintain drag state between function calls
|
||||
static bool oldTouchDetected = false;
|
||||
static bool oldMinusHeld = false;
|
||||
static bool oldPlusHeld = false;
|
||||
static HidTouchState initialTouchPos = {0};
|
||||
static int initialFrameOffsetX = 0;
|
||||
static int initialFrameOffsetY = 0;
|
||||
static constexpr int TOUCH_THRESHOLD = 8;
|
||||
static bool hasMoved = false;
|
||||
|
||||
// Touch detection
|
||||
const bool currentTouchDetected = (touchPos.x > 0 && touchPos.y > 0 &&
|
||||
touchPos.x < screenWidth && touchPos.y < screenHeight);
|
||||
|
||||
static bool clearOnRelease = false;
|
||||
|
||||
if (clearOnRelease && !isRendering) {
|
||||
clearOnRelease = false;
|
||||
isRendering = true;
|
||||
leventClear(&renderingStopEvent);
|
||||
}
|
||||
|
||||
// Use actual dimensions from last render, fallback to estimate if not available
|
||||
size_t totalWidth = actualTotalWidth;
|
||||
size_t totalHeight = actualTotalHeight;
|
||||
|
||||
if (totalWidth == 0) {
|
||||
// Fallback calculation if not yet rendered
|
||||
const size_t textWidth = fontsize * 4;
|
||||
const size_t margin = (fontsize / 8);
|
||||
const size_t innerWidth = textWidth + margin;
|
||||
const size_t innerHeight = fontsize + (margin / 2);
|
||||
totalWidth = innerWidth + (2 * border);
|
||||
totalHeight = innerHeight + (2 * border);
|
||||
}
|
||||
|
||||
// Current overlay position
|
||||
const int overlayX = frameOffsetX;
|
||||
const int overlayY = frameOffsetY;
|
||||
|
||||
// Touch detection area (with padding for easier interaction)
|
||||
static constexpr int touchPadding = 4;
|
||||
const int touchableX = overlayX - touchPadding;
|
||||
const int touchableY = overlayY - touchPadding;
|
||||
const int touchableWidth = totalWidth + (touchPadding * 2);
|
||||
const int touchableHeight = totalHeight + (touchPadding * 2);
|
||||
|
||||
// Screen boundaries for clamping (accounting for total size)
|
||||
const int minX = framePadding;
|
||||
const int maxX = screenWidth - totalWidth - framePadding;
|
||||
const int minY = framePadding;
|
||||
const int maxY = screenHeight - totalHeight - framePadding;
|
||||
|
||||
const bool minusDragReady = buttonState.minusDragActive.load(std::memory_order_acquire);
|
||||
const bool plusDragReady = buttonState.plusDragActive.load(std::memory_order_acquire);
|
||||
|
||||
// Check button states
|
||||
const bool currentMinusHeld = (keysHeld & KEY_MINUS) && !(keysHeld & ~KEY_MINUS & ALL_KEYS_MASK) && minusDragReady;
|
||||
const bool currentPlusHeld = (keysHeld & KEY_PLUS) && !(keysHeld & ~KEY_PLUS & ALL_KEYS_MASK) && plusDragReady;
|
||||
|
||||
// Handle touch dragging
|
||||
if (currentTouchDetected && !isDragging) {
|
||||
const int touchX = touchPos.x;
|
||||
const int touchY = touchPos.y;
|
||||
|
||||
if (!oldTouchDetected) {
|
||||
// Touch just started - check if within overlay bounds
|
||||
if (touchX >= touchableX && touchX <= touchableX + touchableWidth &&
|
||||
touchY >= touchableY && touchY <= touchableY + touchableHeight) {
|
||||
|
||||
// Start touch dragging
|
||||
isDragging = true;
|
||||
triggerRumbleClick.store(true, std::memory_order_release);
|
||||
triggerOnSound.store(true, std::memory_order_release);
|
||||
hasMoved = false;
|
||||
initialTouchPos = touchPos;
|
||||
initialFrameOffsetX = frameOffsetX;
|
||||
initialFrameOffsetY = frameOffsetY;
|
||||
}
|
||||
}
|
||||
} else if (currentTouchDetected && isDragging && !currentMinusHeld && !currentPlusHeld) {
|
||||
// Continue touch dragging
|
||||
const int touchX = touchPos.x;
|
||||
const int touchY = touchPos.y;
|
||||
const int deltaX = touchX - initialTouchPos.x;
|
||||
const int deltaY = touchY - initialTouchPos.y;
|
||||
|
||||
// Check if we've moved enough to consider this a drag
|
||||
if (!hasMoved) {
|
||||
const int totalMovement = abs(deltaX) + abs(deltaY);
|
||||
if (totalMovement >= TOUCH_THRESHOLD) {
|
||||
hasMoved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasMoved) {
|
||||
// Update frame offsets with boundary checking
|
||||
frameOffsetX = std::max(minX, std::min(maxX, initialFrameOffsetX + deltaX));
|
||||
frameOffsetY = std::max(minY, std::min(maxY, initialFrameOffsetY + deltaY));
|
||||
|
||||
if (ult::limitedMemory) {
|
||||
tsl::gfx::Renderer::get().setLayerPos(std::max(std::min((int)(frameOffsetX*1.5 + 0.5) - tsl::impl::currentUnderscanPixels.first, 1280-32 - tsl::impl::currentUnderscanPixels.first), 0), 0);
|
||||
}
|
||||
}
|
||||
} else if (!currentTouchDetected && oldTouchDetected && isDragging && !currentMinusHeld && !currentPlusHeld) {
|
||||
// Touch just released
|
||||
if (hasMoved) {
|
||||
// Save position when touch drag ends
|
||||
auto iniData = ult::getParsedDataFromIniFile(configIniPath);
|
||||
iniData["fps-counter"]["frame_offset_x"] = std::to_string(frameOffsetX);
|
||||
iniData["fps-counter"]["frame_offset_y"] = std::to_string(frameOffsetY);
|
||||
ult::saveIniFileData(configIniPath, iniData);
|
||||
}
|
||||
|
||||
// Reset touch drag state
|
||||
isDragging = false;
|
||||
hasMoved = false;
|
||||
clearOnRelease = true;
|
||||
triggerRumbleDoubleClick.store(true, std::memory_order_release);
|
||||
triggerOffSound.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
// Handle joystick dragging (MINUS + right joystick OR PLUS + left joystick)
|
||||
if ((currentMinusHeld || currentPlusHeld) && !isDragging) {
|
||||
// Start joystick dragging
|
||||
isDragging = true;
|
||||
triggerRumbleClick.store(true, std::memory_order_release);
|
||||
triggerOnSound.store(true, std::memory_order_release);
|
||||
} else if ((currentMinusHeld || currentPlusHeld) && isDragging) {
|
||||
// Continue joystick dragging
|
||||
static constexpr int JOYSTICK_DEADZONE = 20;
|
||||
|
||||
// Choose the appropriate joystick based on which button is held
|
||||
const HidAnalogStickState& activeJoystick = currentMinusHeld ? joyStickPosRight : joyStickPosLeft;
|
||||
|
||||
// Only move if joystick is outside deadzone
|
||||
if (abs(activeJoystick.x) > JOYSTICK_DEADZONE || abs(activeJoystick.y) > JOYSTICK_DEADZONE) {
|
||||
// Calculate joystick magnitude
|
||||
const float magnitude = sqrt((float)(activeJoystick.x * activeJoystick.x + activeJoystick.y * activeJoystick.y));
|
||||
const float normalizedMagnitude = magnitude / 32767.0f;
|
||||
|
||||
// Smooth curve for sensitivity
|
||||
static constexpr float baseSensitivity = 0.00008f;
|
||||
static constexpr float maxSensitivity = 0.0005f;
|
||||
|
||||
const float curveValue = pow(normalizedMagnitude, 8.0f);
|
||||
const float currentSensitivity = baseSensitivity + (maxSensitivity - baseSensitivity) * curveValue;
|
||||
|
||||
// Calculate movement delta with fractional accumulation
|
||||
static float accumulatedX = 0.0f;
|
||||
static float accumulatedY = 0.0f;
|
||||
|
||||
accumulatedX += (float)activeJoystick.x * currentSensitivity;
|
||||
accumulatedY += -(float)activeJoystick.y * currentSensitivity;
|
||||
|
||||
const int deltaX = (int)accumulatedX;
|
||||
const int deltaY = (int)accumulatedY;
|
||||
accumulatedX -= deltaX;
|
||||
accumulatedY -= deltaY;
|
||||
|
||||
// Update frame offsets with boundary checking
|
||||
frameOffsetX = std::max(minX, std::min(maxX, frameOffsetX + deltaX));
|
||||
frameOffsetY = std::max(minY, std::min(maxY, frameOffsetY + deltaY));
|
||||
|
||||
if (ult::limitedMemory) {
|
||||
tsl::gfx::Renderer::get().setLayerPos(std::max(std::min((int)(frameOffsetX*1.5 + 0.5) - tsl::impl::currentUnderscanPixels.first, 1280-32 - tsl::impl::currentUnderscanPixels.first), 0), 0);
|
||||
}
|
||||
}
|
||||
} else if (((!currentMinusHeld && oldMinusHeld) || (!currentPlusHeld && oldPlusHeld)) && isDragging) {
|
||||
// Button just released - stop joystick dragging
|
||||
auto iniData = ult::getParsedDataFromIniFile(configIniPath);
|
||||
iniData["fps-counter"]["frame_offset_x"] = std::to_string(frameOffsetX);
|
||||
iniData["fps-counter"]["frame_offset_y"] = std::to_string(frameOffsetY);
|
||||
ult::saveIniFileData(configIniPath, iniData);
|
||||
isDragging = false;
|
||||
clearOnRelease = true;
|
||||
triggerRumbleDoubleClick.store(true, std::memory_order_release);
|
||||
triggerOffSound.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
// Update state for next frame
|
||||
oldTouchDetected = currentTouchDetected;
|
||||
oldMinusHeld = currentMinusHeld;
|
||||
oldPlusHeld = currentPlusHeld;
|
||||
|
||||
// Handle existing key input logic (but don't interfere with dragging)
|
||||
if (!isDragging) {
|
||||
if (isKeyComboPressed(keysHeld, keysDown)) {
|
||||
isRendering = false;
|
||||
leventSignal(&renderingStopEvent);
|
||||
runOnce = true;
|
||||
skipOnce = true;
|
||||
TeslaFPS = 60;
|
||||
lastSelectedItem = "FPS Counter";
|
||||
lastMode = "";
|
||||
if (skipMain) {
|
||||
lastMode = "return";
|
||||
tsl::goBack();
|
||||
}
|
||||
else {
|
||||
tsl::setNextOverlay(filepath.c_str(), "--lastSelectedItem 'FPS Counter'");
|
||||
tsl::Overlay::get()->close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if we handled the input (during dragging)
|
||||
return isDragging;
|
||||
}
|
||||
};
|
||||
726
Source/Horizon-OC-Monitor/source/modes/FPS_Graph.hpp
Normal file
726
Source/Horizon-OC-Monitor/source/modes/FPS_Graph.hpp
Normal file
@@ -0,0 +1,726 @@
|
||||
class MainMenu;
|
||||
|
||||
class com_FPSGraph : public tsl::Gui {
|
||||
private:
|
||||
uint8_t refreshRate = 0;
|
||||
char FPSavg_c[8];
|
||||
FpsGraphSettings settings;
|
||||
uint64_t systemtickfrequency_impl = systemtickfrequency;
|
||||
uint32_t cnt = 0;
|
||||
char CPU_Load_c[12] = " -";
|
||||
char GPU_Load_c[12] = " -";
|
||||
char RAM_Load_c[12] = " -";
|
||||
char SOC_TEMP_c[12] = " -";
|
||||
char PCB_TEMP_c[12] = " -";
|
||||
char SKIN_TEMP_c[12] = " -";
|
||||
bool skipOnce = true;
|
||||
bool runOnce = true;
|
||||
|
||||
// Repositioning variables (matching Mini)
|
||||
int frameOffsetX = 0;
|
||||
int frameOffsetY = 0;
|
||||
bool isDragging = false;
|
||||
size_t framePadding = 10;
|
||||
static constexpr int screenWidth = 1280;
|
||||
static constexpr int screenHeight = 720;
|
||||
static constexpr int border = 8;
|
||||
|
||||
bool originalUseRightAlignment = ult::useRightAlignment;
|
||||
|
||||
struct ButtonState {
|
||||
std::atomic<bool> minusDragActive{false};
|
||||
std::atomic<bool> plusDragActive{false};
|
||||
} buttonState;
|
||||
|
||||
Thread touchPollThread;
|
||||
std::atomic<bool> touchPollRunning{false};
|
||||
|
||||
// Store actual rendered dimensions (including border)
|
||||
size_t actualTotalWidth = 0;
|
||||
size_t actualTotalHeight = 0;
|
||||
|
||||
public:
|
||||
bool isStarted = false;
|
||||
com_FPSGraph() {
|
||||
tsl::hlp::requestForeground(false);
|
||||
disableJumpTo = true;
|
||||
GetConfigSettings(&settings);
|
||||
|
||||
if (R_SUCCEEDED(SaltySD_Connect())) {
|
||||
if (R_FAILED(SaltySD_GetDisplayRefreshRate(&refreshRate)))
|
||||
refreshRate = 0;
|
||||
svcSleepThread(100'000);
|
||||
SaltySD_Term();
|
||||
}
|
||||
|
||||
// Load saved frame offsets
|
||||
frameOffsetX = settings.frameOffsetX;
|
||||
frameOffsetY = settings.frameOffsetY;
|
||||
framePadding = settings.framePadding;
|
||||
|
||||
|
||||
if (ult::limitedMemory) {
|
||||
tsl::gfx::Renderer::get().setLayerPos(std::max(std::min((int)(frameOffsetX*1.5 + 0.5) - tsl::impl::currentUnderscanPixels.first, 1280-32 - tsl::impl::currentUnderscanPixels.first), 0), 0);
|
||||
}
|
||||
|
||||
FullMode = false;
|
||||
TeslaFPS = settings.refreshRate;
|
||||
systemtickfrequency_impl /= settings.refreshRate;
|
||||
if (settings.disableScreenshots) {
|
||||
tsl::gfx::Renderer::get().removeScreenshotStacks();
|
||||
}
|
||||
deactivateOriginalFooter = true;
|
||||
mutexInit(&mutex_Misc);
|
||||
StartInfoThread();
|
||||
StartFPSCounterThread();
|
||||
|
||||
// Start touch polling thread for instant response at low FPS
|
||||
touchPollRunning.store(true, std::memory_order_release);
|
||||
threadCreate(&touchPollThread, [](void* arg) -> void {
|
||||
com_FPSGraph* overlay = static_cast<com_FPSGraph*>(arg);
|
||||
|
||||
// Allow only Player 1 and handheld mode
|
||||
const HidNpadIdType id_list[2] = { HidNpadIdType_No1, HidNpadIdType_Handheld };
|
||||
|
||||
// Configure HID system to only listen to these IDs
|
||||
hidSetSupportedNpadIdType(id_list, 2);
|
||||
|
||||
// Configure input for up to 2 supported controllers (P1 + Handheld)
|
||||
padConfigureInput(2, HidNpadStyleSet_NpadStandard | HidNpadStyleTag_NpadSystemExt);
|
||||
|
||||
// Initialize separate pad states for both controllers
|
||||
PadState pad_p1;
|
||||
PadState pad_handheld;
|
||||
padInitialize(&pad_p1, HidNpadIdType_No1);
|
||||
padInitialize(&pad_handheld, HidNpadIdType_Handheld);
|
||||
|
||||
u64 minusHoldStart = 0;
|
||||
u64 plusHoldStart = 0;
|
||||
static constexpr u64 HOLD_THRESHOLD_NS = 500'000'000ULL;
|
||||
|
||||
HidTouchScreenState state = {0};
|
||||
bool inputDetected;
|
||||
|
||||
while (overlay->touchPollRunning.load(std::memory_order_acquire)) {
|
||||
// Only poll when rendering and not dragging
|
||||
{
|
||||
inputDetected = false;
|
||||
|
||||
// Check touch in bounds
|
||||
if (hidGetTouchScreenStates(&state, 1) && state.count > 0) {
|
||||
const int touchX = state.touches[0].x;
|
||||
const int touchY = state.touches[0].y;
|
||||
|
||||
// Use actual dimensions, fallback to estimate if not yet rendered
|
||||
size_t totalWidth = overlay->actualTotalWidth;
|
||||
size_t totalHeight = overlay->actualTotalHeight;
|
||||
|
||||
if (totalWidth == 0) {
|
||||
// Fallback calculation
|
||||
const s16 refresh_rate_offset = (overlay->refreshRate < 100) ? 21 : 28;
|
||||
const s16 info_width = overlay->settings.showInfo ? (6 + overlay->rectangle_width/2 - 4) : 0;
|
||||
const s16 content_width = overlay->rectangle_width + refresh_rate_offset + info_width;
|
||||
const s16 content_height = overlay->rectangle_height + 12;
|
||||
totalWidth = content_width + (2 * border);
|
||||
totalHeight = content_height + (2 * border);
|
||||
}
|
||||
|
||||
// Apply frame offsets (base position already includes border offset)
|
||||
const int overlayX = overlay->base_x + overlay->frameOffsetX - border;
|
||||
const int overlayY = overlay->base_y + overlay->frameOffsetY - border;
|
||||
|
||||
// Touch padding
|
||||
const int touchPadding = 4;
|
||||
const int touchableX = overlayX - touchPadding;
|
||||
const int touchableY = overlayY - touchPadding;
|
||||
const int touchableWidth = totalWidth + (touchPadding * 2);
|
||||
const int touchableHeight = totalHeight + (touchPadding * 2);
|
||||
|
||||
// Check if touch is within bounds
|
||||
if (touchX >= touchableX && touchX <= touchableX + touchableWidth &&
|
||||
touchY >= touchableY && touchY <= touchableY + touchableHeight) {
|
||||
inputDetected = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Poll buttons from both controllers
|
||||
padUpdate(&pad_p1);
|
||||
padUpdate(&pad_handheld);
|
||||
|
||||
// Combine input from both controllers
|
||||
const u64 keysHeld = padGetButtons(&pad_p1) | padGetButtons(&pad_handheld);
|
||||
const u64 now = armTicksToNs(armGetSystemTick());
|
||||
|
||||
// Track MINUS hold duration
|
||||
if ((keysHeld & KEY_MINUS) && !(keysHeld & ~KEY_MINUS & ALL_KEYS_MASK)) {
|
||||
if (minusHoldStart == 0) {
|
||||
minusHoldStart = now;
|
||||
}
|
||||
if (now - minusHoldStart >= HOLD_THRESHOLD_NS) {
|
||||
inputDetected = true;
|
||||
overlay->buttonState.minusDragActive.exchange(true, std::memory_order_acq_rel);
|
||||
}
|
||||
}
|
||||
|
||||
// Track PLUS hold duration
|
||||
else if ((keysHeld & KEY_PLUS) && !(keysHeld & ~KEY_PLUS & ALL_KEYS_MASK)) {
|
||||
if (plusHoldStart == 0) {
|
||||
plusHoldStart = now;
|
||||
}
|
||||
if (now - plusHoldStart >= HOLD_THRESHOLD_NS) {
|
||||
inputDetected = true;
|
||||
overlay->buttonState.plusDragActive.exchange(true, std::memory_order_acq_rel);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
minusHoldStart = plusHoldStart = 0;
|
||||
overlay->buttonState.minusDragActive.exchange(false, std::memory_order_acq_rel);
|
||||
overlay->buttonState.plusDragActive.exchange(false, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
// Disable rendering on any input, re-enable when no input
|
||||
static bool resetOnce = true;
|
||||
if (inputDetected) {
|
||||
if (resetOnce && isRendering) {
|
||||
isRendering = false;
|
||||
leventSignal(&renderingStopEvent);
|
||||
resetOnce = false;
|
||||
}
|
||||
} else {
|
||||
resetOnce = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ult::limitedMemory) {
|
||||
static auto lastUnderscanPixels = std::make_pair(0, 0);
|
||||
|
||||
if (lastUnderscanPixels != tsl::impl::currentUnderscanPixels) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
tsl::gfx::Renderer::get().updateLayerSize();
|
||||
tsl::gfx::Renderer::get().setLayerPos(std::max(std::min((int)(overlay->frameOffsetX*1.5 + 0.5) - tsl::impl::currentUnderscanPixels.first, 1280-32 - tsl::impl::currentUnderscanPixels.first), 0), 0);
|
||||
}
|
||||
}
|
||||
lastUnderscanPixels = tsl::impl::currentUnderscanPixels;
|
||||
}
|
||||
|
||||
svcSleepThread(16000000ULL*2); // 16ms polling
|
||||
}
|
||||
}, this, NULL, 0x1000, 0x2B, -2);
|
||||
threadStart(&touchPollThread);
|
||||
}
|
||||
|
||||
~com_FPSGraph() {
|
||||
// Stop touch polling thread
|
||||
touchPollRunning.store(false, std::memory_order_release);
|
||||
threadWaitForExit(&touchPollThread);
|
||||
threadClose(&touchPollThread);
|
||||
|
||||
EndInfoThread();
|
||||
EndFPSCounterThread();
|
||||
FullMode = true;
|
||||
fixForeground = true;
|
||||
ult::useRightAlignment = originalUseRightAlignment;
|
||||
if (settings.disableScreenshots) {
|
||||
tsl::gfx::Renderer::get().addScreenshotStacks();
|
||||
}
|
||||
deactivateOriginalFooter = false;
|
||||
}
|
||||
|
||||
struct stats {
|
||||
s16 value;
|
||||
bool zero_rounded;
|
||||
};
|
||||
|
||||
std::vector<stats> readings;
|
||||
|
||||
s16 base_y = 0;
|
||||
s16 base_x = 0;
|
||||
s16 rectangle_width = 180;
|
||||
s16 rectangle_height = 60;
|
||||
s16 rectangle_x = 15;
|
||||
s16 rectangle_y = 5;
|
||||
s16 rectangle_range_max = 60;
|
||||
s16 rectangle_range_min = 0;
|
||||
char legend_max[4] = "60";
|
||||
char legend_min[2] = "0";
|
||||
s32 range = std::abs(rectangle_range_max - rectangle_range_min) + 1;
|
||||
s16 x_end = rectangle_x + rectangle_width;
|
||||
s16 y_old = rectangle_y+rectangle_height;
|
||||
s16 y_30FPS = rectangle_y+(rectangle_height / 2);
|
||||
s16 y_60FPS = rectangle_y;
|
||||
bool isAbove = false;
|
||||
|
||||
virtual tsl::elm::Element* createUI() override {
|
||||
|
||||
auto* Status = new tsl::elm::CustomDrawer([this](tsl::gfx::Renderer *renderer, u16 x, u16 y, u16 w, u16 h) {
|
||||
|
||||
// Calculate content dimensions (what goes inside the border)
|
||||
const s16 refresh_rate_offset = (refreshRate < 100) ? 21 : 28;
|
||||
const s16 info_width = settings.showInfo ? (6 + rectangle_width/2 - 4) : 6;
|
||||
const s16 content_width = rectangle_width + refresh_rate_offset + info_width;
|
||||
const s16 content_height = rectangle_height + 12;
|
||||
|
||||
// Total dimensions including border
|
||||
const size_t totalWidth = content_width + (2 * border);
|
||||
const size_t totalHeight = content_height + (2 * border);
|
||||
|
||||
// Store actual dimensions for input handling
|
||||
actualTotalWidth = totalWidth;
|
||||
actualTotalHeight = totalHeight;
|
||||
|
||||
if (refreshRate && refreshRate < 240) {
|
||||
rectangle_height = refreshRate;
|
||||
rectangle_range_max = refreshRate;
|
||||
if (refreshRate < 100) {
|
||||
rectangle_x = 15;
|
||||
legend_max[0] = 0x30 + (refreshRate / 10);
|
||||
legend_max[1] = 0x30 + (refreshRate % 10);
|
||||
legend_max[2] = 0;
|
||||
}
|
||||
else {
|
||||
rectangle_x = 22;
|
||||
legend_max[0] = 0x30 + (refreshRate / 100);
|
||||
legend_max[1] = 0x30 + ((refreshRate / 10) % 10);
|
||||
legend_max[2] = 0x30 + (refreshRate % 10);
|
||||
}
|
||||
y_30FPS = rectangle_y+(rectangle_height / 2);
|
||||
range = std::abs(rectangle_range_max - rectangle_range_min) + 1;
|
||||
};
|
||||
|
||||
int _frameOffsetX = ult::limitedMemory ? std::max(0, frameOffsetX - (1280-448)) : frameOffsetX;
|
||||
|
||||
// Calculate position with frame offsets (for the rounded rect, which includes border)
|
||||
int posX = base_x + _frameOffsetX - border;
|
||||
int posY = base_y + frameOffsetY - border;
|
||||
|
||||
// Clamp to screen bounds (accounting for total size including border)
|
||||
posX = std::max(int(framePadding), std::min(posX, static_cast<int>(screenWidth - totalWidth - framePadding)));
|
||||
posY = std::max(int(framePadding), std::min(posY, static_cast<int>(screenHeight - totalHeight - framePadding)));
|
||||
|
||||
// Draw the rounded rectangle (background)
|
||||
const tsl::Color bgColor = !isDragging
|
||||
? settings.backgroundColor
|
||||
: settings.focusBackgroundColor;
|
||||
|
||||
renderer->drawRoundedRectSingleThreaded(
|
||||
posX,
|
||||
posY,
|
||||
totalWidth,
|
||||
totalHeight,
|
||||
16,
|
||||
aWithOpacity(bgColor)
|
||||
);
|
||||
|
||||
posX += 4;
|
||||
|
||||
// Content drawing position (inside the border)
|
||||
const int final_base_x = posX + border;
|
||||
const int final_base_y = posY + border;
|
||||
|
||||
const s16 size = (refreshRate > 60 || !refreshRate) ? 63 : (s32)(63.0/(60.0/refreshRate));
|
||||
const auto width = renderer->getTextDimensions(FPSavg_c, false, size).first;
|
||||
|
||||
const s16 pos_y = size + final_base_y + rectangle_y + ((rectangle_height - size) / 2);
|
||||
const s16 pos_x = final_base_x + rectangle_x + ((rectangle_width - width) / 2);
|
||||
|
||||
if (FPSavg != 254.0)
|
||||
renderer->drawString(FPSavg_c, false, pos_x, pos_y-5, size, settings.fpsColor);
|
||||
renderer->drawEmptyRect(final_base_x+(rectangle_x - 1)+2, final_base_y+(rectangle_y - 1), rectangle_width + 2, rectangle_height + 4, aWithOpacity(settings.borderColor));
|
||||
renderer->drawDashedLine(final_base_x+rectangle_x+2, final_base_y+y_30FPS, final_base_x+rectangle_x+rectangle_width, final_base_y+y_30FPS, 6, aWithOpacity(settings.dashedLineColor));
|
||||
renderer->drawString(&legend_max[0], false, final_base_x+(rectangle_x-((refreshRate < 100) ? 15 : 22)), final_base_y+(rectangle_y+7), 10, (settings.maxFPSTextColor));
|
||||
renderer->drawString(&legend_min[0], false, final_base_x+(rectangle_x-10), final_base_y+(rectangle_y+rectangle_height+3), 10, settings.minFPSTextColor);
|
||||
|
||||
size_t last_element = readings.size() - 1;
|
||||
|
||||
s16 offset = 0;
|
||||
if (refreshRate >= 100) offset = 7;
|
||||
|
||||
static s32 y_on_range;
|
||||
static tsl::Color color = {0};
|
||||
for (s16 x = x_end; x > static_cast<s16>(x_end-readings.size()); x--) {
|
||||
y_on_range = readings[last_element].value + std::abs(rectangle_range_min) + 1;
|
||||
if (y_on_range < 0) {
|
||||
y_on_range = 0;
|
||||
}
|
||||
else if (y_on_range > range) {
|
||||
isAbove = true;
|
||||
y_on_range = range;
|
||||
}
|
||||
|
||||
const s16 y = rectangle_y + static_cast<s16>(std::lround((float)rectangle_height * ((float)(range - y_on_range) / (float)range)));
|
||||
color = (settings.mainLineColor);
|
||||
if (y == y_old && !isAbove && readings[last_element].zero_rounded) {
|
||||
if ((y == y_30FPS || y == y_60FPS))
|
||||
color = (settings.perfectLineColor);
|
||||
else
|
||||
color = (settings.dashedLineColor);
|
||||
}
|
||||
|
||||
if (x == x_end) {
|
||||
y_old = y;
|
||||
}
|
||||
|
||||
renderer->drawLine(final_base_x+x+offset, final_base_y+y, final_base_x+x+offset, final_base_y+y_old, color);
|
||||
isAbove = false;
|
||||
y_old = y;
|
||||
last_element--;
|
||||
}
|
||||
|
||||
if (settings.showInfo) {
|
||||
const s16 info_x = final_base_x+rectangle_width+rectangle_x + 6 +8;
|
||||
const s16 info_y = final_base_y + 3;
|
||||
const s16 fontSize = 11;
|
||||
|
||||
// Get line height from font size (we'll use the actual rendered height)
|
||||
const auto testDimensions = renderer->getTextDimensions("A", false, fontSize);
|
||||
const s16 lineHeight = testDimensions.second;
|
||||
|
||||
// Starting Y position for first line
|
||||
const s16 startY = info_y + lineHeight;
|
||||
|
||||
// Value X position (offset from labels)
|
||||
const s16 value_x = info_x + 40;
|
||||
|
||||
static constexpr s16 SPACING = 1;
|
||||
|
||||
// Compute gradient colors for temperatures
|
||||
const tsl::Color socColor = settings.useDynamicColors ? tsl::GradientColor(SOC_temperatureF) : settings.textColor;
|
||||
const tsl::Color pcbColor = settings.useDynamicColors ? tsl::GradientColor(PCB_temperatureF) : settings.textColor;
|
||||
const tsl::Color skinColor = settings.useDynamicColors ? tsl::GradientColor(static_cast<float>(skin_temperaturemiliC) / 1000.0f) : settings.textColor;
|
||||
|
||||
// Draw each label and value pair on the same baseline
|
||||
// Line 0: CPU
|
||||
renderer->drawString("CPU", false, info_x, startY, fontSize, settings.catColor);
|
||||
renderer->drawString(CPU_Load_c, false, value_x, startY, fontSize, settings.textColor);
|
||||
|
||||
// Line 1: GPU
|
||||
renderer->drawString("GPU", false, info_x, startY + lineHeight+SPACING, fontSize, settings.catColor);
|
||||
renderer->drawString(GPU_Load_c, false, value_x, startY + lineHeight+SPACING, fontSize, settings.textColor);
|
||||
|
||||
// Line 2: RAM
|
||||
renderer->drawString("RAM", false, info_x, startY + lineHeight * 2+2*SPACING, fontSize, settings.catColor);
|
||||
renderer->drawString(RAM_Load_c, false, value_x, startY + lineHeight * 2+2*SPACING, fontSize, settings.textColor);
|
||||
|
||||
// Line 3: SOC (with gradient color)
|
||||
renderer->drawString("SOC", false, info_x, startY + lineHeight * 3+3*SPACING, fontSize, settings.catColor);
|
||||
renderer->drawString(SOC_TEMP_c, false, value_x, startY + lineHeight * 3+3*SPACING, fontSize, socColor);
|
||||
|
||||
// Line 4: PCB (with gradient color)
|
||||
renderer->drawString("PCB", false, info_x, startY + lineHeight * 4+4*SPACING, fontSize, settings.catColor);
|
||||
renderer->drawString(PCB_TEMP_c, false, value_x, startY + lineHeight * 4+4*SPACING, fontSize, pcbColor);
|
||||
|
||||
// Line 5: SKIN (with gradient color)
|
||||
renderer->drawString("Skin", false, info_x, startY + lineHeight * 5+5*SPACING, fontSize, settings.catColor);
|
||||
renderer->drawString(SKIN_TEMP_c, false, value_x, startY + lineHeight * 5+5*SPACING, fontSize, skinColor);
|
||||
}
|
||||
});
|
||||
|
||||
tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("", "");
|
||||
rootFrame->setContent(Status);
|
||||
|
||||
return rootFrame;
|
||||
}
|
||||
|
||||
virtual void update() override {
|
||||
cnt++;
|
||||
if (cnt >= TeslaFPS)
|
||||
cnt = 0;
|
||||
|
||||
///FPS
|
||||
stats temp = {0, false};
|
||||
static uint64_t lastFrame = 0;
|
||||
|
||||
snprintf(FPSavg_c, sizeof FPSavg_c, "%2.1f", FPSavg);
|
||||
const uint8_t SaltySharedDisplayRefreshRate = *(uint8_t*)((uintptr_t)shmemGetAddr(&_sharedmemory) + 1);
|
||||
if (SaltySharedDisplayRefreshRate)
|
||||
refreshRate = SaltySharedDisplayRefreshRate;
|
||||
else refreshRate = 60;
|
||||
if (FPSavg < 254) {
|
||||
snprintf(FPSavg_c, sizeof(FPSavg_c), "%.1f", useOldFPSavg ? FPSavg_old : FPSavg);
|
||||
|
||||
if (lastFrame == lastFrameNumber) return;
|
||||
else lastFrame = lastFrameNumber;
|
||||
if ((s16)(readings.size()) >= rectangle_width) {
|
||||
readings.erase(readings.begin());
|
||||
}
|
||||
const float whole = std::round(useOldFPSavg ? FPSavg_old : FPSavg);
|
||||
temp.value = static_cast<s16>(std::lround(useOldFPSavg ? FPSavg_old : FPSavg));
|
||||
if ((useOldFPSavg ? FPSavg_old : FPSavg) < whole+0.04 && (useOldFPSavg ? FPSavg_old : FPSavg) > whole-0.05) {
|
||||
temp.zero_rounded = true;
|
||||
}
|
||||
readings.push_back(temp);
|
||||
}
|
||||
else {
|
||||
if (readings.size()) {
|
||||
readings.clear();
|
||||
readings.shrink_to_fit();
|
||||
lastFrame = 0;
|
||||
}
|
||||
FPSavg_c[0] = 0;
|
||||
}
|
||||
|
||||
if (cnt)
|
||||
return;
|
||||
|
||||
mutexLock(&mutex_Misc);
|
||||
|
||||
// Format temperature strings separately for proper alignment
|
||||
snprintf(SOC_TEMP_c, sizeof SOC_TEMP_c, "%2.1f\u00B0C", SOC_temperatureF);
|
||||
snprintf(PCB_TEMP_c, sizeof PCB_TEMP_c, "%2.1f\u00B0C", PCB_temperatureF);
|
||||
snprintf(SKIN_TEMP_c, sizeof SKIN_TEMP_c, "%2d.%d\u00B0C",
|
||||
skin_temperaturemiliC / 1000, (skin_temperaturemiliC / 100) % 10);
|
||||
|
||||
// Atomically snapshot each idle tick once
|
||||
const uint64_t idle0 = idletick0.load(std::memory_order_acquire);
|
||||
const uint64_t idle1 = idletick1.load(std::memory_order_acquire);
|
||||
const uint64_t idle2 = idletick2.load(std::memory_order_acquire);
|
||||
const uint64_t idle3 = idletick3.load(std::memory_order_acquire);
|
||||
|
||||
// Clamp values to systemtickfrequency_impl (avoid div-by-zero / runaway)
|
||||
const uint64_t safe0 = std::min(idle0, systemtickfrequency_impl);
|
||||
const uint64_t safe1 = std::min(idle1, systemtickfrequency_impl);
|
||||
const uint64_t safe2 = std::min(idle2, systemtickfrequency_impl);
|
||||
const uint64_t safe3 = std::min(idle3, systemtickfrequency_impl);
|
||||
|
||||
// Compute per-core CPU usage
|
||||
const double cpu_usage0 = (1.0 - (static_cast<double>(safe0) / systemtickfrequency_impl)) * 100.0;
|
||||
const double cpu_usage1 = (1.0 - (static_cast<double>(safe1) / systemtickfrequency_impl)) * 100.0;
|
||||
const double cpu_usage2 = (1.0 - (static_cast<double>(safe2) / systemtickfrequency_impl)) * 100.0;
|
||||
const double cpu_usage3 = (1.0 - (static_cast<double>(safe3) / systemtickfrequency_impl)) * 100.0;
|
||||
|
||||
// Compute max core load (the highest usage)
|
||||
const double cpu_usageM = std::max({cpu_usage0, cpu_usage1, cpu_usage2, cpu_usage3});
|
||||
|
||||
// Format output strings
|
||||
snprintf(CPU_Load_c, sizeof(CPU_Load_c), "%.1f%%", cpu_usageM);
|
||||
snprintf(GPU_Load_c, sizeof(GPU_Load_c), "%d.%d%%", GPU_Load_u / 10, GPU_Load_u % 10);
|
||||
snprintf(RAM_Load_c, sizeof(RAM_Load_c), "%hu.%hhu%%",
|
||||
ramLoad[SysClkRamLoad_All] / 10,
|
||||
ramLoad[SysClkRamLoad_All] % 10);
|
||||
|
||||
mutexUnlock(&mutex_Misc);
|
||||
|
||||
if (!skipOnce) {
|
||||
if (runOnce) {
|
||||
isRendering = true;
|
||||
leventClear(&renderingStopEvent);
|
||||
runOnce = false;
|
||||
}
|
||||
} else {
|
||||
skipOnce = false;
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override {
|
||||
// Static variables to maintain drag state between function calls
|
||||
static bool oldTouchDetected = false;
|
||||
static bool oldMinusHeld = false;
|
||||
static bool oldPlusHeld = false;
|
||||
static HidTouchState initialTouchPos = {0};
|
||||
static int initialFrameOffsetX = 0;
|
||||
static int initialFrameOffsetY = 0;
|
||||
static constexpr int TOUCH_THRESHOLD = 8;
|
||||
static bool hasMoved = false;
|
||||
|
||||
// Touch detection
|
||||
const bool currentTouchDetected = (touchPos.x > 0 && touchPos.y > 0 &&
|
||||
touchPos.x < screenWidth && touchPos.y < screenHeight);
|
||||
|
||||
static bool clearOnRelease = false;
|
||||
|
||||
if (clearOnRelease && !isRendering) {
|
||||
clearOnRelease = false;
|
||||
isRendering = true;
|
||||
leventClear(&renderingStopEvent);
|
||||
}
|
||||
|
||||
// Use actual dimensions from last render, fallback to estimate if not available
|
||||
size_t totalWidth = actualTotalWidth;
|
||||
size_t totalHeight = actualTotalHeight;
|
||||
|
||||
if (totalWidth == 0) {
|
||||
// Fallback calculation if not yet rendered
|
||||
const s16 refresh_rate_offset = (refreshRate < 100) ? 21 : 28;
|
||||
const s16 info_width = settings.showInfo ? (6 + rectangle_width/2 - 4) : 0;
|
||||
const s16 content_width = rectangle_width + refresh_rate_offset + info_width;
|
||||
const s16 content_height = rectangle_height + 12;
|
||||
totalWidth = content_width + (2 * border);
|
||||
totalHeight = content_height + (2 * border);
|
||||
}
|
||||
|
||||
// Current overlay position (top-left of rounded rect)
|
||||
const int overlayX = base_x + frameOffsetX - border;
|
||||
const int overlayY = base_y + frameOffsetY - border;
|
||||
|
||||
// Touch detection area (with padding for easier interaction)
|
||||
static constexpr int touchPadding = 4;
|
||||
const int touchableX = overlayX - touchPadding;
|
||||
const int touchableY = overlayY - touchPadding;
|
||||
const int touchableWidth = totalWidth + (touchPadding * 2);
|
||||
const int touchableHeight = totalHeight + (touchPadding * 2);
|
||||
|
||||
// Screen boundaries for clamping (accounting for total size)
|
||||
const int minX = -(base_x - border) + framePadding;
|
||||
const int maxX = screenWidth - totalWidth - (base_x - border) - framePadding;
|
||||
const int minY = -(base_y - border) + framePadding;
|
||||
const int maxY = screenHeight - totalHeight - (base_y - border) - framePadding;
|
||||
|
||||
const bool minusDragReady = buttonState.minusDragActive.load(std::memory_order_acquire);
|
||||
const bool plusDragReady = buttonState.plusDragActive.load(std::memory_order_acquire);
|
||||
|
||||
// Check button states
|
||||
const bool currentMinusHeld = (keysHeld & KEY_MINUS) && !(keysHeld & ~KEY_MINUS & ALL_KEYS_MASK) && minusDragReady;
|
||||
const bool currentPlusHeld = (keysHeld & KEY_PLUS) && !(keysHeld & ~KEY_PLUS & ALL_KEYS_MASK) && plusDragReady;
|
||||
|
||||
// Handle touch dragging
|
||||
if (currentTouchDetected && !isDragging) {
|
||||
const int touchX = touchPos.x;
|
||||
const int touchY = touchPos.y;
|
||||
|
||||
if (!oldTouchDetected) {
|
||||
// Touch just started - check if within overlay bounds
|
||||
if (touchX >= touchableX && touchX <= touchableX + touchableWidth &&
|
||||
touchY >= touchableY && touchY <= touchableY + touchableHeight) {
|
||||
|
||||
// Start touch dragging
|
||||
isDragging = true;
|
||||
triggerRumbleClick.store(true, std::memory_order_release);
|
||||
triggerOnSound.store(true, std::memory_order_release);
|
||||
hasMoved = false;
|
||||
initialTouchPos = touchPos;
|
||||
initialFrameOffsetX = frameOffsetX;
|
||||
initialFrameOffsetY = frameOffsetY;
|
||||
}
|
||||
}
|
||||
} else if (currentTouchDetected && isDragging && !currentMinusHeld && !currentPlusHeld) {
|
||||
// Continue touch dragging
|
||||
const int touchX = touchPos.x;
|
||||
const int touchY = touchPos.y;
|
||||
const int deltaX = touchX - initialTouchPos.x;
|
||||
const int deltaY = touchY - initialTouchPos.y;
|
||||
|
||||
// Check if we've moved enough to consider this a drag
|
||||
if (!hasMoved) {
|
||||
const int totalMovement = abs(deltaX) + abs(deltaY);
|
||||
if (totalMovement >= TOUCH_THRESHOLD) {
|
||||
hasMoved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasMoved) {
|
||||
// Update frame offsets with boundary checking
|
||||
frameOffsetX = std::max(minX, std::min(maxX, initialFrameOffsetX + deltaX));
|
||||
frameOffsetY = std::max(minY, std::min(maxY, initialFrameOffsetY + deltaY));
|
||||
|
||||
if (ult::limitedMemory) {
|
||||
tsl::gfx::Renderer::get().setLayerPos(std::max(std::min((int)(frameOffsetX*1.5 + 0.5) - tsl::impl::currentUnderscanPixels.first, 1280-32 - tsl::impl::currentUnderscanPixels.first), 0), 0);
|
||||
}
|
||||
}
|
||||
} else if (!currentTouchDetected && oldTouchDetected && isDragging && !currentMinusHeld && !currentPlusHeld) {
|
||||
// Touch just released
|
||||
if (hasMoved) {
|
||||
// Save position when touch drag ends
|
||||
auto iniData = ult::getParsedDataFromIniFile(configIniPath);
|
||||
iniData["fps-graph"]["frame_offset_x"] = std::to_string(frameOffsetX);
|
||||
iniData["fps-graph"]["frame_offset_y"] = std::to_string(frameOffsetY);
|
||||
ult::saveIniFileData(configIniPath, iniData);
|
||||
}
|
||||
|
||||
// Reset touch drag state
|
||||
isDragging = false;
|
||||
hasMoved = false;
|
||||
clearOnRelease = true;
|
||||
triggerRumbleDoubleClick.store(true, std::memory_order_release);
|
||||
triggerOffSound.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
// Handle joystick dragging (MINUS + right joystick OR PLUS + left joystick)
|
||||
if ((currentMinusHeld || currentPlusHeld) && !isDragging) {
|
||||
// Start joystick dragging
|
||||
isDragging = true;
|
||||
triggerRumbleClick.store(true, std::memory_order_release);
|
||||
triggerOnSound.store(true, std::memory_order_release);
|
||||
} else if ((currentMinusHeld || currentPlusHeld) && isDragging) {
|
||||
// Continue joystick dragging
|
||||
static constexpr int JOYSTICK_DEADZONE = 20;
|
||||
|
||||
// Choose the appropriate joystick based on which button is held
|
||||
const HidAnalogStickState& activeJoystick = currentMinusHeld ? joyStickPosRight : joyStickPosLeft;
|
||||
|
||||
// Only move if joystick is outside deadzone
|
||||
if (abs(activeJoystick.x) > JOYSTICK_DEADZONE || abs(activeJoystick.y) > JOYSTICK_DEADZONE) {
|
||||
// Calculate joystick magnitude
|
||||
const float magnitude = sqrt((float)(activeJoystick.x * activeJoystick.x + activeJoystick.y * activeJoystick.y));
|
||||
const float normalizedMagnitude = magnitude / 32767.0f;
|
||||
|
||||
// Smooth curve for sensitivity
|
||||
static constexpr float baseSensitivity = 0.00008f;
|
||||
static constexpr float maxSensitivity = 0.0005f;
|
||||
|
||||
const float curveValue = pow(normalizedMagnitude, 8.0f);
|
||||
const float currentSensitivity = baseSensitivity + (maxSensitivity - baseSensitivity) * curveValue;
|
||||
|
||||
// Calculate movement delta with fractional accumulation
|
||||
static float accumulatedX = 0.0f;
|
||||
static float accumulatedY = 0.0f;
|
||||
|
||||
accumulatedX += (float)activeJoystick.x * currentSensitivity;
|
||||
accumulatedY += -(float)activeJoystick.y * currentSensitivity;
|
||||
|
||||
const int deltaX = (int)accumulatedX;
|
||||
const int deltaY = (int)accumulatedY;
|
||||
accumulatedX -= deltaX;
|
||||
accumulatedY -= deltaY;
|
||||
|
||||
// Update frame offsets with boundary checking
|
||||
frameOffsetX = std::max(minX, std::min(maxX, frameOffsetX + deltaX));
|
||||
frameOffsetY = std::max(minY, std::min(maxY, frameOffsetY + deltaY));
|
||||
|
||||
if (ult::limitedMemory) {
|
||||
tsl::gfx::Renderer::get().setLayerPos(std::max(std::min((int)(frameOffsetX*1.5 + 0.5) - tsl::impl::currentUnderscanPixels.first, 1280-32 - tsl::impl::currentUnderscanPixels.first), 0), 0);
|
||||
}
|
||||
}
|
||||
} else if (((!currentMinusHeld && oldMinusHeld) || (!currentPlusHeld && oldPlusHeld)) && isDragging) {
|
||||
// Button just released - stop joystick dragging
|
||||
auto iniData = ult::getParsedDataFromIniFile(configIniPath);
|
||||
iniData["fps-graph"]["frame_offset_x"] = std::to_string(frameOffsetX);
|
||||
iniData["fps-graph"]["frame_offset_y"] = std::to_string(frameOffsetY);
|
||||
ult::saveIniFileData(configIniPath, iniData);
|
||||
isDragging = false;
|
||||
clearOnRelease = true;
|
||||
triggerRumbleDoubleClick.store(true, std::memory_order_release);
|
||||
triggerOffSound.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
// Update state for next frame
|
||||
oldTouchDetected = currentTouchDetected;
|
||||
oldMinusHeld = currentMinusHeld;
|
||||
oldPlusHeld = currentPlusHeld;
|
||||
|
||||
// Handle existing key input logic (but don't interfere with dragging)
|
||||
if (!isDragging) {
|
||||
if (isKeyComboPressed(keysHeld, keysDown)) {
|
||||
isRendering = false;
|
||||
leventSignal(&renderingStopEvent);
|
||||
runOnce = true;
|
||||
skipOnce = true;
|
||||
TeslaFPS = 60;
|
||||
lastSelectedItem = "FPS Graph";
|
||||
lastMode = "";
|
||||
if (skipMain) {
|
||||
lastMode = "return";
|
||||
tsl::goBack();
|
||||
}
|
||||
else {
|
||||
tsl::setNextOverlay(filepath.c_str(), "--lastSelectedItem 'FPS Graph'");
|
||||
tsl::Overlay::get()->close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if we handled the input (during dragging)
|
||||
return isDragging;
|
||||
}
|
||||
};
|
||||
635
Source/Horizon-OC-Monitor/source/modes/Full.hpp
Normal file
635
Source/Horizon-OC-Monitor/source/modes/Full.hpp
Normal file
@@ -0,0 +1,635 @@
|
||||
class MainMenu;
|
||||
|
||||
class FullOverlay : public tsl::Gui {
|
||||
private:
|
||||
char DeltaCPU_c[12] = "";
|
||||
char DeltaGPU_c[12] = "";
|
||||
char DeltaRAM_c[12] = "";
|
||||
char RealCPU_Hz_c[64] = "";
|
||||
char RealGPU_Hz_c[64] = "";
|
||||
char RealRAM_Hz_c[64] = "";
|
||||
char GPU_Load_c[32] = "";
|
||||
char Rotation_SpeedLevel_c[64] = "";
|
||||
char RAM_compressed_c[64] = "";
|
||||
char RAM_var_compressed_c[128] = "";
|
||||
char RAM_percentage_var_compressed_c[128] = "";
|
||||
char CPU_Hz_c[64] = "";
|
||||
char GPU_Hz_c[64] = "";
|
||||
char RAM_Hz_c[64] = "";
|
||||
char CPU_compressed_c[160] = "";
|
||||
char SOC_temperature_c[32] = "";
|
||||
char PCB_temperature_c[32] = "";
|
||||
char skin_temperature_c[32] = "";
|
||||
char BatteryDraw_c[64] = "";
|
||||
char FPS_var_compressed_c[64] = "";
|
||||
char RAM_load_c[64] = "";
|
||||
char Resolutions_c[64] = "";
|
||||
char readSpeed_c[32] = "";
|
||||
|
||||
// New separated value buffers for CPU cores
|
||||
char CPU_Core0_c[16] = "";
|
||||
char CPU_Core1_c[16] = "";
|
||||
char CPU_Core2_c[16] = "";
|
||||
char CPU_Core3_c[16] = "";
|
||||
|
||||
// New separated value buffers for FPS
|
||||
char PFPS_value_c[16] = "";
|
||||
char FPS_value_c[16] = "";
|
||||
|
||||
static constexpr uint8_t COMMON_MARGIN = 20;
|
||||
FullSettings settings;
|
||||
uint64_t systemtickfrequency_impl = systemtickfrequency;
|
||||
std::string formattedKeyCombo = keyCombo;
|
||||
std::string message;
|
||||
const std::vector<std::string> KEY_SYMBOLS = {
|
||||
"\uE0E4", "\uE0E5", "\uE0E6", "\uE0E7",
|
||||
"\uE0E8", "\uE0E9", "\uE0ED", "\uE0EB",
|
||||
"\uE0EE", "\uE0EC", "\uE0E0", "\uE0E1",
|
||||
"\uE0E2", "\uE0E3", "\uE08A", "\uE08B",
|
||||
"\uE0B6", "\uE0B5"
|
||||
};
|
||||
|
||||
bool skipOnce = true;
|
||||
bool runOnce = true;
|
||||
|
||||
bool originalUseRightAlignment = ult::useRightAlignment;
|
||||
public:
|
||||
FullOverlay() {
|
||||
disableJumpTo = true;
|
||||
GetConfigSettings(&settings);
|
||||
mutexInit(&mutex_BatteryChecker);
|
||||
mutexInit(&mutex_Misc);
|
||||
tsl::hlp::requestForeground(false);
|
||||
TeslaFPS = settings.refreshRate;
|
||||
systemtickfrequency_impl /= settings.refreshRate;
|
||||
idletick0.store(systemtickfrequency_impl, std::memory_order_relaxed);
|
||||
idletick1.store(systemtickfrequency_impl, std::memory_order_relaxed);
|
||||
idletick2.store(systemtickfrequency_impl, std::memory_order_relaxed);
|
||||
idletick3.store(systemtickfrequency_impl, std::memory_order_relaxed);
|
||||
if (settings.setPosRight) {
|
||||
const auto [horizontalUnderscanPixels, verticalUnderscanPixels] = tsl::gfx::getUnderscanPixels();
|
||||
tsl::gfx::Renderer::get().setLayerPos(1280-32 - horizontalUnderscanPixels, 0);
|
||||
ult::useRightAlignment = true;
|
||||
} else {
|
||||
tsl::gfx::Renderer::get().setLayerPos(0, 0);
|
||||
ult::useRightAlignment = false;
|
||||
}
|
||||
if (settings.disableScreenshots) {
|
||||
tsl::gfx::Renderer::get().removeScreenshotStacks();
|
||||
}
|
||||
deactivateOriginalFooter = true;
|
||||
formatButtonCombination(formattedKeyCombo);
|
||||
//message = "Press " + formattedKeyCombo + " to Exit";
|
||||
|
||||
realVoltsPolling = false;
|
||||
StartThreads();
|
||||
}
|
||||
~FullOverlay() {
|
||||
CloseThreads();
|
||||
fixForeground = true;
|
||||
ult::useRightAlignment = originalUseRightAlignment;
|
||||
if (settings.disableScreenshots) {
|
||||
tsl::gfx::Renderer::get().addScreenshotStacks();
|
||||
}
|
||||
deactivateOriginalFooter = false;
|
||||
}
|
||||
|
||||
resolutionCalls m_resolutionRenderCalls[8] = {0};
|
||||
resolutionCalls m_resolutionViewportCalls[8] = {0};
|
||||
resolutionCalls m_resolutionOutput[8] = {0};
|
||||
uint8_t resolutionLookup = 0;
|
||||
|
||||
virtual tsl::elm::Element* createUI() override {
|
||||
|
||||
|
||||
auto Status = new tsl::elm::CustomDrawer([this](tsl::gfx::Renderer *renderer, u16 x, u16 y, u16 w, u16 h) {
|
||||
//static auto targetFreqWidth = renderer->getTextDimensions("Target Frequency", false, 15).first;
|
||||
//static auto realFreqWidth = renderer->getTextDimensions("Real Frequency", false, 15).first;
|
||||
//static auto freqWidth = std::max(targetFreqWidth, realFreqWidth);
|
||||
|
||||
//static auto batteryLabelWidth = renderer->getTextDimensions("Battery Power Flow", false, 15).first;
|
||||
//static auto fanLabelWidth = renderer->getTextDimensions("Fan Rotation Level", false, 15).first;
|
||||
//static auto boardWidth = std::max(batteryLabelWidth, fanLabelWidth);
|
||||
|
||||
static constexpr size_t valueOffset = 150+10;
|
||||
static constexpr size_t deltaOffset = 246+10;
|
||||
static constexpr size_t ramPercentageOffset = 350+10;
|
||||
|
||||
//Print strings
|
||||
///CPU
|
||||
if (R_SUCCEEDED(clkrstCheck) || R_SUCCEEDED(pcvCheck)) {
|
||||
|
||||
uint32_t height_offset = 155;
|
||||
if (realCPU_Hz && settings.showRealFreqs) {
|
||||
height_offset = 162;
|
||||
}
|
||||
renderer->drawString("CPU Usage", false, COMMON_MARGIN, 120, 20, (settings.catColor1));
|
||||
if (settings.showTargetFreqs) {
|
||||
//static auto targetFreqWidth = renderer->getTextDimensions("Target Frequency: ", false, 15).first;
|
||||
renderer->drawString("Target Frequency", false, COMMON_MARGIN, height_offset, 15, (settings.catColor2));
|
||||
renderer->drawString(CPU_Hz_c, false, COMMON_MARGIN + valueOffset, height_offset, 15, (settings.textColor));
|
||||
}
|
||||
if (realCPU_Hz && settings.showRealFreqs) {
|
||||
//static auto realFreqWidth = renderer->getTextDimensions("Real Frequency: ", false, 15).first;
|
||||
renderer->drawString("Real Frequency", false, COMMON_MARGIN, height_offset - 15, 15, (settings.catColor2));
|
||||
renderer->drawString(RealCPU_Hz_c, false, COMMON_MARGIN + valueOffset, height_offset - 15, 15, (settings.textColor));
|
||||
if (settings.showDeltas && settings.showTargetFreqs) {
|
||||
renderer->drawString(DeltaCPU_c, false, COMMON_MARGIN + deltaOffset, height_offset - 7, 15, (settings.textColor));
|
||||
}
|
||||
else if (settings.showDeltas && !settings.showTargetFreqs) {
|
||||
renderer->drawString(DeltaCPU_c, false, COMMON_MARGIN + deltaOffset, height_offset - 15, 15, (settings.textColor));
|
||||
}
|
||||
}
|
||||
else if (realCPU_Hz && settings.showDeltas && (settings.showRealFreqs || settings.showTargetFreqs)) {
|
||||
renderer->drawString(DeltaCPU_c, false, COMMON_MARGIN + deltaOffset, height_offset, 15, (settings.textColor));
|
||||
}
|
||||
|
||||
// CPU Core labels and values
|
||||
static auto core0Width = renderer->getTextDimensions("Core 0 ", false, 15).first;
|
||||
static auto core1Width = renderer->getTextDimensions("Core 1 ", false, 15).first;
|
||||
static auto core2Width = renderer->getTextDimensions("Core 2 ", false, 15).first;
|
||||
static auto core3Width = renderer->getTextDimensions("Core 3 ", false, 15).first;
|
||||
|
||||
const uint32_t core_height = height_offset + 30;
|
||||
renderer->drawString("Core 0 ", false, COMMON_MARGIN, core_height, 15, (settings.catColor2));
|
||||
renderer->drawString(CPU_Core0_c, false, COMMON_MARGIN + core0Width, core_height, 15, (settings.textColor));
|
||||
|
||||
renderer->drawString("Core 1 ", false, COMMON_MARGIN, core_height + 15, 15, (settings.catColor2));
|
||||
renderer->drawString(CPU_Core1_c, false, COMMON_MARGIN + core1Width, core_height + 15, 15, (settings.textColor));
|
||||
|
||||
renderer->drawString("Core 2 ", false, COMMON_MARGIN, core_height + 30, 15, (settings.catColor2));
|
||||
renderer->drawString(CPU_Core2_c, false, COMMON_MARGIN + core2Width, core_height + 30, 15, (settings.textColor));
|
||||
|
||||
renderer->drawString("Core 3 ", false, COMMON_MARGIN, core_height + 45, 15, (settings.catColor2));
|
||||
renderer->drawString(CPU_Core3_c, false, COMMON_MARGIN + core3Width, core_height + 45, 15, (settings.textColor));
|
||||
}
|
||||
|
||||
///GPU
|
||||
if (R_SUCCEEDED(clkrstCheck) || R_SUCCEEDED(pcvCheck) || R_SUCCEEDED(nvCheck)) {
|
||||
|
||||
uint32_t height_offset = 320-8;
|
||||
if (realGPU_Hz && settings.showRealFreqs) {
|
||||
height_offset = 327-8;
|
||||
}
|
||||
|
||||
renderer->drawString("GPU Usage", false, COMMON_MARGIN, 285-8, 20, (settings.catColor1));
|
||||
if (R_SUCCEEDED(clkrstCheck) || R_SUCCEEDED(pcvCheck)) {
|
||||
if (settings.showTargetFreqs) {
|
||||
//static auto targetFreqWidth = renderer->getTextDimensions("Target Frequency: ", false, 15).first;
|
||||
renderer->drawString("Target Frequency", false, COMMON_MARGIN, height_offset, 15, (settings.catColor2));
|
||||
renderer->drawString(GPU_Hz_c, false, COMMON_MARGIN + valueOffset, height_offset, 15, (settings.textColor));
|
||||
}
|
||||
if (realCPU_Hz && settings.showRealFreqs) {
|
||||
//static auto realFreqWidth = renderer->getTextDimensions("Real Frequency: ", false, 15).first;
|
||||
renderer->drawString("Real Frequency", false, COMMON_MARGIN, height_offset - 15, 15, (settings.catColor2));
|
||||
renderer->drawString(RealGPU_Hz_c, false, COMMON_MARGIN + valueOffset, height_offset - 15, 15, (settings.textColor));
|
||||
if (settings.showDeltas && settings.showTargetFreqs) {
|
||||
renderer->drawString(DeltaGPU_c, false, COMMON_MARGIN + deltaOffset, height_offset - 7, 15, (settings.textColor));
|
||||
}
|
||||
else if (settings.showDeltas && !settings.showTargetFreqs) {
|
||||
renderer->drawString(DeltaGPU_c, false, COMMON_MARGIN + deltaOffset, height_offset - 15, 15, (settings.textColor));
|
||||
}
|
||||
}
|
||||
else if (realGPU_Hz && settings.showDeltas && (settings.showRealFreqs || settings.showTargetFreqs)) {
|
||||
renderer->drawString(DeltaGPU_c, false, COMMON_MARGIN + deltaOffset, height_offset, 15, (settings.textColor));
|
||||
}
|
||||
}
|
||||
if (R_SUCCEEDED(nvCheck)) {
|
||||
//static auto loadWidth = renderer->getTextDimensions("Load: ", false, 15).first;
|
||||
renderer->drawString("Load", false, COMMON_MARGIN, height_offset + 15, 15, (settings.catColor2));
|
||||
renderer->drawString(GPU_Load_c, false, COMMON_MARGIN + valueOffset, height_offset + 15, 15, (settings.textColor));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static std::vector<std::string> specialChars = {""};
|
||||
|
||||
///RAM
|
||||
if (R_SUCCEEDED(clkrstCheck) || R_SUCCEEDED(pcvCheck) || R_SUCCEEDED(Hinted)) {
|
||||
|
||||
uint32_t height_offset = 410;
|
||||
if (realRAM_Hz && settings.showRealFreqs) {
|
||||
height_offset += 7;
|
||||
}
|
||||
|
||||
renderer->drawString("RAM Usage", false, COMMON_MARGIN, 375, 20, (settings.catColor1));
|
||||
if (R_SUCCEEDED(clkrstCheck) || R_SUCCEEDED(pcvCheck)) {
|
||||
if (settings.showTargetFreqs) {
|
||||
//static auto targetFreqWidth = renderer->getTextDimensions("Target Frequency: ", false, 15).first;
|
||||
renderer->drawString("Target Frequency", false, COMMON_MARGIN, height_offset, 15, (settings.catColor2));
|
||||
renderer->drawString(RAM_Hz_c, false, COMMON_MARGIN + valueOffset, height_offset, 15, (settings.textColor));
|
||||
}
|
||||
if (realRAM_Hz && settings.showRealFreqs) {
|
||||
//static auto realFreqWidth = renderer->getTextDimensions("Real Frequency: ", false, 15).first;
|
||||
renderer->drawString("Real Frequency", false, COMMON_MARGIN, height_offset - 15, 15, (settings.catColor2));
|
||||
renderer->drawString(RealRAM_Hz_c, false, COMMON_MARGIN + valueOffset, height_offset - 15, 15, (settings.textColor));
|
||||
if (settings.showDeltas && settings.showTargetFreqs) {
|
||||
renderer->drawString(DeltaRAM_c, false, COMMON_MARGIN + deltaOffset, height_offset - 7, 15, (settings.textColor));
|
||||
}
|
||||
else if (settings.showDeltas && !settings.showTargetFreqs) {
|
||||
renderer->drawString(DeltaRAM_c, false, COMMON_MARGIN + deltaOffset, height_offset - 15, 15, (settings.textColor));
|
||||
}
|
||||
}
|
||||
else if (realRAM_Hz && settings.showDeltas && (settings.showRealFreqs || settings.showTargetFreqs)) {
|
||||
renderer->drawString(DeltaRAM_c, false, COMMON_MARGIN + deltaOffset, height_offset, 15, (settings.textColor));
|
||||
}
|
||||
if (R_SUCCEEDED(sysclkCheck)) {
|
||||
static std::vector<std::string> ramLoadColoredChars = {"CPU", "GPU"};
|
||||
//static auto loadLabelWidth = renderer->getTextDimensions("Load: ", false, 15).first;
|
||||
renderer->drawString("Load", false, COMMON_MARGIN, height_offset+15, 15, (settings.catColor2));
|
||||
renderer->drawStringWithColoredSections(RAM_load_c, false, ramLoadColoredChars, COMMON_MARGIN + valueOffset, height_offset+15, 15, (settings.textColor), settings.catColor2);
|
||||
}
|
||||
}
|
||||
if (R_SUCCEEDED(Hinted)) {
|
||||
//static auto textWidth = renderer->getTextDimensions("Total \nApplication \nApplet \nSystem \nSystem Unsafe ", false, 15).first;
|
||||
renderer->drawString("Total\nApplication\nApplet\nSystem\nSystem Unsafe", false, COMMON_MARGIN, height_offset + 40, 15, (settings.catColor2));
|
||||
renderer->drawString(RAM_var_compressed_c, false, COMMON_MARGIN + valueOffset, height_offset + 40, 15, (settings.textColor));
|
||||
renderer->drawString(RAM_percentage_var_compressed_c, false, ramPercentageOffset, height_offset + 40, 15, (settings.textColor));
|
||||
}
|
||||
}
|
||||
|
||||
///Thermal
|
||||
if (R_SUCCEEDED(i2cCheck) || R_SUCCEEDED(tcCheck) || R_SUCCEEDED(pwmCheck)) {
|
||||
renderer->drawString("Board", false, 20, 550+2, 20, (settings.catColor1));
|
||||
if (R_SUCCEEDED(i2cCheck)) {
|
||||
renderer->drawString("Battery Power Flow", false, COMMON_MARGIN, 575+2, 15, (settings.catColor2));
|
||||
renderer->drawStringWithColoredSections(BatteryDraw_c, false, specialChars, COMMON_MARGIN + valueOffset, 575+2, 15, (settings.textColor), settings.separatorColor);
|
||||
}
|
||||
if (R_SUCCEEDED(pwmCheck)) {
|
||||
renderer->drawString("Fan Rotation Level", false, COMMON_MARGIN, 590+2, 15, (settings.catColor2));
|
||||
renderer->drawString(Rotation_SpeedLevel_c, false, COMMON_MARGIN + valueOffset, 590+2, 15, (settings.textColor));
|
||||
}
|
||||
if (R_SUCCEEDED(i2cCheck) || R_SUCCEEDED(tcCheck)) {
|
||||
static auto socLabelWidth = renderer->getTextDimensions("SOC ", false, 15).first;
|
||||
static auto pcbLabelWidth = renderer->getTextDimensions("PCB ", false, 15).first;
|
||||
static auto maxLabelWidth = std::max(socLabelWidth, pcbLabelWidth);
|
||||
static auto skinLabelWidth = renderer->getTextDimensions("Skin ", false, 15).first;
|
||||
|
||||
// Compute gradient colors for temperatures
|
||||
const tsl::Color socColor = settings.useDynamicColors ? tsl::GradientColor(SOC_temperatureF) : settings.textColor;
|
||||
const tsl::Color pcbColor = settings.useDynamicColors ? tsl::GradientColor(PCB_temperatureF) : settings.textColor;
|
||||
const tsl::Color skinColor = settings.useDynamicColors ? tsl::GradientColor(static_cast<float>(skin_temperaturemiliC) / 1000.0f) : settings.textColor;
|
||||
|
||||
renderer->drawString("Temperatures", false, COMMON_MARGIN, 605+2, 15, (settings.catColor2));
|
||||
|
||||
// SOC - starts at valueOffset next to "Temperatures"
|
||||
uint32_t current_x = COMMON_MARGIN + valueOffset;
|
||||
renderer->drawString("SOC ", false, current_x, 605+2, 15, (settings.catColor2));
|
||||
current_x += maxLabelWidth;
|
||||
renderer->drawString(SOC_temperature_c, false, current_x, 605+2, 15, socColor);
|
||||
|
||||
// SKIN - same spacing to the right
|
||||
current_x += renderer->getTextDimensions(SOC_temperature_c, false, 15).first + 15;
|
||||
renderer->drawString("Skin ", false, current_x, 605+2, 15, (settings.catColor2));
|
||||
current_x += skinLabelWidth;
|
||||
renderer->drawString(skin_temperature_c, false, current_x, 605+2, 15, skinColor);
|
||||
|
||||
// PCB - below SOC on next line
|
||||
current_x = COMMON_MARGIN + valueOffset;
|
||||
renderer->drawString("PCB ", false, current_x, 620+2, 15, (settings.catColor2));
|
||||
current_x += maxLabelWidth;
|
||||
renderer->drawString(PCB_temperature_c, false, current_x, 620+2, 15, pcbColor);
|
||||
}
|
||||
}
|
||||
|
||||
///FPS
|
||||
if (GameRunning) {
|
||||
const uint32_t width_offset = valueOffset;
|
||||
if (settings.showFPS || settings.showRES || settings.showRDSD) {
|
||||
renderer->drawString("Game", false, COMMON_MARGIN + width_offset, 185+12, 20, (settings.catColor1));
|
||||
}
|
||||
uint32_t height = 210+12;
|
||||
if (settings.showFPS == true) {
|
||||
static auto pfpsWidth = renderer->getTextDimensions("PFPS ", false, 15).first;
|
||||
static auto fpsWidth = renderer->getTextDimensions("FPS ", false, 15).first;
|
||||
|
||||
renderer->drawString("PFPS ", false, COMMON_MARGIN + width_offset, height, 15, (settings.catColor2));
|
||||
renderer->drawString(PFPS_value_c, false, COMMON_MARGIN + width_offset + pfpsWidth, height, 15, (settings.textColor));
|
||||
|
||||
// Calculate position for "FPS " label - add some spacing
|
||||
const uint32_t fps_x_offset = COMMON_MARGIN + width_offset + pfpsWidth + renderer->getTextDimensions(PFPS_value_c, false, 15).first + 15;
|
||||
renderer->drawString("FPS ", false, fps_x_offset, height, 15, (settings.catColor2));
|
||||
renderer->drawString(FPS_value_c, false, fps_x_offset + fpsWidth, height, 15, (settings.textColor));
|
||||
|
||||
height += 15;
|
||||
}
|
||||
if ((settings.showRES == true) && NxFps && SharedMemoryUsed && (NxFps -> API >= 1)) {
|
||||
static auto resLabelWidth = renderer->getTextDimensions("Resolutions ", false, 15).first;
|
||||
renderer->drawString("Resolutions ", false, COMMON_MARGIN + width_offset, height, 15, (settings.catColor2));
|
||||
|
||||
renderer->drawStringWithColoredSections(Resolutions_c, false, specialChars, COMMON_MARGIN + width_offset + resLabelWidth, height, 15, (settings.textColor), settings.separatorColor);
|
||||
height += 15;
|
||||
}
|
||||
if (settings.showRDSD == true) {
|
||||
static auto readLabelWidth = renderer->getTextDimensions("Read Speed ", false, 15).first;
|
||||
renderer->drawString("Read Speed ", false, COMMON_MARGIN + width_offset, height, 15, (settings.catColor2));
|
||||
renderer->drawString(readSpeed_c, false, COMMON_MARGIN + width_offset + readLabelWidth, height, 15, (settings.textColor));
|
||||
}
|
||||
}
|
||||
|
||||
//renderer->drawStringWithColoredSections(message, false, KEY_SYMBOLS, 30, 693, 23, a(tsl::bottomTextColor), a(tsl::buttonColor));
|
||||
|
||||
|
||||
static const auto pressWidth = renderer->getTextDimensions("Press ", false, 23).first;
|
||||
static const auto keyComboWidth = renderer->getTextDimensions(formattedKeyCombo.c_str(), false, 23).first;
|
||||
|
||||
static constexpr u16 baseX = 30;
|
||||
static constexpr u16 baseY = 693;
|
||||
static constexpr u8 fontSize = 23;
|
||||
|
||||
// Draw "Press "
|
||||
renderer->drawString("Press ", false, baseX, baseY, fontSize, (tsl::bottomTextColor));
|
||||
|
||||
// Draw formatted key combo with colored sections
|
||||
renderer->drawStringWithColoredSections(formattedKeyCombo, false, KEY_SYMBOLS, baseX + pressWidth, baseY, fontSize, (tsl::bottomTextColor), (tsl::buttonColor));
|
||||
|
||||
// Draw " to Exit"
|
||||
renderer->drawString(" to Exit", false, baseX + pressWidth + keyComboWidth, baseY, fontSize, (tsl::bottomTextColor));
|
||||
});
|
||||
|
||||
auto rootFrame = new tsl::elm::HeaderOverlayFrame("Status Monitor", APP_VERSION);
|
||||
rootFrame->setContent(Status);
|
||||
|
||||
return rootFrame;
|
||||
}
|
||||
|
||||
virtual void update() override {
|
||||
//Make stuff ready to print
|
||||
///CPU
|
||||
if (systemtickfrequency_impl > 0) {
|
||||
const uint64_t idle0_val = std::min(idletick0.load(std::memory_order_acquire), systemtickfrequency_impl);
|
||||
const uint64_t idle1_val = std::min(idletick1.load(std::memory_order_acquire), systemtickfrequency_impl);
|
||||
const uint64_t idle2_val = std::min(idletick2.load(std::memory_order_acquire), systemtickfrequency_impl);
|
||||
const uint64_t idle3_val = std::min(idletick3.load(std::memory_order_acquire), systemtickfrequency_impl);
|
||||
|
||||
const float usage0 = std::clamp(100.0f * (1.0f - float(idle0_val) / systemtickfrequency_impl), 0.0f, 100.0f);
|
||||
const float usage1 = std::clamp(100.0f * (1.0f - float(idle1_val) / systemtickfrequency_impl), 0.0f, 100.0f);
|
||||
const float usage2 = std::clamp(100.0f * (1.0f - float(idle2_val) / systemtickfrequency_impl), 0.0f, 100.0f);
|
||||
const float usage3 = std::clamp(100.0f * (1.0f - float(idle3_val) / systemtickfrequency_impl), 0.0f, 100.0f);
|
||||
|
||||
// Format individual core values
|
||||
snprintf(CPU_Core0_c, sizeof(CPU_Core0_c), "%.2f%%", usage0);
|
||||
snprintf(CPU_Core1_c, sizeof(CPU_Core1_c), "%.2f%%", usage1);
|
||||
snprintf(CPU_Core2_c, sizeof(CPU_Core2_c), "%.2f%%", usage2);
|
||||
snprintf(CPU_Core3_c, sizeof(CPU_Core3_c), "%.2f%%", usage3);
|
||||
}
|
||||
|
||||
mutexLock(&mutex_Misc);
|
||||
snprintf(CPU_Hz_c, sizeof(CPU_Hz_c), "%u.%u MHz", CPU_Hz / 1000000, (CPU_Hz / 100000) % 10);
|
||||
if (realCPU_Hz) {
|
||||
snprintf(RealCPU_Hz_c, sizeof(RealCPU_Hz_c), "%u.%u MHz", realCPU_Hz / 1000000, (realCPU_Hz / 100000) % 10);
|
||||
const int32_t deltaCPU = (int32_t)(realCPU_Hz / 1000) - (CPU_Hz / 1000);
|
||||
snprintf(DeltaCPU_c, sizeof(DeltaCPU_c), "Δ %d.%u", deltaCPU / 1000, abs(deltaCPU / 100) % 10);
|
||||
}
|
||||
|
||||
///GPU
|
||||
snprintf(GPU_Hz_c, sizeof GPU_Hz_c, "%u.%u MHz", GPU_Hz / 1000000, (GPU_Hz / 100000) % 10);
|
||||
if (realGPU_Hz) {
|
||||
snprintf(RealGPU_Hz_c, sizeof(RealGPU_Hz_c), "%u.%u MHz", realGPU_Hz / 1000000, (realGPU_Hz / 100000) % 10);
|
||||
const int32_t deltaGPU = (int32_t)(realGPU_Hz / 1000) - (GPU_Hz / 1000);
|
||||
snprintf(DeltaGPU_c, sizeof(DeltaGPU_c), "Δ %d.%u", deltaGPU / 1000, abs(deltaGPU / 100) % 10);
|
||||
}
|
||||
snprintf(GPU_Load_c, sizeof GPU_Load_c, "%u.%u%%", GPU_Load_u / 10, GPU_Load_u % 10);
|
||||
|
||||
///RAM
|
||||
snprintf(RAM_Hz_c, sizeof RAM_Hz_c, "%u.%u MHz", RAM_Hz / 1000000, (RAM_Hz / 100000) % 10);
|
||||
if (realRAM_Hz) {
|
||||
snprintf(RealRAM_Hz_c, sizeof(RealRAM_Hz_c), "%u.%u MHz", realRAM_Hz / 1000000, (realRAM_Hz / 100000) % 10);
|
||||
const int32_t deltaRAM = (int32_t)(realRAM_Hz / 1000) - (RAM_Hz / 1000);
|
||||
snprintf(DeltaRAM_c, sizeof(DeltaRAM_c), "Δ %d.%u", deltaRAM / 1000, abs(deltaRAM / 100) % 10);
|
||||
}
|
||||
|
||||
const float RAM_Total_application_f = (float)RAM_Total_application_u / 1024 / 1024;
|
||||
const float RAM_Total_applet_f = (float)RAM_Total_applet_u / 1024 / 1024;
|
||||
const float RAM_Total_system_f = (float)RAM_Total_system_u / 1024 / 1024;
|
||||
const float RAM_Total_systemunsafe_f = (float)RAM_Total_systemunsafe_u / 1024 / 1024;
|
||||
const float RAM_Total_all_f = RAM_Total_application_f + RAM_Total_applet_f + RAM_Total_system_f + RAM_Total_systemunsafe_f;
|
||||
|
||||
const float RAM_Used_application_f = (float)RAM_Used_application_u / 1024 / 1024;
|
||||
const float RAM_Used_applet_f = (float)RAM_Used_applet_u / 1024 / 1024;
|
||||
const float RAM_Used_system_f = (float)RAM_Used_system_u / 1024 / 1024;
|
||||
const float RAM_Used_systemunsafe_f = (float)RAM_Used_systemunsafe_u / 1024 / 1024;
|
||||
const float RAM_Used_all_f = RAM_Used_application_f + RAM_Used_applet_f + RAM_Used_system_f + RAM_Used_systemunsafe_f;
|
||||
|
||||
// Compute percentages
|
||||
const int RAMPct_all = (int)((RAM_Used_all_f / RAM_Total_all_f) * 100.0f );
|
||||
const int RAMPct_app = (int)((RAM_Used_application_f / RAM_Total_application_f) * 100.0f );
|
||||
const int RAMPct_applet = (int)((RAM_Used_applet_f / RAM_Total_applet_f) * 100.0f );
|
||||
const int RAMPct_system = (int)((RAM_Used_system_f / RAM_Total_system_f) * 100.0f );
|
||||
const int RAMPct_systemunsafe = (int)((RAM_Used_systemunsafe_f/ RAM_Total_systemunsafe_f)* 100.0f );
|
||||
|
||||
snprintf(RAM_var_compressed_c, sizeof(RAM_var_compressed_c),
|
||||
"%.1f MB / %.1f MB\n"
|
||||
"%.1f MB / %.1f MB\n"
|
||||
"%.1f MB / %.1f MB\n"
|
||||
"%.1f MB / %.1f MB\n"
|
||||
"%.1f MB / %.1f MB",
|
||||
RAM_Used_all_f, RAM_Total_all_f,
|
||||
RAM_Used_application_f, RAM_Total_application_f,
|
||||
RAM_Used_applet_f, RAM_Total_applet_f,
|
||||
RAM_Used_system_f, RAM_Total_system_f,
|
||||
RAM_Used_systemunsafe_f, RAM_Total_systemunsafe_f
|
||||
);
|
||||
|
||||
// 2. Percentages only (newlines preserved)
|
||||
snprintf(RAM_percentage_var_compressed_c, sizeof(RAM_percentage_var_compressed_c),
|
||||
"(%d%%)\n"
|
||||
"(%d%%)\n"
|
||||
"(%d%%)\n"
|
||||
"(%d%%)\n"
|
||||
"(%d%%)",
|
||||
RAMPct_all,
|
||||
RAMPct_app,
|
||||
RAMPct_applet,
|
||||
RAMPct_system,
|
||||
RAMPct_systemunsafe
|
||||
);
|
||||
|
||||
if (R_SUCCEEDED(sysclkCheck)) {
|
||||
const int RAM_GPU_Load = ramLoad[SysClkRamLoad_All] - ramLoad[SysClkRamLoad_Cpu];
|
||||
snprintf(RAM_load_c, sizeof RAM_load_c,
|
||||
"%u.%u%% CPU %u.%u%% GPU %u.%u%%",
|
||||
ramLoad[SysClkRamLoad_All] / 10, ramLoad[SysClkRamLoad_All] % 10,
|
||||
ramLoad[SysClkRamLoad_Cpu] / 10, ramLoad[SysClkRamLoad_Cpu] % 10,
|
||||
RAM_GPU_Load / 10, RAM_GPU_Load % 10);
|
||||
}
|
||||
///Thermal
|
||||
snprintf(SOC_temperature_c, sizeof SOC_temperature_c, "%.1f\u00B0C", SOC_temperatureF);
|
||||
snprintf(PCB_temperature_c, sizeof PCB_temperature_c, "%.1f\u00B0C", PCB_temperatureF);
|
||||
snprintf(skin_temperature_c, sizeof skin_temperature_c, "%d.%d\u00B0C", skin_temperaturemiliC / 1000, (skin_temperaturemiliC / 100) % 10);
|
||||
|
||||
snprintf(Rotation_SpeedLevel_c, sizeof Rotation_SpeedLevel_c, "%.1f%%", Rotation_Duty);
|
||||
|
||||
///FPS
|
||||
if (settings.showFPS == true) {
|
||||
snprintf(PFPS_value_c, sizeof PFPS_value_c, "%1u", FPS);
|
||||
snprintf(FPS_value_c, sizeof FPS_value_c, "%.1f", useOldFPSavg ? FPSavg_old : FPSavg);
|
||||
}
|
||||
|
||||
//Resolutions
|
||||
if ((settings.showRES == true) && GameRunning && NxFps) {
|
||||
if (!resolutionLookup) {
|
||||
(NxFps -> renderCalls[0].calls) = 0xFFFF;
|
||||
resolutionLookup = 1;
|
||||
}
|
||||
else if (resolutionLookup == 1) {
|
||||
if ((NxFps -> renderCalls[0].calls) != 0xFFFF) resolutionLookup = 2;
|
||||
}
|
||||
else {
|
||||
if (NxFps && SharedMemoryUsed) {
|
||||
memcpy(&m_resolutionRenderCalls, &(NxFps -> renderCalls), sizeof(m_resolutionRenderCalls));
|
||||
memcpy(&m_resolutionViewportCalls, &(NxFps -> viewportCalls), sizeof(m_resolutionViewportCalls));
|
||||
} else {
|
||||
memset(&m_resolutionRenderCalls, 0, sizeof(m_resolutionRenderCalls));
|
||||
memset(&m_resolutionViewportCalls, 0, sizeof(m_resolutionViewportCalls));
|
||||
}
|
||||
qsort(m_resolutionRenderCalls, 8, sizeof(resolutionCalls), compare);
|
||||
qsort(m_resolutionViewportCalls, 8, sizeof(resolutionCalls), compare);
|
||||
memset(&m_resolutionOutput, 0, sizeof(m_resolutionOutput));
|
||||
size_t out_iter = 0;
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
for (size_t x = 0; x < 8; x++) {
|
||||
if (m_resolutionRenderCalls[i].width == 0) {
|
||||
break;
|
||||
}
|
||||
if ((m_resolutionRenderCalls[i].width == m_resolutionViewportCalls[x].width) && (m_resolutionRenderCalls[i].height == m_resolutionViewportCalls[x].height)) {
|
||||
m_resolutionOutput[out_iter].width = m_resolutionRenderCalls[i].width;
|
||||
m_resolutionOutput[out_iter].height = m_resolutionRenderCalls[i].height;
|
||||
m_resolutionOutput[out_iter].calls = (m_resolutionRenderCalls[i].calls > m_resolutionViewportCalls[x].calls) ? m_resolutionRenderCalls[i].calls : m_resolutionViewportCalls[x].calls;
|
||||
out_iter++;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found && m_resolutionRenderCalls[i].width != 0) {
|
||||
m_resolutionOutput[out_iter].width = m_resolutionRenderCalls[i].width;
|
||||
m_resolutionOutput[out_iter].height = m_resolutionRenderCalls[i].height;
|
||||
m_resolutionOutput[out_iter].calls = m_resolutionRenderCalls[i].calls;
|
||||
out_iter++;
|
||||
}
|
||||
found = false;
|
||||
if (out_iter == 8) break;
|
||||
}
|
||||
if (out_iter < 8) {
|
||||
const size_t out_iter_s = out_iter;
|
||||
for (size_t x = 0; x < 8; x++) {
|
||||
for (size_t y = 0; y < out_iter_s; y++) {
|
||||
if (m_resolutionViewportCalls[x].width == 0) {
|
||||
break;
|
||||
}
|
||||
if ((m_resolutionViewportCalls[x].width == m_resolutionOutput[y].width) && (m_resolutionViewportCalls[x].height == m_resolutionOutput[y].height)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found && m_resolutionViewportCalls[x].width != 0) {
|
||||
m_resolutionOutput[out_iter].width = m_resolutionViewportCalls[x].width;
|
||||
m_resolutionOutput[out_iter].height = m_resolutionViewportCalls[x].height;
|
||||
m_resolutionOutput[out_iter].calls = m_resolutionViewportCalls[x].calls;
|
||||
out_iter++;
|
||||
}
|
||||
found = false;
|
||||
if (out_iter == 8) break;
|
||||
}
|
||||
}
|
||||
qsort(m_resolutionOutput, 8, sizeof(resolutionCalls), compare);
|
||||
static std::pair<uint16_t, uint16_t> old_res[2];
|
||||
|
||||
// Only swap if BOTH resolutions exist (prevent swapping with empty slot)
|
||||
if (m_resolutionOutput[0].width && m_resolutionOutput[1].width) {
|
||||
if ((m_resolutionOutput[0].width == old_res[1].first && m_resolutionOutput[0].height == old_res[1].second) ||
|
||||
(m_resolutionOutput[1].width == old_res[0].first && m_resolutionOutput[1].height == old_res[0].second)) {
|
||||
const uint16_t swap_width = m_resolutionOutput[0].width;
|
||||
const uint16_t swap_height = m_resolutionOutput[0].height;
|
||||
m_resolutionOutput[0].width = m_resolutionOutput[1].width;
|
||||
m_resolutionOutput[0].height = m_resolutionOutput[1].height;
|
||||
m_resolutionOutput[1].width = swap_width;
|
||||
m_resolutionOutput[1].height = swap_height;
|
||||
}
|
||||
}
|
||||
|
||||
//if (!m_resolutionOutput[1].width) {
|
||||
// snprintf(Resolutions_c, sizeof(Resolutions_c), "%dx%d", m_resolutionOutput[0].width, m_resolutionOutput[0].height);
|
||||
//}
|
||||
//else {
|
||||
// snprintf(Resolutions_c, sizeof(Resolutions_c), "%dx%d%dx%d", m_resolutionOutput[0].width, m_resolutionOutput[0].height, m_resolutionOutput[1].width, m_resolutionOutput[1].height);
|
||||
//}
|
||||
|
||||
if (!m_resolutionOutput[1].width || !m_resolutionOutput[0].width) {
|
||||
if (!m_resolutionOutput[1].width)
|
||||
snprintf(Resolutions_c, sizeof(Resolutions_c), "%dx%d", m_resolutionOutput[0].width, m_resolutionOutput[0].height);
|
||||
else snprintf(Resolutions_c, sizeof(Resolutions_c), "%dx%d", m_resolutionOutput[1].width, m_resolutionOutput[1].height);
|
||||
}
|
||||
else snprintf(Resolutions_c, sizeof(Resolutions_c),"%dx%d%dx%d", m_resolutionOutput[0].width, m_resolutionOutput[0].height, m_resolutionOutput[1].width, m_resolutionOutput[1].height);
|
||||
|
||||
old_res[0] = std::make_pair(m_resolutionOutput[0].width, m_resolutionOutput[0].height);
|
||||
old_res[1] = std::make_pair(m_resolutionOutput[1].width, m_resolutionOutput[1].height);
|
||||
}
|
||||
if (settings.showRDSD == true && GameRunning && NxFps) {
|
||||
if ((NxFps -> readSpeedPerSecond) != 0.f) snprintf(readSpeed_c, sizeof(readSpeed_c), "%.2f MiB/s", (NxFps -> readSpeedPerSecond) / 1048576.f);
|
||||
else snprintf(readSpeed_c, sizeof(readSpeed_c), "n/d");
|
||||
}
|
||||
}
|
||||
else if (!GameRunning && resolutionLookup != 0) {
|
||||
resolutionLookup = 0;
|
||||
}
|
||||
|
||||
mutexUnlock(&mutex_Misc);
|
||||
|
||||
/* ── Battery / power draw ───────────────────────────────────── */
|
||||
char remainingBatteryLife[8];
|
||||
|
||||
/* Normalise "-0.00" → "0.00" W */
|
||||
const float drawW = (fabsf(PowerConsumption) < 0.01f) ? 0.0f
|
||||
: PowerConsumption;
|
||||
|
||||
mutexLock(&mutex_BatteryChecker);
|
||||
|
||||
/* keep "--:--" whenever estimate is negative */
|
||||
if (batTimeEstimate >= 0 && !(drawW <= 0.01f && drawW >= -0.01f)) {
|
||||
snprintf(remainingBatteryLife, sizeof(remainingBatteryLife),
|
||||
"%d:%02d", batTimeEstimate / 60, batTimeEstimate % 60);
|
||||
} else {
|
||||
strcpy(remainingBatteryLife, "--:--");
|
||||
}
|
||||
|
||||
const float batteryPercent = (float)_batteryChargeInfoFields.RawBatteryCharge / 1000.0f;
|
||||
|
||||
snprintf(BatteryDraw_c, sizeof(BatteryDraw_c),
|
||||
"%.2f W%.0f%% [%s]",
|
||||
drawW,
|
||||
batteryPercent,
|
||||
remainingBatteryLife);
|
||||
|
||||
mutexUnlock(&mutex_BatteryChecker);
|
||||
|
||||
if (!skipOnce) {
|
||||
if (runOnce) {
|
||||
isRendering = true;
|
||||
leventClear(&renderingStopEvent);
|
||||
runOnce = false;
|
||||
}
|
||||
} else {
|
||||
skipOnce = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override {
|
||||
if (isKeyComboPressed(keysHeld, keysDown)) {
|
||||
isRendering = false;
|
||||
leventSignal(&renderingStopEvent);
|
||||
triggerRumbleDoubleClick.store(true, std::memory_order_release);
|
||||
triggerExitSound.store(true, std::memory_order_release);
|
||||
skipOnce = true;
|
||||
runOnce = true;
|
||||
TeslaFPS = 60;
|
||||
lastSelectedItem = "Full";
|
||||
lastMode = "";
|
||||
tsl::swapTo<MainMenu>();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
1137
Source/Horizon-OC-Monitor/source/modes/Micro.hpp
Normal file
1137
Source/Horizon-OC-Monitor/source/modes/Micro.hpp
Normal file
File diff suppressed because it is too large
Load Diff
1649
Source/Horizon-OC-Monitor/source/modes/Mini.hpp
Normal file
1649
Source/Horizon-OC-Monitor/source/modes/Mini.hpp
Normal file
File diff suppressed because it is too large
Load Diff
189
Source/Horizon-OC-Monitor/source/modes/Misc.hpp
Normal file
189
Source/Horizon-OC-Monitor/source/modes/Misc.hpp
Normal file
@@ -0,0 +1,189 @@
|
||||
class OtherMenu;
|
||||
|
||||
void StartMiscThread() {
|
||||
// Wait for existing thread to exit
|
||||
threadWaitForExit(&t0);
|
||||
|
||||
// Clear the thread exit event for new thread
|
||||
leventClear(&threadexit);
|
||||
|
||||
// Close and recreate thread
|
||||
threadClose(&t0);
|
||||
threadCreate(&t0, Misc2, NULL, NULL, 0x1000, 0x3F, 3);
|
||||
threadStart(&t0);
|
||||
}
|
||||
|
||||
void EndMiscThread() {
|
||||
// Signal the thread exit event
|
||||
leventSignal(&threadexit);
|
||||
|
||||
// Wait for thread to exit
|
||||
threadWaitForExit(&t0);
|
||||
|
||||
// Close thread handle
|
||||
threadClose(&t0);
|
||||
}
|
||||
|
||||
class MiscOverlay : public tsl::Gui {
|
||||
private:
|
||||
char DSP_Load_c[16];
|
||||
// Separated value buffers for NV clocks
|
||||
char NVDEC_value_c[16] = "";
|
||||
char NVENC_value_c[16] = "";
|
||||
char NVJPG_value_c[16] = "";
|
||||
char Nifm_pass[96];
|
||||
FullSettings settings;
|
||||
public:
|
||||
MiscOverlay() {
|
||||
GetConfigSettings(&settings);
|
||||
disableJumpTo = true;
|
||||
smInitialize();
|
||||
nifmCheck = nifmInitialize(NifmServiceType_Admin);
|
||||
if (R_SUCCEEDED(mmuInitialize())) {
|
||||
nvdecCheck = mmuRequestInitialize(&nvdecRequest, MmuModuleId(5), 8, false);
|
||||
nvencCheck = mmuRequestInitialize(&nvencRequest, MmuModuleId(6), 8, false);
|
||||
nvjpgCheck = mmuRequestInitialize(&nvjpgRequest, MmuModuleId(7), 8, false);
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(audsnoopInitialize()))
|
||||
audsnoopCheck = audsnoopEnableDspUsageMeasurement();
|
||||
|
||||
smExit();
|
||||
StartMiscThread();
|
||||
//tsl::elm::g_disableMenuCacheOnReturn.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
~MiscOverlay() {
|
||||
EndMiscThread();
|
||||
nifmExit();
|
||||
mmuRequestFinalize(&nvdecRequest);
|
||||
mmuRequestFinalize(&nvencRequest);
|
||||
mmuRequestFinalize(&nvjpgRequest);
|
||||
mmuExit();
|
||||
if (R_SUCCEEDED(audsnoopCheck)) {
|
||||
audsnoopDisableDspUsageMeasurement();
|
||||
}
|
||||
audsnoopExit();
|
||||
}
|
||||
|
||||
virtual tsl::elm::Element* createUI() override {
|
||||
|
||||
auto* Status = new tsl::elm::CustomDrawer([this](tsl::gfx::Renderer *renderer, u16 x, u16 y, u16 w, u16 h) {
|
||||
static constexpr u16 X_OFFSET = 30;
|
||||
static constexpr u16 NV_VALUE_X = 120; // Aligned X position for NV values
|
||||
|
||||
///DSP
|
||||
if (R_SUCCEEDED(audsnoopCheck)) {
|
||||
renderer->drawString(DSP_Load_c, false, X_OFFSET, 120, 20, (settings.textColor));
|
||||
}
|
||||
|
||||
//Multimedia engines
|
||||
if (R_SUCCEEDED(nvdecCheck | nvencCheck | nvjpgCheck)) {
|
||||
renderer->drawString("Multimedia Clock Rates", false, X_OFFSET, 165, 20, (settings.catColor1));
|
||||
|
||||
u16 currentY = 185;
|
||||
|
||||
if (R_SUCCEEDED(nvdecCheck)) {
|
||||
renderer->drawString("NVDEC", false, X_OFFSET+15, currentY, 15, (settings.catColor2));
|
||||
renderer->drawString(NVDEC_value_c, false, NV_VALUE_X, currentY, 15, (settings.textColor));
|
||||
currentY += 15;
|
||||
}
|
||||
if (R_SUCCEEDED(nvencCheck)) {
|
||||
renderer->drawString("NVENC", false, X_OFFSET+15, currentY, 15, (settings.catColor2));
|
||||
renderer->drawString(NVENC_value_c, false, NV_VALUE_X, currentY, 15, (settings.textColor));
|
||||
currentY += 15;
|
||||
}
|
||||
if (R_SUCCEEDED(nvjpgCheck)) {
|
||||
renderer->drawString("NVJPG", false, X_OFFSET+15, currentY, 15, (settings.catColor2));
|
||||
renderer->drawString(NVJPG_value_c, false, NV_VALUE_X, currentY, 15, (settings.textColor));
|
||||
}
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(nifmCheck)) {
|
||||
renderer->drawString("Network", false, X_OFFSET, 255, 20, (settings.catColor1));
|
||||
if (!Nifm_internet_rc) {
|
||||
if (NifmConnectionType == NifmInternetConnectionType_WiFi) {
|
||||
renderer->drawString("Type: Wi-Fi", false, X_OFFSET, 280, 18, (settings.catColor2));
|
||||
if (!Nifm_profile_rc) {
|
||||
if (Nifm_showpass)
|
||||
renderer->drawString(Nifm_pass, false, X_OFFSET, 305, 15, (settings.textColor));
|
||||
else
|
||||
renderer->drawString("Press Y to show password", false, X_OFFSET, 305, 15, (settings.textColor));
|
||||
}
|
||||
}
|
||||
else if (NifmConnectionType == NifmInternetConnectionType_Ethernet)
|
||||
renderer->drawString("Type: Ethernet", false, X_OFFSET, 280, 18, (settings.textColor));
|
||||
}
|
||||
else
|
||||
renderer->drawString("Type: Not connected", false, X_OFFSET, 280, 18, (settings.textColor));
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
//tsl::elm::g_disableMenuCacheOnReturn.store(true, std::memory_order_release);
|
||||
tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("Status Monitor", APP_VERSION, true);
|
||||
rootFrame->setContent(Status);
|
||||
|
||||
return rootFrame;
|
||||
}
|
||||
|
||||
virtual void update() override {
|
||||
|
||||
snprintf(DSP_Load_c, sizeof DSP_Load_c, "DSP usage: %u%%", DSP_Load_u);
|
||||
|
||||
// Format just the values for NV clocks
|
||||
snprintf(NVDEC_value_c, sizeof(NVDEC_value_c), "%.1f MHz", (float)NVDEC_Hz / 1000000);
|
||||
snprintf(NVENC_value_c, sizeof(NVENC_value_c), "%.1f MHz", (float)NVENC_Hz / 1000000);
|
||||
snprintf(NVJPG_value_c, sizeof(NVJPG_value_c), "%.1f MHz", (float)NVJPG_Hz / 1000000);
|
||||
|
||||
char pass_temp1[25] = "";
|
||||
char pass_temp2[25] = "";
|
||||
char pass_temp3[17] = "";
|
||||
if (Nifm_profile.wireless_setting_data.passphrase_len > 48) {
|
||||
memcpy(&pass_temp1, &(Nifm_profile.wireless_setting_data.passphrase[0]), 24);
|
||||
memcpy(&pass_temp2, &(Nifm_profile.wireless_setting_data.passphrase[24]), 24);
|
||||
memcpy(&pass_temp3, &(Nifm_profile.wireless_setting_data.passphrase[48]), 16);
|
||||
}
|
||||
else if (Nifm_profile.wireless_setting_data.passphrase_len > 24) {
|
||||
memcpy(&pass_temp1, &(Nifm_profile.wireless_setting_data.passphrase[0]), 24);
|
||||
memcpy(&pass_temp2, &(Nifm_profile.wireless_setting_data.passphrase[24]), 24);
|
||||
}
|
||||
else {
|
||||
memcpy(&pass_temp1, &(Nifm_profile.wireless_setting_data.passphrase[0]), 24);
|
||||
}
|
||||
snprintf(Nifm_pass, sizeof Nifm_pass, "%s\n%s\n%s", pass_temp1, pass_temp2, pass_temp3);
|
||||
|
||||
static bool skipOnce = true;
|
||||
|
||||
if (!skipOnce) {
|
||||
static bool runOnce = true;
|
||||
if (runOnce) {
|
||||
isRendering = true;
|
||||
leventClear(&renderingStopEvent);
|
||||
runOnce = false;
|
||||
}
|
||||
} else {
|
||||
skipOnce = false;
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override {
|
||||
if (keysHeld & KEY_Y) {
|
||||
Nifm_showpass = true;
|
||||
}
|
||||
else Nifm_showpass = false;
|
||||
|
||||
if (keysDown & KEY_B) {
|
||||
isRendering = false;
|
||||
leventSignal(&renderingStopEvent);
|
||||
triggerRumbleDoubleClick.store(true, std::memory_order_release);
|
||||
triggerExitSound.store(true, std::memory_order_release);
|
||||
lastSelectedItem = "Miscellaneous";
|
||||
lastMode = "";
|
||||
tsl::swapTo<OtherMenu>();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
581
Source/Horizon-OC-Monitor/source/modes/Resolutions.hpp
Normal file
581
Source/Horizon-OC-Monitor/source/modes/Resolutions.hpp
Normal file
@@ -0,0 +1,581 @@
|
||||
class MainMenu;
|
||||
|
||||
class ResolutionsOverlay : public tsl::Gui {
|
||||
private:
|
||||
char Resolutions_c[512] = {0};
|
||||
char Resolutions2_c[512] = {0};
|
||||
ResolutionSettings settings;
|
||||
bool skipOnce = true;
|
||||
bool runOnce = true;
|
||||
|
||||
// Repositioning variables (matching Mini)
|
||||
int frameOffsetX = 0;
|
||||
int frameOffsetY = 0;
|
||||
bool isDragging = false;
|
||||
size_t framePadding = 10;
|
||||
static constexpr int screenWidth = 1280;
|
||||
static constexpr int screenHeight = 720;
|
||||
|
||||
bool originalUseRightAlignment = ult::useRightAlignment;
|
||||
|
||||
struct ButtonState {
|
||||
std::atomic<bool> minusDragActive{false};
|
||||
std::atomic<bool> plusDragActive{false};
|
||||
} buttonState;
|
||||
|
||||
Thread touchPollThread;
|
||||
std::atomic<bool> touchPollRunning{false};
|
||||
|
||||
std::atomic<bool> inputDetected{false};
|
||||
|
||||
public:
|
||||
ResolutionsOverlay() {
|
||||
tsl::hlp::requestForeground(false);
|
||||
disableJumpTo = true;
|
||||
GetConfigSettings(&settings);
|
||||
|
||||
// Load saved frame offsets
|
||||
frameOffsetX = settings.frameOffsetX;
|
||||
frameOffsetY = settings.frameOffsetY;
|
||||
framePadding = settings.framePadding;
|
||||
|
||||
if (ult::limitedMemory) {
|
||||
tsl::gfx::Renderer::get().setLayerPos(std::max(std::min((int)(frameOffsetX*1.5 + 0.5) - tsl::impl::currentUnderscanPixels.first, 1280-32 - tsl::impl::currentUnderscanPixels.first), 0), 0);
|
||||
}
|
||||
|
||||
if (settings.disableScreenshots) {
|
||||
tsl::gfx::Renderer::get().removeScreenshotStacks();
|
||||
}
|
||||
deactivateOriginalFooter = true;
|
||||
FullMode = false;
|
||||
TeslaFPS = settings.refreshRate;
|
||||
StartFPSCounterThread();
|
||||
|
||||
// Start touch polling thread for instant response at low FPS
|
||||
touchPollRunning.store(true, std::memory_order_release);
|
||||
threadCreate(&touchPollThread, [](void* arg) -> void {
|
||||
ResolutionsOverlay* overlay = static_cast<ResolutionsOverlay*>(arg);
|
||||
|
||||
// Allow only Player 1 and handheld mode
|
||||
const HidNpadIdType id_list[2] = { HidNpadIdType_No1, HidNpadIdType_Handheld };
|
||||
|
||||
// Configure HID system to only listen to these IDs
|
||||
hidSetSupportedNpadIdType(id_list, 2);
|
||||
|
||||
// Configure input for up to 2 supported controllers (P1 + Handheld)
|
||||
padConfigureInput(2, HidNpadStyleSet_NpadStandard | HidNpadStyleTag_NpadSystemExt);
|
||||
|
||||
// Initialize separate pad states for both controllers
|
||||
PadState pad_p1;
|
||||
PadState pad_handheld;
|
||||
padInitialize(&pad_p1, HidNpadIdType_No1);
|
||||
padInitialize(&pad_handheld, HidNpadIdType_Handheld);
|
||||
|
||||
u64 minusHoldStart = 0;
|
||||
u64 plusHoldStart = 0;
|
||||
static constexpr u64 HOLD_THRESHOLD_NS = 500'000'000ULL;
|
||||
|
||||
HidTouchScreenState state = {0};
|
||||
|
||||
|
||||
while (overlay->touchPollRunning.load(std::memory_order_acquire)) {
|
||||
// Only poll when rendering and not dragging
|
||||
{
|
||||
overlay->inputDetected.store(false, std::memory_order_release);
|
||||
|
||||
// Check touch in bounds
|
||||
if (hidGetTouchScreenStates(&state, 1) && state.count > 0) {
|
||||
const int touchX = state.touches[0].x;
|
||||
const int touchY = state.touches[0].y;
|
||||
|
||||
// Calculate bounds (same logic as handleInput)
|
||||
const int overlayX = overlay->frameOffsetX;
|
||||
const int overlayY = overlay->frameOffsetY;
|
||||
|
||||
// Overlay dimensions based on game state
|
||||
int overlayWidth, overlayHeight;
|
||||
overlayWidth = 360-20;
|
||||
overlayHeight = 200;
|
||||
|
||||
// Add touch padding
|
||||
const int touchPadding = 4;
|
||||
const int touchableX = overlayX - touchPadding;
|
||||
const int touchableY = overlayY - touchPadding;
|
||||
const int touchableWidth = overlayWidth + (touchPadding * 2);
|
||||
const int touchableHeight = overlayHeight + (touchPadding * 2);
|
||||
|
||||
// Check if touch is within bounds
|
||||
if (touchX >= touchableX && touchX <= touchableX + touchableWidth &&
|
||||
touchY >= touchableY && touchY <= touchableY + touchableHeight) {
|
||||
overlay->inputDetected.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
// Poll buttons from both controllers
|
||||
padUpdate(&pad_p1);
|
||||
padUpdate(&pad_handheld);
|
||||
|
||||
// Combine input from both controllers
|
||||
const u64 keysHeld = padGetButtons(&pad_p1) | padGetButtons(&pad_handheld);
|
||||
const u64 now = armTicksToNs(armGetSystemTick());
|
||||
|
||||
// Track MINUS hold duration
|
||||
if ((keysHeld & KEY_MINUS) && !(keysHeld & ~KEY_MINUS & ALL_KEYS_MASK)) {
|
||||
if (minusHoldStart == 0) {
|
||||
minusHoldStart = now;
|
||||
}
|
||||
if (now - minusHoldStart >= HOLD_THRESHOLD_NS) {
|
||||
// Long enough to start drag
|
||||
overlay->inputDetected.store(true, std::memory_order_release);
|
||||
overlay->buttonState.minusDragActive.exchange(true, std::memory_order_acq_rel);
|
||||
}
|
||||
}
|
||||
|
||||
// Track PLUS hold duration
|
||||
else if ((keysHeld & KEY_PLUS) && !(keysHeld & ~KEY_PLUS & ALL_KEYS_MASK)) {
|
||||
if (plusHoldStart == 0) {
|
||||
plusHoldStart = now;
|
||||
}
|
||||
if (now - plusHoldStart >= HOLD_THRESHOLD_NS) {
|
||||
// Long enough to start drag
|
||||
overlay->inputDetected.store(true, std::memory_order_release);
|
||||
overlay->buttonState.plusDragActive.exchange(true, std::memory_order_acq_rel);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
minusHoldStart = plusHoldStart = 0;
|
||||
overlay->buttonState.minusDragActive.exchange(false, std::memory_order_acq_rel);
|
||||
overlay->buttonState.plusDragActive.exchange(false, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
// Disable rendering on any input, re-enable when no input
|
||||
static bool resetOnce = true;
|
||||
if (overlay->inputDetected.load(std::memory_order_acquire)) {
|
||||
if (resetOnce && isRendering) {
|
||||
isRendering = false;
|
||||
leventSignal(&renderingStopEvent);
|
||||
resetOnce = false;
|
||||
}
|
||||
} else {
|
||||
resetOnce = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ult::limitedMemory) {
|
||||
static auto lastUnderscanPixels = std::make_pair(0, 0);
|
||||
|
||||
if (lastUnderscanPixels != tsl::impl::currentUnderscanPixels) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
tsl::gfx::Renderer::get().updateLayerSize();
|
||||
tsl::gfx::Renderer::get().setLayerPos(std::max(std::min((int)(overlay->frameOffsetX*1.5 + 0.5) - tsl::impl::currentUnderscanPixels.first, 1280-32 - tsl::impl::currentUnderscanPixels.first), 0), 0);
|
||||
}
|
||||
}
|
||||
lastUnderscanPixels = tsl::impl::currentUnderscanPixels;
|
||||
}
|
||||
|
||||
svcSleepThread(16000000ULL*2); // 16ms polling
|
||||
}
|
||||
}, this, NULL, 0x1000, 0x2B, -2);
|
||||
threadStart(&touchPollThread);
|
||||
}
|
||||
|
||||
~ResolutionsOverlay() {
|
||||
// Stop touch polling thread
|
||||
touchPollRunning.store(false, std::memory_order_release);
|
||||
threadWaitForExit(&touchPollThread);
|
||||
threadClose(&touchPollThread);
|
||||
|
||||
EndFPSCounterThread();
|
||||
TeslaFPS = 60;
|
||||
if (settings.disableScreenshots) {
|
||||
tsl::gfx::Renderer::get().addScreenshotStacks();
|
||||
}
|
||||
deactivateOriginalFooter = false;
|
||||
ult::useRightAlignment = originalUseRightAlignment;
|
||||
fixForeground = true;
|
||||
FullMode = true;
|
||||
}
|
||||
|
||||
resolutionCalls m_resolutionRenderCalls[8] = {0};
|
||||
resolutionCalls m_resolutionViewportCalls[8] = {0};
|
||||
bool gameStart = false;
|
||||
uint8_t resolutionLookup = 0;
|
||||
u64 lastGameSeenTick = 0;
|
||||
bool waitingForGame = true;
|
||||
|
||||
virtual tsl::elm::Element* createUI() override {
|
||||
|
||||
auto* Status = new tsl::elm::CustomDrawer([this](tsl::gfx::Renderer *renderer, u16 x, u16 y, u16 w, u16 h) {
|
||||
int base_y = 0;
|
||||
int base_x = 0;
|
||||
int clippingOffsetX = 0, clippingOffsetY = 0;
|
||||
|
||||
int total_width = 360 - 20;
|
||||
int total_height = 200;
|
||||
|
||||
// Check clipping bounds (same as before)
|
||||
if (base_x + frameOffsetX < int(framePadding))
|
||||
clippingOffsetX = framePadding - (base_x + frameOffsetX);
|
||||
else if ((base_x + frameOffsetX + total_width) > static_cast<int>(screenWidth - framePadding))
|
||||
clippingOffsetX = (screenWidth - framePadding) - (base_x + frameOffsetX + total_width);
|
||||
|
||||
if (base_y + frameOffsetY < int(framePadding))
|
||||
clippingOffsetY = framePadding - (base_y + frameOffsetY);
|
||||
else if ((base_y + frameOffsetY + total_height) > static_cast<int>(screenHeight - framePadding))
|
||||
clippingOffsetY = (screenHeight - framePadding) - (base_y + frameOffsetY + total_height);
|
||||
|
||||
int _frameOffsetX = ult::limitedMemory ? std::max(0, frameOffsetX - (1280-448)) : frameOffsetX;
|
||||
|
||||
const int final_base_x = base_x + _frameOffsetX + clippingOffsetX;
|
||||
const int final_base_y = base_y + frameOffsetY + clippingOffsetY;
|
||||
|
||||
const tsl::Color bgColor = !isDragging ? settings.backgroundColor : settings.focusBackgroundColor;
|
||||
|
||||
const u64 curTick = armGetSystemTick();
|
||||
|
||||
// guard: ensure lastGameSeenTick is initialized to something reasonable
|
||||
if (lastGameSeenTick == 0) lastGameSeenTick = curTick;
|
||||
|
||||
// threshold in ns (100 ms)
|
||||
static constexpr u64 CHECK_NS = 2000000000ULL;
|
||||
|
||||
// Game detected
|
||||
if (gameStart && NxFps && NxFps->API >= 1 && (Resolutions_c[0] != '\0' && Resolutions2_c[0] != '\0')) {
|
||||
lastGameSeenTick = curTick;
|
||||
waitingForGame = true; // reset waiting state so next missing cycle shows "Checking..."
|
||||
renderer->drawRoundedRectSingleThreaded(final_base_x, final_base_y, total_width, total_height, 16, aWithOpacity(bgColor));
|
||||
|
||||
int xOffset = 10;
|
||||
int yOffset = 10;
|
||||
renderer->drawString("Depth", false, xOffset + final_base_x + 20, yOffset + final_base_y + 20, 20, settings.catColor);
|
||||
renderer->drawString(Resolutions_c, false, xOffset + final_base_x + 20, yOffset + final_base_y + 55, 18, settings.textColor);
|
||||
renderer->drawString("Viewport", false, xOffset + final_base_x + 180, yOffset + final_base_y + 20, 20, settings.catColor);
|
||||
renderer->drawString(Resolutions2_c, false, xOffset + final_base_x + 180, yOffset + final_base_y + 55, 18, settings.textColor);
|
||||
}
|
||||
// Game not detected
|
||||
else {
|
||||
renderer->drawRoundedRectSingleThreaded(final_base_x, final_base_y, total_width, total_height, 16, aWithOpacity(bgColor));
|
||||
|
||||
// Check elapsed time since last game detection
|
||||
u64 elapsed_ns = armTicksToNs(curTick - lastGameSeenTick);
|
||||
const bool under100ms = (elapsed_ns < CHECK_NS); // 100ms
|
||||
|
||||
std::string msg;
|
||||
if (under100ms && waitingForGame)
|
||||
msg = "Checking for game...";
|
||||
else {
|
||||
msg = "Game is not running\nor is incompatible.";
|
||||
waitingForGame = false;
|
||||
}
|
||||
|
||||
const auto [textWidth, textHeight] = renderer->getTextDimensions(msg, false, 20);
|
||||
const int text_x = final_base_x + (total_width - textWidth) / 2;
|
||||
const int text_y = final_base_y + (total_height) / 2;
|
||||
|
||||
renderer->drawString(msg, false, text_x, (under100ms && waitingForGame) ? text_y+textHeight/2 : text_y, 20, (under100ms && waitingForGame) ? 0xFFFF : 0xF00F);
|
||||
}
|
||||
});
|
||||
|
||||
tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("", "");
|
||||
rootFrame->setContent(Status);
|
||||
|
||||
return rootFrame;
|
||||
}
|
||||
|
||||
virtual void update() override {
|
||||
|
||||
if (gameStart && NxFps) {
|
||||
if (!resolutionLookup) {
|
||||
NxFps -> renderCalls[0].calls = 0xFFFF;
|
||||
resolutionLookup = 1;
|
||||
}
|
||||
else if (resolutionLookup == 1) {
|
||||
if ((NxFps -> renderCalls[0].calls) != 0xFFFF) resolutionLookup = 2;
|
||||
else return;
|
||||
}
|
||||
memcpy(&m_resolutionRenderCalls, &(NxFps -> renderCalls), sizeof(m_resolutionRenderCalls));
|
||||
memcpy(&m_resolutionViewportCalls, &(NxFps -> viewportCalls), sizeof(m_resolutionViewportCalls));
|
||||
qsort(m_resolutionRenderCalls, 8, sizeof(resolutionCalls), compare);
|
||||
qsort(m_resolutionViewportCalls, 8, sizeof(resolutionCalls), compare);
|
||||
snprintf(Resolutions_c, sizeof Resolutions_c,
|
||||
"%dx%d, %d\n"
|
||||
"%dx%d, %d\n"
|
||||
"%dx%d, %d\n"
|
||||
"%dx%d, %d\n"
|
||||
"%dx%d, %d\n"
|
||||
"%dx%d, %d\n"
|
||||
"%dx%d, %d\n"
|
||||
"%dx%d, %d",
|
||||
m_resolutionRenderCalls[0].width, m_resolutionRenderCalls[0].height, m_resolutionRenderCalls[0].calls,
|
||||
m_resolutionRenderCalls[1].width, m_resolutionRenderCalls[1].height, m_resolutionRenderCalls[1].calls,
|
||||
m_resolutionRenderCalls[2].width, m_resolutionRenderCalls[2].height, m_resolutionRenderCalls[2].calls,
|
||||
m_resolutionRenderCalls[3].width, m_resolutionRenderCalls[3].height, m_resolutionRenderCalls[3].calls,
|
||||
m_resolutionRenderCalls[4].width, m_resolutionRenderCalls[4].height, m_resolutionRenderCalls[4].calls,
|
||||
m_resolutionRenderCalls[5].width, m_resolutionRenderCalls[5].height, m_resolutionRenderCalls[5].calls,
|
||||
m_resolutionRenderCalls[6].width, m_resolutionRenderCalls[6].height, m_resolutionRenderCalls[6].calls,
|
||||
m_resolutionRenderCalls[7].width, m_resolutionRenderCalls[7].height, m_resolutionRenderCalls[7].calls
|
||||
);
|
||||
snprintf(Resolutions2_c, sizeof Resolutions2_c,
|
||||
"%dx%d, %d\n"
|
||||
"%dx%d, %d\n"
|
||||
"%dx%d, %d\n"
|
||||
"%dx%d, %d\n"
|
||||
"%dx%d, %d\n"
|
||||
"%dx%d, %d\n"
|
||||
"%dx%d, %d\n"
|
||||
"%dx%d, %d",
|
||||
m_resolutionViewportCalls[0].width, m_resolutionViewportCalls[0].height, m_resolutionViewportCalls[0].calls,
|
||||
m_resolutionViewportCalls[1].width, m_resolutionViewportCalls[1].height, m_resolutionViewportCalls[1].calls,
|
||||
m_resolutionViewportCalls[2].width, m_resolutionViewportCalls[2].height, m_resolutionViewportCalls[2].calls,
|
||||
m_resolutionViewportCalls[3].width, m_resolutionViewportCalls[3].height, m_resolutionViewportCalls[3].calls,
|
||||
m_resolutionViewportCalls[4].width, m_resolutionViewportCalls[4].height, m_resolutionViewportCalls[4].calls,
|
||||
m_resolutionViewportCalls[5].width, m_resolutionViewportCalls[5].height, m_resolutionViewportCalls[5].calls,
|
||||
m_resolutionViewportCalls[6].width, m_resolutionViewportCalls[6].height, m_resolutionViewportCalls[6].calls,
|
||||
m_resolutionViewportCalls[7].width, m_resolutionViewportCalls[7].height, m_resolutionViewportCalls[7].calls
|
||||
);
|
||||
}
|
||||
if (FPSavg < 254) {
|
||||
gameStart = true;
|
||||
}
|
||||
else {
|
||||
gameStart = false;
|
||||
resolutionLookup = false;
|
||||
}
|
||||
|
||||
if (!skipOnce) {
|
||||
if (runOnce) {
|
||||
isRendering = true;
|
||||
leventClear(&renderingStopEvent);
|
||||
runOnce = false;
|
||||
}
|
||||
} else {
|
||||
skipOnce = false;
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override {
|
||||
// Static variables to maintain drag state between function calls
|
||||
static bool oldTouchDetected = false;
|
||||
static bool oldMinusHeld = false;
|
||||
static bool oldPlusHeld = false;
|
||||
static HidTouchState initialTouchPos = {0};
|
||||
static int initialFrameOffsetX = 0;
|
||||
static int initialFrameOffsetY = 0;
|
||||
static constexpr int TOUCH_THRESHOLD = 8;
|
||||
static bool hasMoved = false;
|
||||
|
||||
// Better touch detection - check if coordinates are within reasonable screen bounds
|
||||
const bool currentTouchDetected = (touchPos.x > 0 && touchPos.y > 0 &&
|
||||
touchPos.x < screenWidth && touchPos.y < screenHeight);
|
||||
|
||||
static bool clearOnRelease = false;
|
||||
|
||||
if (clearOnRelease && !isRendering) {
|
||||
clearOnRelease = false;
|
||||
isRendering = true;
|
||||
leventClear(&renderingStopEvent);
|
||||
}
|
||||
|
||||
// Calculate overlay bounds
|
||||
// Cache bounds calculation
|
||||
static int cachedBaseX = 0;
|
||||
static int cachedBaseY = 0;
|
||||
static bool boundsNeedUpdate = true;
|
||||
|
||||
// Only recalculate bounds when needed
|
||||
if (boundsNeedUpdate) {
|
||||
cachedBaseX = 0;
|
||||
cachedBaseY = 0;
|
||||
boundsNeedUpdate = false;
|
||||
}
|
||||
|
||||
const int overlayX = cachedBaseX + frameOffsetX;
|
||||
const int overlayY = cachedBaseY + frameOffsetY;
|
||||
|
||||
// Overlay dimensions based on game state
|
||||
int overlayWidth, overlayHeight;
|
||||
overlayWidth = 360-20;
|
||||
overlayHeight = 200;
|
||||
|
||||
// Add padding to make touch detection more forgiving
|
||||
static constexpr int touchPadding = 4;
|
||||
const int touchableX = overlayX - touchPadding;
|
||||
const int touchableY = overlayY - touchPadding;
|
||||
const int touchableWidth = overlayWidth + (touchPadding * 2);
|
||||
const int touchableHeight = overlayHeight + (touchPadding * 2);
|
||||
|
||||
// Screen boundaries for clamping
|
||||
const int minX = -cachedBaseX + framePadding;
|
||||
const int maxX = screenWidth - overlayWidth - cachedBaseX - framePadding;
|
||||
const int minY = -cachedBaseY + framePadding;
|
||||
const int maxY = screenHeight - overlayHeight - cachedBaseY - framePadding;
|
||||
|
||||
const bool minusDragReady = buttonState.minusDragActive.load(std::memory_order_acquire);
|
||||
const bool plusDragReady = buttonState.plusDragActive.load(std::memory_order_acquire);
|
||||
|
||||
// Check button states
|
||||
const bool currentMinusHeld = (keysHeld & KEY_MINUS) && !(keysHeld & ~KEY_MINUS & ALL_KEYS_MASK) && minusDragReady;
|
||||
const bool currentPlusHeld = (keysHeld & KEY_PLUS) && !(keysHeld & ~KEY_PLUS & ALL_KEYS_MASK) && plusDragReady;
|
||||
|
||||
// Handle touch dragging
|
||||
if (currentTouchDetected && !isDragging) {
|
||||
const int touchX = touchPos.x;
|
||||
const int touchY = touchPos.y;
|
||||
|
||||
if (!oldTouchDetected) {
|
||||
// Touch just started - check if within overlay bounds
|
||||
if (touchX >= touchableX && touchX <= touchableX + touchableWidth &&
|
||||
touchY >= touchableY && touchY <= touchableY + touchableHeight) {
|
||||
|
||||
// Start touch dragging
|
||||
isDragging = true;
|
||||
triggerRumbleClick.store(true, std::memory_order_release);
|
||||
triggerOnSound.store(true, std::memory_order_release);
|
||||
hasMoved = false;
|
||||
initialTouchPos = touchPos;
|
||||
initialFrameOffsetX = frameOffsetX;
|
||||
initialFrameOffsetY = frameOffsetY;
|
||||
}
|
||||
}
|
||||
} else if (currentTouchDetected && isDragging && !currentMinusHeld && !currentPlusHeld) {
|
||||
// Continue touch dragging
|
||||
const int touchX = touchPos.x;
|
||||
const int touchY = touchPos.y;
|
||||
const int deltaX = touchX - initialTouchPos.x;
|
||||
const int deltaY = touchY - initialTouchPos.y;
|
||||
|
||||
// Check if we've moved enough to consider this a drag
|
||||
if (!hasMoved) {
|
||||
const int totalMovement = abs(deltaX) + abs(deltaY);
|
||||
if (totalMovement >= TOUCH_THRESHOLD) {
|
||||
hasMoved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasMoved) {
|
||||
// Update frame offsets with boundary checking
|
||||
const int newFrameOffsetX = std::max(minX, std::min(maxX, initialFrameOffsetX + deltaX));
|
||||
const int newFrameOffsetY = std::max(minY, std::min(maxY, initialFrameOffsetY + deltaY));
|
||||
|
||||
frameOffsetX = newFrameOffsetX;
|
||||
frameOffsetY = newFrameOffsetY;
|
||||
|
||||
if (ult::limitedMemory) {
|
||||
tsl::gfx::Renderer::get().setLayerPos(std::max(std::min((int)(frameOffsetX*1.5 + 0.5) - tsl::impl::currentUnderscanPixels.first, 1280-32 - tsl::impl::currentUnderscanPixels.first), 0), 0);
|
||||
}
|
||||
|
||||
boundsNeedUpdate = true;
|
||||
}
|
||||
} else if (!currentTouchDetected && oldTouchDetected && isDragging && !currentMinusHeld && !currentPlusHeld) {
|
||||
// Touch just released
|
||||
if (hasMoved) {
|
||||
// Save position when touch drag ends
|
||||
auto iniData = ult::getParsedDataFromIniFile(configIniPath);
|
||||
iniData["game_resolutions"]["frame_offset_x"] = std::to_string(frameOffsetX);
|
||||
iniData["game_resolutions"]["frame_offset_y"] = std::to_string(frameOffsetY);
|
||||
ult::saveIniFileData(configIniPath, iniData);
|
||||
}
|
||||
|
||||
// Reset touch drag state
|
||||
isDragging = false;
|
||||
hasMoved = false;
|
||||
clearOnRelease = true;
|
||||
triggerRumbleDoubleClick.store(true, std::memory_order_release);
|
||||
triggerOffSound.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
// Handle joystick dragging (MINUS + right joystick OR PLUS + left joystick)
|
||||
if ((currentMinusHeld || currentPlusHeld) && !isDragging) {
|
||||
// Start joystick dragging
|
||||
isDragging = true;
|
||||
triggerRumbleClick.store(true, std::memory_order_release);
|
||||
triggerOnSound.store(true, std::memory_order_release);
|
||||
} else if ((currentMinusHeld || currentPlusHeld) && isDragging) {
|
||||
// Continue joystick dragging
|
||||
static constexpr int JOYSTICK_DEADZONE = 20;
|
||||
|
||||
// Choose the appropriate joystick based on which button is held
|
||||
const HidAnalogStickState& activeJoystick = currentMinusHeld ? joyStickPosRight : joyStickPosLeft;
|
||||
|
||||
// Only move if joystick is outside deadzone
|
||||
if (abs(activeJoystick.x) > JOYSTICK_DEADZONE || abs(activeJoystick.y) > JOYSTICK_DEADZONE) {
|
||||
// Calculate joystick magnitude
|
||||
const float magnitude = sqrt((float)(activeJoystick.x * activeJoystick.x + activeJoystick.y * activeJoystick.y));
|
||||
const float normalizedMagnitude = magnitude / 32767.0f;
|
||||
|
||||
// Smooth curve for sensitivity
|
||||
static constexpr float baseSensitivity = 0.00008f;
|
||||
static constexpr float maxSensitivity = 0.0005f;
|
||||
|
||||
const float curveValue = pow(normalizedMagnitude, 8.0f);
|
||||
const float currentSensitivity = baseSensitivity + (maxSensitivity - baseSensitivity) * curveValue;
|
||||
|
||||
// Calculate movement delta with fractional accumulation
|
||||
static float accumulatedX = 0.0f;
|
||||
static float accumulatedY = 0.0f;
|
||||
|
||||
accumulatedX += (float)activeJoystick.x * currentSensitivity;
|
||||
accumulatedY += -(float)activeJoystick.y * currentSensitivity;
|
||||
|
||||
const int deltaX = (int)accumulatedX;
|
||||
const int deltaY = (int)accumulatedY;
|
||||
accumulatedX -= deltaX;
|
||||
accumulatedY -= deltaY;
|
||||
|
||||
// Update frame offsets with boundary checking
|
||||
const int newFrameOffsetX = std::max(minX, std::min(maxX, frameOffsetX + deltaX));
|
||||
const int newFrameOffsetY = std::max(minY, std::min(maxY, frameOffsetY + deltaY));
|
||||
|
||||
frameOffsetX = newFrameOffsetX;
|
||||
frameOffsetY = newFrameOffsetY;
|
||||
|
||||
if (ult::limitedMemory) {
|
||||
tsl::gfx::Renderer::get().setLayerPos(std::max(std::min((int)(frameOffsetX*1.5 + 0.5) - tsl::impl::currentUnderscanPixels.first, 1280-32 - tsl::impl::currentUnderscanPixels.first), 0), 0);
|
||||
}
|
||||
|
||||
boundsNeedUpdate = true;
|
||||
}
|
||||
} else if (((!currentMinusHeld && oldMinusHeld) || (!currentPlusHeld && oldPlusHeld)) && isDragging) {
|
||||
// Button just released - stop joystick dragging
|
||||
auto iniData = ult::getParsedDataFromIniFile(configIniPath);
|
||||
iniData["game_resolutions"]["frame_offset_x"] = std::to_string(frameOffsetX);
|
||||
iniData["game_resolutions"]["frame_offset_y"] = std::to_string(frameOffsetY);
|
||||
ult::saveIniFileData(configIniPath, iniData);
|
||||
isDragging = false;
|
||||
clearOnRelease = true;
|
||||
triggerRumbleDoubleClick.store(true, std::memory_order_release);
|
||||
triggerOffSound.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
// Update state for next frame
|
||||
oldTouchDetected = currentTouchDetected;
|
||||
oldMinusHeld = currentMinusHeld;
|
||||
oldPlusHeld = currentPlusHeld;
|
||||
|
||||
// Handle existing key input logic (but don't interfere with dragging)
|
||||
if (!isDragging) {
|
||||
if (isKeyComboPressed(keysHeld, keysDown)) {
|
||||
isRendering = false;
|
||||
leventSignal(&renderingStopEvent);
|
||||
runOnce = true;
|
||||
skipOnce = true;
|
||||
TeslaFPS = 60;
|
||||
lastSelectedItem = "Game Resolutions";
|
||||
lastMode = "";
|
||||
if (skipMain) {
|
||||
lastMode = "return";
|
||||
tsl::goBack();
|
||||
}
|
||||
else {
|
||||
tsl::setNextOverlay(filepath.c_str(), "--lastSelectedItem 'Game Resolutions'");
|
||||
tsl::Overlay::get()->close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if we handled the input (during dragging)
|
||||
return isDragging;
|
||||
}
|
||||
};
|
||||
35
Source/Horizon-OC-Monitor/source/pwm.c
Normal file
35
Source/Horizon-OC-Monitor/source/pwm.c
Normal file
@@ -0,0 +1,35 @@
|
||||
#define NX_SERVICE_ASSUME_NON_DOMAIN
|
||||
#include <switch.h>
|
||||
#include <service_guard.h>
|
||||
#include "pwm.h"
|
||||
|
||||
static Service g_pwmSrv;
|
||||
|
||||
NX_GENERATE_SERVICE_GUARD(pwm);
|
||||
|
||||
Result _pwmInitialize(void) {
|
||||
return smGetService(&g_pwmSrv, "pwm");
|
||||
}
|
||||
|
||||
void _pwmCleanup(void) {
|
||||
serviceClose(&g_pwmSrv);
|
||||
}
|
||||
|
||||
Service* pwmGetServiceSession(void) {
|
||||
return &g_pwmSrv;
|
||||
}
|
||||
|
||||
Result pwmOpenSession2(PwmChannelSession *out, u32 device_code) {
|
||||
return serviceDispatchIn(&g_pwmSrv, 2, device_code,
|
||||
.out_num_objects = 1,
|
||||
.out_objects = &out->s,
|
||||
);
|
||||
}
|
||||
|
||||
Result pwmChannelSessionGetDutyCycle(PwmChannelSession *c, double* out) {
|
||||
return serviceDispatchOut(&c->s, 7, *out);
|
||||
}
|
||||
|
||||
void pwmChannelSessionClose(PwmChannelSession *c) {
|
||||
serviceClose(&c->s);
|
||||
}
|
||||
43
Source/Horizon-OC-Monitor/source/rgltr_services.cpp
Normal file
43
Source/Horizon-OC-Monitor/source/rgltr_services.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
// rgltr_services.cpp (no changes needed here—just compile it once)
|
||||
#include <switch.h>
|
||||
#include "rgltr.h"
|
||||
#include "rgltr_services.h" // for extern Service g_rgltrSrv, etc.
|
||||
|
||||
// Global service handle
|
||||
Service g_rgltrSrv;
|
||||
|
||||
Result rgltrInitialize(void) {
|
||||
if (hosversionBefore(8, 0, 0)) {
|
||||
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
|
||||
}
|
||||
return smGetService(&g_rgltrSrv, "rgltr");
|
||||
}
|
||||
|
||||
void rgltrExit(void) {
|
||||
serviceClose(&g_rgltrSrv);
|
||||
}
|
||||
|
||||
Result rgltrOpenSession(RgltrSession* session_out, PowerDomainId module_id) {
|
||||
const u32 in = (u32)module_id;
|
||||
return serviceDispatchIn(
|
||||
&g_rgltrSrv,
|
||||
0,
|
||||
in,
|
||||
.out_num_objects = 1,
|
||||
.out_objects = &session_out->s
|
||||
);
|
||||
}
|
||||
|
||||
Result rgltrGetVoltage(RgltrSession* session, u32* out_volt) {
|
||||
// Service returns µV (microvolts) in a local u32:
|
||||
u32 temp = 0;
|
||||
Result rc = serviceDispatchOut(&session->s, 4, temp);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out_volt = temp;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
void rgltrCloseSession(RgltrSession* session) {
|
||||
serviceClose(&session->s);
|
||||
}
|
||||
128
Source/Horizon-OC-Monitor/source/sysclk_ipc.c
Normal file
128
Source/Horizon-OC-Monitor/source/sysclk_ipc.c
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* --------------------------------------------------------------------------
|
||||
* "THE BEER-WARE LICENSE" (Revision 42):
|
||||
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
|
||||
* wrote this file. As long as you retain this notice you can do whatever you
|
||||
* want with this stuff. If you meet any of us some day, and you think this
|
||||
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#define NX_SERVICE_ASSUME_NON_DOMAIN
|
||||
#include <sysclk/client/ipc.h>
|
||||
#include <switch.h>
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
static Service g_sysclkSrv;
|
||||
static atomic_size_t g_refCnt;
|
||||
|
||||
bool sysclkIpcRunning()
|
||||
{
|
||||
Handle handle;
|
||||
const bool running = R_FAILED(smRegisterService(&handle, smEncodeName(SYSCLK_IPC_SERVICE_NAME), false, 1));
|
||||
|
||||
if (!running)
|
||||
{
|
||||
smUnregisterService(smEncodeName(SYSCLK_IPC_SERVICE_NAME));
|
||||
}
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
Result sysclkIpcInitialize(void)
|
||||
{
|
||||
Result rc = 0;
|
||||
|
||||
g_refCnt++;
|
||||
|
||||
if (serviceIsActive(&g_sysclkSrv))
|
||||
return 0;
|
||||
|
||||
rc = smGetService(&g_sysclkSrv, SYSCLK_IPC_SERVICE_NAME);
|
||||
|
||||
if (R_FAILED(rc)) sysclkIpcExit();
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void sysclkIpcExit(void)
|
||||
{
|
||||
if (--g_refCnt == 0)
|
||||
{
|
||||
serviceClose(&g_sysclkSrv);
|
||||
}
|
||||
}
|
||||
|
||||
Result sysclkIpcGetAPIVersion(u32* out_ver)
|
||||
{
|
||||
return serviceDispatchOut(&g_sysclkSrv, SysClkIpcCmd_GetApiVersion, *out_ver);
|
||||
}
|
||||
|
||||
Result sysclkIpcGetVersionString(char* out, size_t len)
|
||||
{
|
||||
return serviceDispatch(&g_sysclkSrv, SysClkIpcCmd_GetVersionString,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
|
||||
.buffers = {{out, len}},
|
||||
);
|
||||
}
|
||||
|
||||
Result sysclkIpcGetCurrentContext(SysClkContext* out_context)
|
||||
{
|
||||
return serviceDispatchOut(&g_sysclkSrv, SysClkIpcCmd_GetCurrentContext, *out_context);
|
||||
}
|
||||
|
||||
Result sysclkIpcGetProfileCount(u64 tid, u8* out_count)
|
||||
{
|
||||
return serviceDispatchInOut(&g_sysclkSrv, SysClkIpcCmd_GetProfileCount, tid, *out_count);
|
||||
}
|
||||
|
||||
Result sysclkIpcSetEnabled(bool enabled)
|
||||
{
|
||||
u8 enabledRaw = (u8)enabled;
|
||||
return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetEnabled, enabledRaw);
|
||||
}
|
||||
|
||||
Result sysclkIpcSetOverride(SysClkModule module, u32 hz)
|
||||
{
|
||||
SysClkIpc_SetOverride_Args args = {
|
||||
.module = module,
|
||||
.hz = hz
|
||||
};
|
||||
return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetOverride, args);
|
||||
}
|
||||
|
||||
Result sysclkIpcGetProfiles(u64 tid, SysClkTitleProfileList* out_profiles)
|
||||
{
|
||||
return serviceDispatchInOut(&g_sysclkSrv, SysClkIpcCmd_GetProfiles, tid, *out_profiles);
|
||||
}
|
||||
|
||||
Result sysclkIpcSetProfiles(u64 tid, SysClkTitleProfileList* profiles)
|
||||
{
|
||||
SysClkIpc_SetProfiles_Args args;
|
||||
args.tid = tid;
|
||||
memcpy(&args.profiles, profiles, sizeof(SysClkTitleProfileList));
|
||||
return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetProfiles, args);
|
||||
}
|
||||
|
||||
Result sysclkIpcGetConfigValues(SysClkConfigValueList* out_configValues)
|
||||
{
|
||||
return serviceDispatchOut(&g_sysclkSrv, SysClkIpcCmd_GetConfigValues, *out_configValues);
|
||||
}
|
||||
|
||||
Result sysclkIpcSetConfigValues(SysClkConfigValueList* configValues)
|
||||
{
|
||||
return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetConfigValues, *configValues);
|
||||
}
|
||||
|
||||
Result sysclkIpcGetFreqList(SysClkModule module, u32* list, u32 maxCount, u32* outCount)
|
||||
{
|
||||
SysClkIpc_GetFreqList_Args args = {
|
||||
.module = module,
|
||||
.maxCount = maxCount
|
||||
};
|
||||
return serviceDispatchInOut(&g_sysclkSrv, SysClkIpcCmd_GetFreqList, args, *outCount,
|
||||
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out },
|
||||
.buffers = {{list, maxCount * sizeof(u32)}},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user