Skip to content

Commit 9ec05e2

Browse files
Add function to show QR Code of an entry's wiki link (#164)
* Add qrcodegen library * Add function to show QR Code of an entry's wiki link * Draw top screen background without top bar * Run aptMainLoop() --------- Co-authored-by: Pk11 <[email protected]>
1 parent e5f4fe9 commit 9ec05e2

File tree

10 files changed

+1646
-10
lines changed

10 files changed

+1646
-10
lines changed

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,10 @@ TARGET := Universal-Updater
9393
BUILD := build
9494
UNIVCORE := Universal-Core
9595
SOURCES := $(UNIVCORE) source source/download source/gui source/lang source/menu source/overlays \
96-
source/qr source/screens source/store source/utils
96+
source/qr source/qrcodegen source/screens source/store source/utils
9797
DATA := data
98-
INCLUDES := $(UNIVCORE) include include/download include/gui include/lang include/overlays include/qr include/screens \
99-
include/store include/utils
98+
INCLUDES := $(UNIVCORE) include include/download include/gui include/lang include/overlays include/qr \
99+
include/qrcodegen include/screens include/store include/utils
100100
GRAPHICS := assets/gfx
101101
ROMFS := romfs
102102
GFXBUILD := $(ROMFS)/gfx

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,4 @@ If you're testing in Citra, run `make citra` instead of just `make` to disable t
9090
- [lvandeve](https://github.com/lvandeve): For [LodePNG](https://github.com/lvandeve/lodepng)
9191
- [PabloMK7](https://github.com/PabloMK7): 3D Banner & Download Code Improvements
9292
- [lividhen](https://github.com/lividhen): 3D Banner
93+
- [Nayuki](https://github.com/nayuki): Original developer of the [QR Code generator library](https://github.com/nayuki/QR-Code-generator)

include/overlays/overlay.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ namespace Overlays {
3636
void ShowCredits();
3737
std::string SelectDir(const std::string &oldDir, const std::string &msg);
3838
void SelectTheme();
39+
void ShowQrCodeUrl(const std::string &title, const std::string &url);
3940
};
4041

4142
#endif

include/qrcodegen/qrcodegen.h

Lines changed: 385 additions & 0 deletions
Large diffs are not rendered by default.

romfs/lang/en/app.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@
111111
"OP_MOVING": "Moving",
112112
"OP_WAITING": "Waiting",
113113
"POPULARITY": "Popularity",
114+
"OPEN_URL_WEB_BROWSER": "Open URL in Web Browser (SELECT)",
115+
"OPEN_URL_WEB_BROWSER_DISABLED": "The web browser cannot be used\nwhile the Queue is running.",
116+
"QR_CODE_GEN_FAILED": "Failed to generate QR Code.",
114117
"QUEUE": "Queue",
115118
"QUEUE_POSITION": "Queue position",
116119
"QUEUE_PROGRESS": "Step: %d / %d",

source/menu/entryInfo.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
#include "common.hpp"
2828
#include "files.hpp"
29+
#include "overlay.hpp"
2930
#include "storeUtils.hpp"
3031
#include "structs.hpp"
3132

@@ -107,13 +108,7 @@ void StoreUtils::EntryHandle(bool &showMark, bool &fetch, bool &sFetch, int &mod
107108
}
108109

109110
if (touching(touch, wiki) && entry->GetWiki() != "") {
110-
char *buf = new char[0x1000]; // Needs to be this size size it gets memcpy'd to
111-
int length = entry->GetWiki().size();
112-
memcpy(buf, entry->GetWiki().c_str(), length);
113-
buf[length] = 0;
114-
aptLaunchSystemApplet(APPID_WEB, buf, length + 1, 0);
115-
116-
delete[] buf;
111+
Overlays::ShowQrCodeUrl(entry->GetTitle() + " Wiki", entry->GetWiki());
117112
}
118113
}
119114

source/overlays/showQrCode.cpp

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*
2+
* This file is part of Universal-Updater
3+
* Copyright (C) 2019-2021 Universal-Team
4+
* Copyright (C) 2025 Alvin Wong
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*
19+
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
20+
* * Requiring preservation of specified reasonable legal notices or
21+
* author attributions in that material or in the Appropriate Legal
22+
* Notices displayed by works containing it.
23+
* * Prohibiting misrepresentation of the origin of that material,
24+
* or requiring that modified versions of such material be marked in
25+
* reasonable ways as different from the original version.
26+
*/
27+
28+
#include "animation.hpp"
29+
#include "common.hpp"
30+
#include "overlay.hpp"
31+
#include "qrcodegen.h"
32+
33+
extern bool touching(touchPosition touch, Structs::ButtonPos button);
34+
extern bool QueueRuns;
35+
36+
static const Structs::ButtonPos backButton{ 4, 0, 24, 24 };
37+
static const Structs::ButtonPos browserButton = { 4, 212, 312, 22 };
38+
39+
static std::vector<std::string> wrapUrlLines(std::string_view url, uint32_t maxLines = std::numeric_limits<uint32_t>::max()) {
40+
// Crudely line wrap the URL:
41+
std::vector<std::string> wrappedUrlLines;
42+
std::string line;
43+
size_t lastWrap = 0;
44+
for (size_t i = 0; i < url.size(); i++) {
45+
char c = url[i];
46+
line.push_back(c);
47+
48+
if (i - lastWrap < 30) continue;
49+
50+
bool wrap = false;
51+
switch (c) {
52+
case '-':
53+
case '/':
54+
case '_':
55+
case '?':
56+
case '=':
57+
case '&':
58+
case ' ':
59+
wrap = true;
60+
break;
61+
default:
62+
if (i - lastWrap >= 40) wrap = true;
63+
break;
64+
}
65+
66+
if (wrap) {
67+
if (wrappedUrlLines.size() + 1 >= maxLines) {
68+
line.append("...");
69+
break;
70+
}
71+
if (line[line.size() - 1] == ' ') {
72+
// add keyboard return symbol to visualize whitespace
73+
line.append("\uE056");
74+
}
75+
wrappedUrlLines.emplace_back(std::move(line));
76+
lastWrap = i;
77+
}
78+
}
79+
if (!line.empty()) wrappedUrlLines.emplace_back(std::move(line));
80+
return wrappedUrlLines;
81+
}
82+
83+
/* Show a QR Code URL. */
84+
void Overlays::ShowQrCodeUrl(const std::string &title, const std::string &url) {
85+
86+
std::unique_ptr<uint8_t[]> qrcode = std::make_unique<uint8_t[]>(qrcodegen_BUFFER_LEN_MAX);
87+
std::unique_ptr<uint8_t[]> tempBuffer = std::make_unique<uint8_t[]>(qrcodegen_BUFFER_LEN_MAX);
88+
bool ok = qrcodegen_encodeText(url.c_str(), tempBuffer.get(), qrcode.get(), qrcodegen_Ecc_LOW, qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true);
89+
if (!ok) {
90+
Msg::DisplayMsg(Lang::get("QR_CODE_GEN_FAILED"));
91+
return;
92+
}
93+
94+
int size = qrcodegen_getSize(qrcode.get());
95+
if (size <= 0 || size > 256) {
96+
Msg::DisplayMsg(Lang::get("QR_CODE_GEN_FAILED"));
97+
return;
98+
}
99+
100+
C3D_Tex tex;
101+
102+
C3D_TexInit(&tex, 256, 256, GPU_TEXCOLOR::GPU_A8);
103+
C3D_TexSetFilter(&tex, GPU_NEAREST, GPU_NEAREST);
104+
C3D_TexSetWrap(&tex, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
105+
106+
for (int x = 0; x < size; x++) {
107+
for (int y = 0; y < size; y++) {
108+
const u32 dstPos = ((((y >> 3) * (256 >> 3) + (x >> 3)) << 6) +
109+
((x & 1) | ((y & 1) << 1) | ((x & 2) << 1) | ((y & 2) << 2) |
110+
((x & 4) << 2) | ((y & 4) << 3)));
111+
112+
((uint8_t *)tex.data)[dstPos] = qrcodegen_getModule(qrcode.get(), x, y) ? 255 : 0;
113+
}
114+
}
115+
116+
constexpr int border = 4;
117+
int scale = 240 / (size + border * 2);
118+
int drawSize = size * scale;
119+
int drawX = (400 - drawSize) / 2;
120+
int drawY = (240 - drawSize) / 2;
121+
int drawBorder = border * scale;
122+
123+
C2D_Image qrImage;
124+
Tex3DS_SubTexture subtex{(u16)drawSize, (u16)drawSize, 0.0f, 1.0f, size / 256.f, 1.0f - (size / 256.f)};
125+
qrImage.tex = &tex;
126+
qrImage.subtex = &subtex;
127+
C2D_Tint tint{
128+
.color = BLACK,
129+
.blend = 1.f,
130+
};
131+
C2D_ImageTint imageTint{
132+
.corners = {tint, tint, tint, tint},
133+
};
134+
135+
std::vector<std::string> wrappedUrlLines = wrapUrlLines(url, 8);
136+
137+
bool doOut = false;
138+
139+
while(!doOut && aptMainLoop()) {
140+
Gui::clearTextBufs();
141+
C3D_FrameBegin(C3D_FRAME_SYNCDRAW);
142+
C2D_TargetClear(Top, TRANSPARENT);
143+
C2D_TargetClear(Bottom, TRANSPARENT);
144+
145+
// Not using GFX::DrawTop because the QR doesn't fit as
146+
// nicely with the border.
147+
Gui::ScreenDraw(Top);
148+
Gui::Draw_Rect(0, 0, 400, 240, UIThemes->BGColor());
149+
150+
C2D_DrawRectSolid(drawX - drawBorder, drawY - drawBorder, 0.75f, drawSize + drawBorder * 2, drawSize + drawBorder * 2, WHITE);
151+
C2D_DrawImageAt(qrImage, drawX, drawY, 1.f, &imageTint);
152+
153+
Animation::QueueEntryDone();
154+
GFX::DrawBottom();
155+
Gui::Draw_Rect(0, 0, 320, 25, UIThemes->BarColor());
156+
Gui::Draw_Rect(0, 25, 320, 1, UIThemes->BarOutline());
157+
GFX::DrawIcon(sprites_arrow_idx, backButton.x, backButton.y, UIThemes->TextColor());
158+
Gui::DrawStringCentered(0, 2, 0.6, UIThemes->TextColor(), title, 310, 0, font);
159+
160+
int y = 34;
161+
for (const std::string &urlLine : wrappedUrlLines) {
162+
Gui::DrawStringCentered(0, y, 0.6f, UIThemes->TextColor(), urlLine, 312, 0, font);
163+
y += 20;
164+
if (y >= browserButton.y) break;
165+
}
166+
167+
const bool browserAllowed = !QueueRuns && aptIsHomeAllowed();
168+
Gui::Draw_Rect(browserButton.x, browserButton.y, browserButton.w, browserButton.h, browserAllowed ? UIThemes->MarkSelected() : UIThemes->MarkUnselected());
169+
Gui::DrawStringCentered(0, browserButton.y + 4, 0.45f, UIThemes->TextColor(), Lang::get("OPEN_URL_WEB_BROWSER"), 255, 0, font);
170+
171+
C3D_FrameEnd(0);
172+
173+
hidScanInput();
174+
touchPosition touch;
175+
hidTouchRead(&touch);
176+
Animation::HandleQueueEntryDone();
177+
178+
if ((hidKeysDown() & KEY_START) || (hidKeysDown() & KEY_B) || (hidKeysDown() & KEY_A) || (hidKeysDown() & KEY_TOUCH && touching(touch, backButton))) doOut = true;
179+
180+
if ((hidKeysDown() & KEY_SELECT) || (hidKeysDown() & KEY_TOUCH && touching(touch, browserButton))) {
181+
if (browserAllowed) {
182+
char *buf = new char[0x1000]; // Needs to be this size size it gets memcpy'd to
183+
int length = url.size();
184+
memcpy(buf, url.c_str(), length);
185+
buf[length] = 0;
186+
aptLaunchSystemApplet(APPID_WEB, buf, length + 1, 0);
187+
188+
delete[] buf;
189+
190+
doOut = true;
191+
} else {
192+
Msg::waitMsg(Lang::get("OPEN_URL_WEB_BROWSER_DISABLED"));
193+
}
194+
}
195+
}
196+
197+
C3D_TexDelete(&tex);
198+
}

source/qrcodegen/LICENSE

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright © 2024 Project Nayuki. (MIT License)
2+
[https://www.nayuki.io/page/qr-code-generator-library](https://www.nayuki.io/page/qr-code-generator-library)
3+
4+
Permission is hereby granted, free of charge, to any person obtaining a copy of
5+
this software and associated documentation files (the "Software"), to deal in
6+
the Software without restriction, including without limitation the rights to
7+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8+
the Software, and to permit persons to whom the Software is furnished to do so,
9+
subject to the following conditions:
10+
11+
* The above copyright notice and this permission notice shall be included in
12+
all copies or substantial portions of the Software.
13+
14+
* The Software is provided "as is", without warranty of any kind, express or
15+
implied, including but not limited to the warranties of merchantability,
16+
fitness for a particular purpose and noninfringement. In no event shall the
17+
authors or copyright holders be liable for any claim, damages or other
18+
liability, whether in an action of contract, tort or otherwise, arising from,
19+
out of or in connection with the Software or the use or other dealings in the
20+
Software.

source/qrcodegen/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
This QR Code generator library is from
2+
https://github.com/nayuki/QR-Code-generator/tree/8329a7108fc22be3e1eec0a9f9318978579e3621/c
3+
available under MIT License.
4+
5+
There is a C++ version of the library available, however it cannot be used
6+
because it uses exceptions.

0 commit comments

Comments
 (0)