From 3b2de89d2ff07aead748093a5e1513ae4f0eca3c Mon Sep 17 00:00:00 2001 From: souldbminersmwc Date: Wed, 1 Apr 2026 16:08:42 -0400 Subject: [PATCH] fix submodules again --- .gitmodules | 4 +- .../libultrahand/libtesla/include/tesla.hpp | 360 ++++++++++-------- .../libultrahand/libtesla/source/tesla.cpp | 11 +- .../libultra/include/debug_funcs.hpp | 4 +- .../libultrahand/libultra/include/haptics.hpp | 1 + .../libultra/source/debug_funcs.cpp | 4 +- .../libultrahand/libultra/source/haptics.cpp | 8 + hoc-clk/libultrahand | 1 - 8 files changed, 229 insertions(+), 164 deletions(-) delete mode 160000 hoc-clk/libultrahand diff --git a/.gitmodules b/.gitmodules index 6640397b..aae37b6a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,7 +6,7 @@ path = Source/Horizon-OC-Monitor/lib/libultrahand url = https://github.com/ppkantorski/libultrahand branch = main -[submodule "hoc-clk/libultrahand"] - path = hoc-clk/libultrahand +[submodule "Source/hoc-clk/overlay/lib/libultrahand"] + path = Source/hoc-clk/overlay/lib/libultrahand url = https://github.com/ppkantorski/libultrahand branch = main diff --git a/Source/hoc-clk/overlay/lib/libultrahand/libtesla/include/tesla.hpp b/Source/hoc-clk/overlay/lib/libultrahand/libtesla/include/tesla.hpp index 155bde55..710f62b9 100644 --- a/Source/hoc-clk/overlay/lib/libultrahand/libtesla/include/tesla.hpp +++ b/Source/hoc-clk/overlay/lib/libultrahand/libtesla/include/tesla.hpp @@ -159,6 +159,9 @@ inline u32 offsetWidthVar = 112; inline std::string lastOverlayFilename; inline std::string lastOverlayMode; +inline std::string returnOverlayPath{ult::OVERLAY_PATH + "ovlmenu.ovl"}; +inline bool skipRumbleDoubleClick{false}; + inline std::mutex jumpItemMutex; inline std::string jumpItemName; inline std::string jumpItemValue; @@ -246,6 +249,11 @@ namespace tsl { inline const std::vector s_dividerSpecialChars = {ult::DIVIDER_SYMBOL}; inline const std::vector s_footerSpecialChars = {"\uE0E1","\uE0E0","\uE0ED","\uE0EE","\uE0E5"}; + // Windowed-mode notification Y offset in touch space. + // Set to (g_win_pos_y * 2/3) by windowed overlay; 0 in normal mode. + // X is handled by ult::layerEdge which already exists for this purpose. + inline s32 layerEdgeY = 0; + // Booleans inline std::atomic clearGlyphCacheNow(false); @@ -395,10 +403,10 @@ namespace tsl { extern Color onTextColor; extern Color offTextColor; - #if IS_LAUNCHER_DIRECTIVE + //#if IS_LAUNCHER_DIRECTIVE extern Color dynamicLogoRGB1; extern Color dynamicLogoRGB2; - #endif + //#endif extern bool invertBGClickColor; @@ -500,7 +508,7 @@ namespace tsl { namespace elm { class Element; } void shiftItemFocus(elm::Element* element); // forward declare - + namespace impl { /** @@ -1879,38 +1887,33 @@ namespace tsl { } } - static void processRoundedRectChunk(Renderer* self, const s32 x, const s32 y, const s32 w, const s32 h, - const s32 radius, const Color& color, - const s32 startRow, const s32 endRow) { + // --- Optimized rounded rectangle chunk processor --- + static void processRoundedRectChunk(Renderer* self, + const s32 x, const s32 y, + const s32 w, const s32 h, + const s32 radius, + const Color& color, + const s32 startRow, const s32 endRow) + { if (radius <= 0) return; - + const s32 x_end = x + w; const s32 y_end = y + h; - + const s32 clip_x = std::max(0, x); const s32 clip_x_end = std::min(cfg::FramebufferWidth, x_end); - + const s32 left_arc_end = x + radius - 1; const s32 right_arc_start = x_end - radius; const s32 top_arc_end = y + radius - 1; const s32 bottom_arc_start = y_end - radius; - - const int cx2_left = 2 * (x + radius); - const int cx2_right = 2 * (x_end - radius); - const int cy2_top = 2 * (y + radius); - const int cy2_bottom = 2 * (y_end - radius); - + const long long r2_scaled = 4LL * radius * radius; const long long reject_threshold = (2LL*radius + 2)*(2LL*radius + 2); - + const u8 base_a = color.a; - - // Pre-compute sample offsets (constant per corner) - const int sx_left = ((x + radius) & 1) ? -1 : 1; - const int sx_right = ((x_end - radius) & 1) ? -1 : 1; - const int sy_top = ((y + radius) & 1) ? -1 : 1; - const int sy_bottom = ((y_end - radius) & 1) ? -1 : 1; - + + // Precompute batch arrays alignas(64) u8 redArray[512], greenArray[512], blueArray[512], alphaArray[512]; const uint8x16_t rv = vdupq_n_u8(color.r); const uint8x16_t gv = vdupq_n_u8(color.g); @@ -1922,14 +1925,15 @@ namespace tsl { vst1q_u8(blueArray + i, bv); vst1q_u8(alphaArray + i, av); } - + for (s32 yc = startRow; yc < endRow; ++yc) { if (yc < y || yc >= y_end) continue; - + const bool is_top = (yc <= top_arc_end); const bool in_arc_rows = is_top || (yc >= bottom_arc_start); - + if (!in_arc_rows) { + // Full-width batch for middle flat area s32 xs = std::max(clip_x, x); s32 xe = std::min(clip_x_end, x_end); for (s32 xp = xs; xp < xe; xp += 512) @@ -1937,38 +1941,37 @@ namespace tsl { std::min(512, xe - xp)); continue; } - - const int cy2 = is_top ? cy2_top : cy2_bottom; - const int py2 = 2 * yc + 1; - const int sy = is_top ? sy_top : sy_bottom; - - // Quick row reject + + const int cy2 = is_top ? 2*(y + radius) : 2*(y_end - radius); + const int py2 = 2*yc + 1; const long long dy = py2 - cy2; - if (dy * dy > reject_threshold) continue; - - const s32 xe = std::min(clip_x_end, x_end); + if (dy*dy > reject_threshold) continue; + s32 xp = std::max(clip_x, x); - - // Left arc + const s32 xe = std::min(clip_x_end, x_end); + + // Left corner arc for (; xp <= left_arc_end && xp < xe; ++xp) { - sampleAndBlendArcPixel(self, xp, yc, 2*xp + 1, cx2_left, sx_left, - py2, cy2, sy, r2_scaled, color, base_a); + sampleAndBlendArcPixel(self, xp, yc, + 2*xp + 1, 2*(x + radius), 1, + py2, cy2, 1, + r2_scaled, color, base_a); } - - // Middle flat + + // Middle flat area s32 mid_start = std::max(xp, left_arc_end + 1); s32 mid_end = std::min(xe, right_arc_start); - if (mid_start < mid_end) { - for (s32 bx = mid_start; bx < mid_end; bx += 512) - self->setPixelBlendDstBatch(bx, yc, redArray, greenArray, blueArray, alphaArray, - std::min(512, mid_end - bx)); - } - - // Right arc + for (s32 bx = mid_start; bx < mid_end; bx += 512) + self->setPixelBlendDstBatch(bx, yc, redArray, greenArray, blueArray, alphaArray, + std::min(512, mid_end - bx)); + + // Right corner arc xp = std::max(xp, right_arc_start); for (; xp < xe; ++xp) { - sampleAndBlendArcPixel(self, xp, yc, 2*xp + 1, cx2_right, sx_right, - py2, cy2, sy, r2_scaled, color, base_a); + sampleAndBlendArcPixel(self, xp, yc, + 2*xp + 1, 2*(x_end - radius), 1, + py2, cy2, 1, + r2_scaled, color, base_a); } } } @@ -2186,120 +2189,136 @@ namespace tsl { // RGBA4444 processing - no expansion needed const uint8x16_t mask_low = vdupq_n_u8(0x0F); - inline void processBMPChunk(const u32 x, const u32 y, const s32 imageW, const u8 *preprocessedData, - const s32 startRow, const s32 endRow, const u8 globalAlphaLimit, - const bool useBarrier = true, const bool preserveAlpha = false) { + inline void processBMPChunk(const u32 x, const u32 y, const s32 imageW, const u8* preprocessedData, + const s32 startRow, const s32 endRow, const u8 globalAlphaLimit, + const bool useBarrier = true, const bool preserveAlpha = false) + { const s32 bytesPerRow = imageW * 2; - const s32 endX16 = imageW & ~15; + const s32 endX16 = imageW & ~15; // multiple of 16 + const uint8x16_t mask_low = vdupq_n_u8(0x0F); const uint8x16_t alpha_limit_vec = vdupq_n_u8(globalAlphaLimit); - Color* const framebuffer = static_cast(this->getCurrentFramebuffer()); + Color* const framebuffer = static_cast(Renderer::getCurrentFramebuffer()); - const bool hasScissor = !this->m_scissoringStack.empty(); - const auto scissor = hasScissor ? this->m_scissoringStack.top() : ScissoringConfig{}; + const bool hasScissor = !Renderer::m_scissoringStack.empty(); + const auto scissor = hasScissor ? Renderer::m_scissoringStack.top() : ScissoringConfig{}; + + // Precompute Y offsets + std::vector yParts(endRow - startRow); + for (s32 y1 = startRow; y1 < endRow; ++y1) { + const u32 baseY = y + y1; + yParts[y1 - startRow] = ((((baseY & 127) >> 4) + ((baseY >> 7) * offsetWidthVar)) << 9) + + ((baseY & 8) << 5) + ((baseY & 6) << 4) + ((baseY & 1) << 3); + } for (s32 y1 = startRow; y1 < endRow; ++y1) { const u32 baseY = y + y1; + if (hasScissor && (baseY < scissor.y || baseY >= scissor.y_max)) [[unlikely]] continue; - if (hasScissor && (baseY < scissor.y || baseY >= scissor.y_max)) [[unlikely]] - continue; - - const u32 yPart = ((((baseY & 127) >> 4) + ((baseY >> 7) * offsetWidthVar)) << 9) - + ((baseY & 8) << 5) + ((baseY & 6) << 4) + ((baseY & 1) << 3); - - const u8 *rowPtr = preprocessedData + (y1 * bytesPerRow); + const u32 yPart = yParts[y1 - startRow]; + const u8* rowPtr = preprocessedData + (y1 * bytesPerRow); s32 x1 = 0; + // --- Vectorized 16-pixel loop --- for (; x1 < endX16; x1 += 16) { const u8* ptr = rowPtr + (x1 << 1); - uint8x16x2_t packed = vld2q_u8(ptr); - uint8x16_t high1 = vshrq_n_u8(packed.val[0], 4); - uint8x16_t low1 = vandq_u8(packed.val[0], mask_low); - uint8x16_t high2 = vshrq_n_u8(packed.val[1], 4); - uint8x16_t low2 = vminq_u8(vandq_u8(packed.val[1], mask_low), alpha_limit_vec); - alignas(16) u8 red_vals[16], green_vals[16], blue_vals[16], alpha_vals[16]; - vst1q_u8(red_vals, high1); - vst1q_u8(green_vals, low1); - vst1q_u8(blue_vals, high2); - vst1q_u8(alpha_vals, low2); + uint8x16_t red = vshrq_n_u8(packed.val[0], 4); + uint8x16_t green = vandq_u8(packed.val[0], mask_low); + uint8x16_t blue = vshrq_n_u8(packed.val[1], 4); + uint8x16_t alpha = vminq_u8(vandq_u8(packed.val[1], mask_low), alpha_limit_vec); + + alignas(16) u8 r[16], g[16], b[16], a[16]; + vst1q_u8(r, red); + vst1q_u8(g, green); + vst1q_u8(b, blue); + vst1q_u8(a, alpha); const u32 baseX = x + x1; - for (int i = 0; i < 16; ++i) { - const u8 a = alpha_vals[i]; - if (a == 0) [[unlikely]] continue; + if (a[i] == 0) continue; + const u32 px = baseX + i; - if (hasScissor && (px < scissor.x || px >= scissor.x_max)) [[unlikely]] continue; - const u32 offset = yPart + ((px >> 5) << 12) - + ((px & 16) << 3) + ((px & 8) << 1) + (px & 7); - const Color src = framebuffer[offset]; - framebuffer[offset] = { - blendColor(src.r, red_vals[i], a), - blendColor(src.g, green_vals[i], a), - blendColor(src.b, blue_vals[i], a), - static_cast(preserveAlpha ? src.a : (a + ((src.a * (0xF - a)) >> 4))) - }; + if (hasScissor && (px < scissor.x || px >= scissor.x_max)) continue; + + const u32 offset = yPart + ((px >> 5) << 12) + ((px & 16) << 3) + ((px & 8) << 1) + (px & 7); + Color& dst = framebuffer[offset]; + + dst.r = blendColor(dst.r, r[i], a[i]); + dst.g = blendColor(dst.g, g[i], a[i]); + dst.b = blendColor(dst.b, b[i], a[i]); + if (!preserveAlpha) + dst.a = static_cast(a[i] + ((dst.a * (0xF - a[i])) >> 4)); } } + // --- Scalar leftover pixels --- for (; x1 < imageW; ++x1) { const u8 p1 = rowPtr[x1 << 1]; const u8 p2 = rowPtr[(x1 << 1) + 1]; - const u8 alpha = std::min(static_cast(p2 & 0x0F), globalAlphaLimit); - if (alpha == 0) [[unlikely]] continue; + const u8 alphaVal = std::min(p2 & 0x0F, globalAlphaLimit); + if (alphaVal == 0) continue; + const u32 px = x + x1; - if (hasScissor && (px < scissor.x || px >= scissor.x_max)) [[unlikely]] continue; - const u32 offset = yPart + ((px >> 5) << 12) - + ((px & 16) << 3) + ((px & 8) << 1) + (px & 7); - const Color bg = framebuffer[offset]; - framebuffer[offset] = { - blendColor(bg.r, static_cast(p1 >> 4), alpha), - blendColor(bg.g, static_cast(p1 & 0x0F), alpha), - blendColor(bg.b, static_cast(p2 >> 4), alpha), - static_cast(preserveAlpha ? bg.a : (alpha + ((bg.a * (0xF - alpha)) >> 4))) - }; + if (hasScissor && (px < scissor.x || px >= scissor.x_max)) continue; + + const u32 offset = yPart + ((px >> 5) << 12) + ((px & 16) << 3) + ((px & 8) << 1) + (px & 7); + Color& dst = framebuffer[offset]; + + dst.r = dst.r + (((p1 >> 4) - dst.r) * alphaVal >> 4); + dst.g = dst.g + (((p1 & 0x0F) - dst.g) * alphaVal >> 4); + dst.b = dst.b + (((p2 >> 4) - dst.b) * alphaVal >> 4); + if (!preserveAlpha) + dst.a = static_cast(alphaVal + ((dst.a * (0xF - alphaVal)) >> 4)); } } - if (useBarrier) - ult::inPlotBarrier.arrive_and_wait(); + if (useBarrier) ult::inPlotBarrier.arrive_and_wait(); } - inline void drawBitmapRGBA4444(const u32 x, const u32 y, const u32 imageW, const u32 imageH, - const u8 *preprocessedData, float opacity = 1.0f, bool preserveAlpha = false) { + // --- Draw bitmap RGBA4444 --- + inline void drawBitmapRGBA4444(const u32 x, const u32 y, const u32 imageW, const u32 imageH, + const u8* preprocessedData, float opacity = 1.0f, bool preserveAlpha = false) + { const u8 globalAlphaLimit = static_cast(0xF * opacity); - + + // Small width -> single-threaded if (imageW < 448) { processBMPChunk(x, y, imageW, preprocessedData, 0, imageH, globalAlphaLimit, false, preserveAlpha); return; } - - for (unsigned i = 0; i < ult::numThreads; ++i) { - const u32 startRow = i * ult::bmpChunkSize; - const u32 endRow = std::min(startRow + ult::bmpChunkSize, imageH); - ult::renderThreads[i] = std::thread([this, x, y, imageW, preprocessedData, startRow, endRow, globalAlphaLimit, preserveAlpha](){ + + // Multi-threaded rendering + const u32 numThreads = ult::numThreads; + const u32 chunkSize = (imageH + numThreads - 1) / numThreads; + + for (u32 t = 0; t < numThreads; ++t) { + const u32 startRow = t * chunkSize; + const u32 endRow = std::min(startRow + chunkSize, imageH); + + ult::renderThreads[t] = std::thread([=, this]() { processBMPChunk(x, y, imageW, preprocessedData, startRow, endRow, globalAlphaLimit, true, preserveAlpha); }); } - for (auto& t : ult::renderThreads) t.join(); + + for (auto& th : ult::renderThreads) th.join(); } + // --- Draw wallpaper --- inline void drawWallpaper() { - if (!ult::expandedMemory || ult::refreshWallpaper.load(std::memory_order_acquire)) { - return; - } - + if (!ult::expandedMemory || ult::refreshWallpaper.load(std::memory_order_acquire)) return; + ult::inPlot.store(true, std::memory_order_release); - - if (!ult::wallpaperData.empty() && - !ult::refreshWallpaper.load(std::memory_order_acquire) && - ult::correctFrameSize) { - drawBitmapRGBA4444(0, 0, cfg::FramebufferWidth, cfg::FramebufferHeight, - ult::wallpaperData.data(), Renderer::s_opacity, true); + + if (!ult::wallpaperData.empty() && + !ult::refreshWallpaper.load(std::memory_order_acquire) && + ult::correctFrameSize) + { + drawBitmapRGBA4444(0, 0, cfg::FramebufferWidth, cfg::FramebufferHeight, + ult::wallpaperData.data(), Renderer::s_opacity, true); } - + ult::inPlot.store(false, std::memory_order_release); } @@ -2827,6 +2846,13 @@ namespace tsl { cfg::LayerHeight += cfg::ScreenHeight/720. * verticalUnderscanPixels; } else if (ult::correctFrameSize) { cfg::LayerWidth += horizontalUnderscanPixels; + } else if (horizontalUnderscanPixels > 0) { + // General case: any non-standard FB size (e.g. windowed GB). + // Scale the correction proportionally to the fraction of the + // full 1280-logical-space width this layer occupies. + cfg::LayerWidth += static_cast( + horizontalUnderscanPixels * + (float(cfg::FramebufferWidth) / float(cfg::LayerMaxWidth)) + 0.5f); } // Update position if using right alignment @@ -3064,30 +3090,35 @@ namespace tsl { // Optimized glyph rendering inline void renderGlyph(std::shared_ptr glyph, float x, float y, const Color& color, bool skipAlphaLimit = false) { if (!glyph->glyphBmp || color.a == 0) [[unlikely]] return; - + const s32 xPos = static_cast(x + glyph->bounds[0]); const s32 yPos = static_cast(y + glyph->bounds[1]); - + + const s32 glyphWidth = glyph->width; + const s32 glyphHeight = glyph->height; + + // Clipping if (xPos >= cfg::FramebufferWidth || yPos >= cfg::FramebufferHeight || - xPos + glyph->width <= 0 || yPos + glyph->height <= 0) [[unlikely]] return; - + xPos + glyphWidth <= 0 || yPos + glyphHeight <= 0) [[unlikely]] return; + const s32 startX = std::max(0, -xPos); const s32 startY = std::max(0, -yPos); - const s32 endX = std::min(glyph->width, static_cast(cfg::FramebufferWidth) - xPos); - const s32 endY = std::min(glyph->height, static_cast(cfg::FramebufferHeight) - yPos); + const s32 endX = std::min(glyphWidth, static_cast(cfg::FramebufferWidth) - xPos); + const s32 endY = std::min(glyphHeight, static_cast(cfg::FramebufferHeight) - yPos); + const u8 alphaLimit = skipAlphaLimit ? color.a : static_cast(0xF * Renderer::s_opacity); - const uint8_t* bmpPtr = glyph->glyphBmp + startY * glyph->width; - - for (s32 bmpY = startY; bmpY < endY; ++bmpY, bmpPtr += glyph->width) { + const uint8_t* bmpPtr = glyph->glyphBmp + startY * glyphWidth; + + for (s32 bmpY = startY; bmpY < endY; ++bmpY, bmpPtr += glyphWidth) { const s32 pixelY = yPos + bmpY; - + for (s32 bmpX = startX; bmpX < endX; ++bmpX) { u8 alpha = bmpPtr[bmpX] >> 4; if (alpha == 0) [[unlikely]] continue; - + alpha = (alpha < alphaLimit) ? alpha : alphaLimit; const s32 pixelX = xPos + bmpX; - + if (alpha == 0xF) [[likely]] { this->setPixel(pixelX, pixelY, color); } else { @@ -3120,6 +3151,16 @@ namespace tsl { screenshotsAreForceDisabled.store(true, std::memory_order_release); } + /** + * @brief Get the current framebuffer address + * + * @return Framebuffer address + */ + inline void* getCurrentFramebuffer() { + return this->m_currentFramebuffer; + } + + private: Renderer() {} @@ -3147,15 +3188,6 @@ namespace tsl { - /** - * @brief Get the current framebuffer address - * - * @return Framebuffer address - */ - inline void* getCurrentFramebuffer() { - return this->m_currentFramebuffer; - } - /** * @brief Get the next framebuffer address * @@ -3265,6 +3297,10 @@ namespace tsl { cfg::LayerHeight += cfg::ScreenHeight/720. *verticalUnderscanPixels; else if (ult::correctFrameSize) cfg::LayerWidth += horizontalUnderscanPixels; + else if (horizontalUnderscanPixels > 0) + cfg::LayerWidth += static_cast( + horizontalUnderscanPixels * + (float(cfg::FramebufferWidth) / float(cfg::LayerMaxWidth)) + 0.5f); if (this->m_initialized) return; @@ -4072,7 +4108,7 @@ namespace tsl { }; - #if IS_LAUNCHER_DIRECTIVE + //#if IS_LAUNCHER_DIRECTIVE // Simple utility function to draw the dynamic "Ultra" part of the logo static s32 drawDynamicUltraText(gfx::Renderer* renderer, s32 startX, s32 y, u32 fontSize, const tsl::Color& staticColor, bool useNotificationMethod = false) { @@ -4148,7 +4184,7 @@ namespace tsl { return totalWidth; } - #endif + //#endif /** * @brief The base frame which can contain another view @@ -6714,6 +6750,7 @@ namespace tsl { m_clickAnimationProgress = 0; // Only trigger normal click if we weren't in a hold if (!wasHolding) { + tsl::shiftItemFocus(this); return onClick(determineKeyOnTouchRelease()); } } @@ -8077,10 +8114,10 @@ namespace tsl { (this->m_focused && ult::useSelectionValue) ? selectedValueTextColor : onTextColor); if (m_icon[0] != '\0') - renderer->drawString(this->m_icon, false, this->getX()+42, this->getY() + 50+2+2, 30, tsl::style::color::ColorText); + renderer->drawString(this->m_icon, false, this->getX()+42, this->getY() + 50+2+2, 30, ((!this->m_focused || !ult::useSelectionText) ? defaultTextColor : selectedTextColor)); } else { if (m_icon[0] != '\0') - renderer->drawString(this->m_icon, false, this->getX()+42, this->getY() + 50+2+2, 30, tsl::style::color::ColorText); + renderer->drawString(this->m_icon, false, this->getX()+42, this->getY() + 50+2+2, 30, ((!this->m_focused || !ult::useSelectionText) ? defaultTextColor : selectedTextColor)); } if (m_lastBottomBound != this->getTopBound()) @@ -11674,7 +11711,7 @@ namespace tsl { ult::FALSE_STR ); tsl::setNextOverlay( - ult::OVERLAY_PATH + "ovlmenu.ovl" + returnOverlayPath ); tsl::Overlay::get()->close(); break; @@ -11865,7 +11902,7 @@ namespace tsl { ult::TRUE_STR ); - tsl::setNextOverlay(ult::OVERLAY_PATH + "ovlmenu.ovl", "--direct --comboReturn"); + tsl::setNextOverlay(returnOverlayPath, "--direct --comboReturn"); fireLaunch(); return; } @@ -11923,7 +11960,7 @@ namespace tsl { } #else if (idx == WaiterObject_HomeButton || idx == WaiterObject_PowerButton) { // Changed condition to exclude capture button - if (shData->overlayOpen) { + if (shData->overlayOpen && !disableHiding) { tsl::Overlay::get()->hide(); shData->overlayOpen = false; } @@ -12349,6 +12386,19 @@ namespace tsl { } } + // Detect -returning: overlay was launched as a return from a sub-mode + // (e.g. windowed → normal). Uses exit feedback instead of enter feedback. + // Only needed in the non-STATUS_MONITOR, non-LAUNCHER path below. + #if !IS_STATUS_MONITOR_DIRECTIVE && !IS_LAUNCHER_DIRECTIVE + bool isReturningLaunch = false; + for (u8 arg = 1; arg < argc; arg++) { + if (strcmp(argv[arg], "-returning") == 0) { + isReturningLaunch = true; + break; + } + } + #endif + bool skipCombo = false; #if IS_LAUNCHER_DIRECTIVE bool comboReturn = false; @@ -12431,7 +12481,7 @@ namespace tsl { Thread backgroundFeedbackThread; threadCreate(&backgroundFeedbackThread, impl::backgroundFeedbackPoller, nullptr, nullptr, 0x1000, 0x2c, -2); threadStart(&backgroundFeedbackThread); - + Thread backgroundEventThread; threadCreate(&backgroundEventThread, impl::backgroundEventPoller, &shData, nullptr, 0x2000, 0x2c, -2); threadStart(&backgroundEventThread); @@ -12598,7 +12648,7 @@ namespace tsl { launchComboHasTriggered.store(true, std::memory_order_release); // for isolating sound effect if (usingPackageLauncher || directMode) { - tsl::setNextOverlay(ult::OVERLAY_PATH + "ovlmenu.ovl"); + tsl::setNextOverlay(returnOverlayPath); } hlp::requestForeground(false); @@ -12670,7 +12720,11 @@ namespace tsl { triggerEnterFeedback(); } #else - triggerEnterFeedback(); + if (isReturningLaunch) { + triggerExitFeedback(); + } else { + triggerEnterFeedback(); + } #endif } #endif @@ -12745,7 +12799,7 @@ namespace tsl { if (directMode && !launchComboHasTriggered.load(std::memory_order_acquire)) { if (!disableSound.load(std::memory_order_acquire) && ult::useSoundEffects) ult::Audio::playExitSound(); - if (ult::useHapticFeedback) { + if (ult::useHapticFeedback && !skipRumbleDoubleClick) { ult::rumbleDoubleClickStandalone(); } } diff --git a/Source/hoc-clk/overlay/lib/libultrahand/libtesla/source/tesla.cpp b/Source/hoc-clk/overlay/lib/libultrahand/libtesla/source/tesla.cpp index cc6236d9..133d54de 100644 --- a/Source/hoc-clk/overlay/lib/libultrahand/libtesla/source/tesla.cpp +++ b/Source/hoc-clk/overlay/lib/libultrahand/libtesla/source/tesla.cpp @@ -141,10 +141,10 @@ Color ultPackageVersionTextColor; Color onTextColor; Color offTextColor; -#if IS_LAUNCHER_DIRECTIVE +//#if IS_LAUNCHER_DIRECTIVE Color dynamicLogoRGB1; Color dynamicLogoRGB2; -#endif +//#endif bool invertBGClickColor = false; @@ -329,10 +329,10 @@ void initializeThemeVars() { logoColor1 = getColor("logo_color_1"); logoColor2 = getColor("logo_color_2"); - #if IS_LAUNCHER_DIRECTIVE + //#if IS_LAUNCHER_DIRECTIVE dynamicLogoRGB1 = getColor("dynamic_logo_color_1"); dynamicLogoRGB2 = getColor("dynamic_logo_color_2"); - #endif + //#endif defaultBackgroundAlpha = getAlpha("bg_alpha"); defaultBackgroundColor = getColor("bg_color", defaultBackgroundAlpha); @@ -985,6 +985,9 @@ bool NotificationPrompt::hasActiveFile(std::string_view fname) const { } int NotificationPrompt::findHitSlot_NoLock(s32 tx, s32 ty) const { + // In windowed mode the layer is offset in touch space; layerEdge already + // accounts for X. Subtract layerEdgeY to bring ty into framebuffer-local space. + ty -= layerEdgeY; const s32 sx = (ult::useRightAlignment ? static_cast(tsl::cfg::FramebufferWidth) - NOTIF_WIDTH : 0) + static_cast(ult::layerEdge); diff --git a/Source/hoc-clk/overlay/lib/libultrahand/libultra/include/debug_funcs.hpp b/Source/hoc-clk/overlay/lib/libultrahand/libultra/include/debug_funcs.hpp index 4fb32836..3cd31c21 100644 --- a/Source/hoc-clk/overlay/lib/libultrahand/libultra/include/debug_funcs.hpp +++ b/Source/hoc-clk/overlay/lib/libultrahand/libultra/include/debug_funcs.hpp @@ -23,7 +23,7 @@ #include namespace ult { - #if USING_LOGGING_DIRECTIVE + //#if USING_LOGGING_DIRECTIVE // Specify the log file path extern const std::string defaultLogFilePath; @@ -41,5 +41,5 @@ namespace ult { */ void logMessage(const char* message); void logMessage(const std::string& message); - #endif + //#endif } \ No newline at end of file diff --git a/Source/hoc-clk/overlay/lib/libultrahand/libultra/include/haptics.hpp b/Source/hoc-clk/overlay/lib/libultrahand/libultra/include/haptics.hpp index d37943be..09632d31 100644 --- a/Source/hoc-clk/overlay/lib/libultrahand/libultra/include/haptics.hpp +++ b/Source/hoc-clk/overlay/lib/libultrahand/libultra/include/haptics.hpp @@ -36,5 +36,6 @@ namespace ult { void rumbleDoubleClick(); void processRumbleStop(u64 nowNs); void processRumbleDoubleClick(u64 nowNs); + void rumbleClickStandalone(); void rumbleDoubleClickStandalone(); } \ No newline at end of file diff --git a/Source/hoc-clk/overlay/lib/libultrahand/libultra/source/debug_funcs.cpp b/Source/hoc-clk/overlay/lib/libultrahand/libultra/source/debug_funcs.cpp index f61cfa9c..f193c6ed 100644 --- a/Source/hoc-clk/overlay/lib/libultrahand/libultra/source/debug_funcs.cpp +++ b/Source/hoc-clk/overlay/lib/libultrahand/libultra/source/debug_funcs.cpp @@ -18,7 +18,7 @@ #include "debug_funcs.hpp" namespace ult { - #if USING_LOGGING_DIRECTIVE + //#if USING_LOGGING_DIRECTIVE // Define static variables const std::string defaultLogFilePath = "sdmc:/switch/.packages/log.txt"; std::string logFilePath = defaultLogFilePath; @@ -48,5 +48,5 @@ namespace ult { void logMessage(const std::string& message) { logMessage(message.c_str()); } - #endif + //#endif } \ No newline at end of file diff --git a/Source/hoc-clk/overlay/lib/libultrahand/libultra/source/haptics.cpp b/Source/hoc-clk/overlay/lib/libultrahand/libultra/source/haptics.cpp index 49cc599e..98424819 100644 --- a/Source/hoc-clk/overlay/lib/libultrahand/libultra/source/haptics.cpp +++ b/Source/hoc-clk/overlay/lib/libultrahand/libultra/source/haptics.cpp @@ -181,6 +181,14 @@ namespace ult { } } + void rumbleClickStandalone() { + // Match regular rumbleClick() behavior, but blocking + sendVibration(&vibrationStop); + sendVibration2x(&hapticsPreset); + svcSleepThread(RUMBLE_DURATION_NS); + sendVibration(&vibrationStop); + } + void rumbleDoubleClickStandalone() { // Standalone uses sleeps, but still use cached style for decision diff --git a/hoc-clk/libultrahand b/hoc-clk/libultrahand deleted file mode 160000 index 8a1dbe99..00000000 --- a/hoc-clk/libultrahand +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8a1dbe9910c844eb26fde7ac75a2a597b3c0e5e5