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) {
140
+ Gui::clearTextBufs ();
141
+ C3D_FrameBegin (C3D_FRAME_SYNCDRAW);
142
+ C2D_TargetClear (Top, TRANSPARENT);
143
+ C2D_TargetClear (Bottom, TRANSPARENT);
144
+
145
+ GFX::DrawTop ();
146
+
147
+ C2D_DrawRectSolid (drawX - drawBorder, drawY - drawBorder, 0 .75f , drawSize + drawBorder * 2 , drawSize + drawBorder * 2 , WHITE);
148
+ C2D_DrawImageAt (qrImage, drawX, drawY, 1 .f , &imageTint);
149
+
150
+ Animation::QueueEntryDone ();
151
+ GFX::DrawBottom ();
152
+ Gui::Draw_Rect (0 , 0 , 320 , 25 , UIThemes->BarColor ());
153
+ Gui::Draw_Rect (0 , 25 , 320 , 1 , UIThemes->BarOutline ());
154
+ GFX::DrawIcon (sprites_arrow_idx, backButton.x , backButton.y , UIThemes->TextColor ());
155
+ Gui::DrawStringCentered (0 , 2 , 0.6 , UIThemes->TextColor (), title, 310 , 0 , font);
156
+
157
+ int y = 34 ;
158
+ for (const std::string &urlLine : wrappedUrlLines) {
159
+ Gui::DrawStringCentered (0 , y, 0 .6f , UIThemes->TextColor (), urlLine, 312 , 0 , font);
160
+ y += 20 ;
161
+ if (y >= browserButton.y ) break ;
162
+ }
163
+
164
+ const bool browserAllowed = !QueueRuns && aptIsHomeAllowed ();
165
+ Gui::Draw_Rect (browserButton.x , browserButton.y , browserButton.w , browserButton.h , browserAllowed ? UIThemes->MarkSelected () : UIThemes->MarkUnselected ());
166
+ Gui::DrawStringCentered (0 , browserButton.y + 4 , 0 .45f , UIThemes->TextColor (), Lang::get (" OPEN_URL_WEB_BROWSER" ), 255 , 0 , font);
167
+
168
+ C3D_FrameEnd (0 );
169
+
170
+ hidScanInput ();
171
+ touchPosition touch;
172
+ hidTouchRead (&touch);
173
+ Animation::HandleQueueEntryDone ();
174
+
175
+ if ((hidKeysDown () & KEY_START) || (hidKeysDown () & KEY_B) || (hidKeysDown () & KEY_A) || (hidKeysDown () & KEY_TOUCH && touching (touch, backButton))) doOut = true ;
176
+
177
+ if ((hidKeysDown () & KEY_SELECT) || (hidKeysDown () & KEY_TOUCH && touching (touch, browserButton))) {
178
+ if (browserAllowed) {
179
+ char *buf = new char [0x1000 ]; // Needs to be this size size it gets memcpy'd to
180
+ int length = url.size ();
181
+ memcpy (buf, url.c_str (), length);
182
+ buf[length] = 0 ;
183
+ aptLaunchSystemApplet (APPID_WEB, buf, length + 1 , 0 );
184
+
185
+ delete[] buf;
186
+
187
+ doOut = true ;
188
+ } else {
189
+ Msg::waitMsg (Lang::get (" OPEN_URL_WEB_BROWSER_DISABLED" ));
190
+ }
191
+ }
192
+ }
193
+
194
+ C3D_TexDelete (&tex);
195
+ }
0 commit comments