5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
22 #include "io/frsky_firmware_update.h"
23 #include "io/multi_firmware_update.h"
25 #include "storage/modelslist.h"
27 #define NODE_TYPE(fname) fname[SD_SCREEN_FILE_LENGTH+1]
28 #define IS_DIRECTORY(fname) ((bool)(!NODE_TYPE(fname)))
29 #define IS_FILE(fname) ((bool)(NODE_TYPE(fname)))
31 int currentBitmapIndex
= 0;
32 BitmapBuffer
* currentBitmap
= nullptr;
34 inline void REFRESH_FILES()
36 reusableBuffer
.sdManager
.offset
= 65535;
37 currentBitmapIndex
= -1;
40 bool menuRadioSdManagerInfo(event_t event
)
42 SIMPLE_SUBMENU(STR_SD_INFO_TITLE
, ICON_RADIO_SD_MANAGER
, 1);
44 lcdDrawText(MENUS_MARGIN_LEFT
, 2*FH
, STR_SD_TYPE
);
45 lcdDrawText(100, 2*FH
, SD_IS_HC() ? STR_SDHC_CARD
: STR_SD_CARD
);
47 lcdDrawText(MENUS_MARGIN_LEFT
, 3*FH
, STR_SD_SIZE
);
48 lcdDrawNumber(100, 3*FH
, sdGetSize(), LEFT
, 0, NULL
, "M");
50 lcdDrawText(MENUS_MARGIN_LEFT
, 4*FH
, STR_SD_SECTORS
);
51 #if defined(SD_GET_FREE_BLOCKNR)
52 lcdDrawNumber(100, 4*FH
, SD_GET_FREE_BLOCKNR()/1000, LEFT
, 0, NULL
, "/");
53 lcdDrawNumber(150, 4*FH
, sdGetNoSectors()/1000, LEFT
);
55 lcdDrawNumber(100, 4*FH
, sdGetNoSectors()/1000, LEFT
, 0, NULL
, "k");
58 lcdDrawText(MENUS_MARGIN_LEFT
, 5*FH
, STR_SD_SPEED
);
59 lcdDrawNumber(100, 5*FH
, SD_GET_SPEED()/1000, LEFT
, 0, NULL
, "kb/s");
64 inline bool isFilenameGreater(bool isfile
, const char * fn
, const char * line
)
66 return (isfile
&& IS_DIRECTORY(line
)) || (isfile
==IS_FILE(line
) && strcasecmp(fn
, line
) > 0);
69 inline bool isFilenameLower(bool isfile
, const char * fn
, const char * line
)
71 return (!isfile
&& IS_FILE(line
)) || (isfile
==IS_FILE(line
) && strcasecmp(fn
, line
) < 0);
74 void getSelectionFullPath(char * lfn
)
76 f_getcwd(lfn
, _MAX_LFN
);
78 strcat(lfn
, reusableBuffer
.sdManager
.lines
[menuVerticalPosition
- menuVerticalOffset
]);
81 void onSdFormatConfirm(const char * result
)
83 if (result
== STR_OK
) {
84 showMessageBox(STR_FORMATTING
);
94 void onSdManagerMenu(const char * result
)
96 TCHAR lfn
[_MAX_LFN
+1];
98 // TODO possible buffer overflows here!
100 uint8_t index
= menuVerticalPosition
-menuVerticalOffset
;
101 char *line
= reusableBuffer
.sdManager
.lines
[index
];
103 if (result
== STR_SD_INFO
) {
104 pushMenu(menuRadioSdManagerInfo
);
106 else if (result
== STR_SD_FORMAT
) {
107 POPUP_CONFIRMATION(STR_CONFIRM_FORMAT
, onSdFormatConfirm
);
109 else if (result
== STR_COPY_FILE
) {
110 clipboard
.type
= CLIPBOARD_TYPE_SD_FILE
;
111 f_getcwd(clipboard
.data
.sd
.directory
, CLIPBOARD_PATH_LEN
);
112 strncpy(clipboard
.data
.sd
.filename
, line
, CLIPBOARD_PATH_LEN
-1);
114 else if (result
== STR_PASTE
) {
115 f_getcwd(lfn
, _MAX_LFN
);
116 // if destination is dir, copy into that dir
117 if (IS_DIRECTORY(line
)) {
121 if (strcmp(clipboard
.data
.sd
.directory
, lfn
)) { // prevent copying to the same directory
122 POPUP_WARNING(sdCopyFile(clipboard
.data
.sd
.filename
, clipboard
.data
.sd
.directory
, clipboard
.data
.sd
.filename
, lfn
));
126 else if (result
== STR_RENAME_FILE
) {
127 memcpy(reusableBuffer
.sdManager
.originalName
, line
, sizeof(reusableBuffer
.sdManager
.originalName
));
128 uint8_t fnlen
= 0, extlen
= 0;
129 getFileExtension(line
, 0, LEN_FILE_EXTENSION_MAX
, &fnlen
, &extlen
);
130 // write spaces to allow extending the length of a filename
131 memset(line
+ fnlen
- extlen
, ' ', SD_SCREEN_FILE_LENGTH
- fnlen
+ extlen
);
132 line
[SD_SCREEN_FILE_LENGTH
-extlen
] = '\0';
133 s_editMode
= EDIT_MODIFY_STRING
;
134 editNameCursorPos
= 0;
136 else if (result
== STR_DELETE_FILE
) {
137 getSelectionFullPath(lfn
);
139 menuVerticalOffset
= 0;
140 menuVerticalPosition
= 0;
143 else if (result
== STR_PLAY_FILE
) {
144 getSelectionFullPath(lfn
);
145 audioQueue
.stopAll();
146 audioQueue
.playFile(lfn
, 0, ID_PLAY_FROM_SD_MANAGER
);
148 else if (result
== STR_ASSIGN_BITMAP
) {
149 memcpy(g_model
.header
.bitmap
, line
, sizeof(g_model
.header
.bitmap
));
150 if(modelslist
.getCurrentModel())
151 modelslist
.getCurrentModel()->resetBuffer();
152 storageDirty(EE_MODEL
);
154 else if (result
== STR_ASSIGN_SPLASH
) {
155 f_getcwd(lfn
, _MAX_LFN
);
156 sdCopyFile(line
, lfn
, SPLASH_FILE
, BITMAPS_PATH
);
158 else if (result
== STR_VIEW_TEXT
) {
159 getSelectionFullPath(lfn
);
160 pushMenuTextView(lfn
);
162 else if (result
== STR_FLASH_BOOTLOADER
) {
163 getSelectionFullPath(lfn
);
164 bootloaderFlash(lfn
);
166 else if (result
== STR_FLASH_INTERNAL_MODULE
) {
167 getSelectionFullPath(lfn
);
168 FrskyDeviceFirmwareUpdate
device(INTERNAL_MODULE
);
169 device
.flashFirmware(lfn
);
171 else if (result
== STR_FLASH_EXTERNAL_MODULE
) {
172 getSelectionFullPath(lfn
);
173 FrskyDeviceFirmwareUpdate
device(EXTERNAL_MODULE
);
174 device
.flashFirmware(lfn
);
176 else if (result
== STR_FLASH_EXTERNAL_DEVICE
) {
177 getSelectionFullPath(lfn
);
178 FrskyDeviceFirmwareUpdate
device(SPORT_MODULE
);
179 device
.flashFirmware(lfn
);
181 #if defined(BLUETOOTH)
182 else if (result
== STR_FLASH_BLUETOOTH_MODULE
) {
183 getSelectionFullPath(lfn
);
184 bluetooth
.flashFirmware(lfn
);
187 #if defined(HARDWARE_POWER_MANAGEMENT_UNIT)
188 else if (result
== STR_FLASH_POWER_MANAGEMENT_UNIT
) {
189 getSelectionFullPath(lfn
);
190 FrskyChipFirmwareUpdate device
;
191 device
.flashFirmware(lfn
);
194 #if defined(MULTIMODULE)
195 #if defined(INTERNAL_MODULE_MULTI)
196 else if (result
== STR_FLASH_INTERNAL_MULTI
) {
197 getSelectionFullPath(lfn
);
198 multiFlashFirmware(INTERNAL_MODULE
, lfn
);
201 else if (result
== STR_FLASH_EXTERNAL_MULTI
) {
202 getSelectionFullPath(lfn
);
203 multiFlashFirmware(EXTERNAL_MODULE
, lfn
);
207 else if (result
== STR_EXECUTE_FILE
) {
208 getSelectionFullPath(lfn
);
214 bool menuRadioSdManager(event_t _event
)
216 event_t event
= (EVT_KEY_MASK(_event
) == KEY_ENTER
? 0 : _event
);
217 SIMPLE_MENU(SD_IS_HC() ? STR_SDHC_CARD
: STR_SD_CARD
, RADIO_ICONS
, menuTabGeneral
, MENU_RADIO_SD_MANAGER
, reusableBuffer
.sdManager
.count
);
219 int index
= menuVerticalPosition
- menuVerticalOffset
;
227 memset(&reusableBuffer
.sdManager
, 0, sizeof(reusableBuffer
.sdManager
));
228 menuVerticalPosition
= 0;
233 // TODO: Implement it
234 case EVT_KEY_LONG(KEY_MENU
):
235 if (!READ_ONLY() && s_editMode
== 0) {
237 POPUP_MENU_ADD_ITEM(STR_SD_INFO
);
238 POPUP_MENU_ADD_ITEM(STR_SD_FORMAT
);
239 POPUP_MENU_START(onSdManagerMenu
);
244 case EVT_KEY_BREAK(KEY_EXIT
):
248 case EVT_KEY_BREAK(KEY_ENTER
):
249 if (s_editMode
> 0) {
253 if (IS_DIRECTORY(reusableBuffer
.sdManager
.lines
[index
])) {
254 f_chdir(reusableBuffer
.sdManager
.lines
[index
]);
255 menuVerticalOffset
= 0;
256 menuVerticalPosition
= 1;
265 case EVT_KEY_LONG(KEY_ENTER
):
266 if (s_editMode
== 0) {
268 char * line
= reusableBuffer
.sdManager
.lines
[index
];
269 if (!strcmp(line
, "..")) {
270 break; // no menu for parent dir
272 const char * ext
= getFileExtension(line
);
274 if (!strcasecmp(ext
, SOUNDS_EXT
)) {
275 POPUP_MENU_ADD_ITEM(STR_PLAY_FILE
);
277 else if (isExtensionMatching(ext
, BITMAPS_EXT
)) {
278 TCHAR lfn
[_MAX_LFN
+1];
279 f_getcwd(lfn
, _MAX_LFN
);
280 if (!READ_ONLY() && unsigned(strlen(ext
)+ext
-line
) <= sizeof(g_model
.header
.bitmap
) && !strcmp(lfn
, BITMAPS_PATH
)) {
281 POPUP_MENU_ADD_ITEM(STR_ASSIGN_BITMAP
);
283 if (!strcmp(ext
, PNG_EXT
)) {
284 POPUP_MENU_ADD_ITEM(STR_ASSIGN_SPLASH
);
287 else if (!READ_ONLY() && !strcasecmp(ext
, SPORT_FIRMWARE_EXT
)) {
288 if (HAS_SPORT_UPDATE_CONNECTOR())
289 POPUP_MENU_ADD_ITEM(STR_FLASH_EXTERNAL_DEVICE
);
290 POPUP_MENU_ADD_ITEM(STR_FLASH_INTERNAL_MODULE
);
291 POPUP_MENU_ADD_ITEM(STR_FLASH_EXTERNAL_MODULE
);
293 else if (!READ_ONLY() && !strcasecmp(ext
, FRSKY_FIRMWARE_EXT
)) {
294 FrSkyFirmwareInformation information
;
295 if (readFrSkyFirmwareInformation(line
, information
) == nullptr) {
296 if (information
.productFamily
== FIRMWARE_FAMILY_INTERNAL_MODULE
)
297 POPUP_MENU_ADD_ITEM(STR_FLASH_INTERNAL_MODULE
);
298 if (information
.productFamily
== FIRMWARE_FAMILY_EXTERNAL_MODULE
)
299 POPUP_MENU_ADD_ITEM(STR_FLASH_EXTERNAL_MODULE
);
300 if (HAS_SPORT_UPDATE_CONNECTOR() && (information
.productFamily
== FIRMWARE_FAMILY_RECEIVER
|| information
.productFamily
== FIRMWARE_FAMILY_SENSOR
))
301 POPUP_MENU_ADD_ITEM(STR_FLASH_EXTERNAL_DEVICE
);
303 if (information
.productFamily
== FIRMWARE_FAMILY_RECEIVER
)
304 POPUP_MENU_ADD_ITEM(STR_FLASH_RECEIVER_OTA
);
306 #if defined(BLUETOOTH)
307 if (information
.productFamily
== FIRMWARE_FAMILY_BLUETOOTH_CHIP
)
308 POPUP_MENU_ADD_ITEM(STR_FLASH_BLUETOOTH_MODULE
);
310 #if defined(HARDWARE_POWER_MANAGEMENT_UNIT)
311 if (information
.productFamily
== FIRMWARE_FAMILY_POWER_MANAGEMENT_UNIT
)
312 POPUP_MENU_ADD_ITEM(STR_FLASH_POWER_MANAGEMENT_UNIT
);
317 // Warning FIRMWARE_EXT and MULTI_FIRMWARE_EXT are the same
318 if (!READ_ONLY() && !strcasecmp(ext
, FIRMWARE_EXT
)) {
319 TCHAR lfn
[_MAX_LFN
+1];
320 getSelectionFullPath(lfn
);
321 if (isBootloader(lfn
)) {
322 POPUP_MENU_ADD_ITEM(STR_FLASH_BOOTLOADER
);
326 #if defined(MULTIMODULE)
327 if (!READ_ONLY() && !strcasecmp(ext
, MULTI_FIRMWARE_EXT
)) {
328 TCHAR lfn
[_MAX_LFN
+ 1];
329 getSelectionFullPath(lfn
);
330 MultiFirmwareInformation information
;
331 if (information
.readMultiFirmwareInformation(line
) == nullptr) {
332 #if defined(INTERNAL_MODULE_MULTI)
333 POPUP_MENU_ADD_ITEM(STR_FLASH_INTERNAL_MULTI
);
335 POPUP_MENU_ADD_ITEM(STR_FLASH_EXTERNAL_MULTI
);
340 if (isExtensionMatching(ext
, SCRIPTS_EXT
)) {
341 POPUP_MENU_ADD_ITEM(STR_EXECUTE_FILE
);
344 if (isExtensionMatching(ext
, TEXT_EXT
) || isExtensionMatching(ext
, SCRIPTS_EXT
)) {
345 POPUP_MENU_ADD_ITEM(STR_VIEW_TEXT
);
351 POPUP_MENU_ADD_ITEM(STR_COPY_FILE
);
352 if (clipboard
.type
== CLIPBOARD_TYPE_SD_FILE
)
353 POPUP_MENU_ADD_ITEM(STR_PASTE
);
354 POPUP_MENU_ADD_ITEM(STR_RENAME_FILE
);
356 POPUP_MENU_ADD_ITEM(STR_DELETE_FILE
);
358 POPUP_MENU_START(onSdManagerMenu
);
363 if (reusableBuffer
.sdManager
.offset
!= menuVerticalOffset
) {
367 if (menuVerticalOffset
== reusableBuffer
.sdManager
.offset
+ 1) {
368 memmove(reusableBuffer
.sdManager
.lines
[0], reusableBuffer
.sdManager
.lines
[1], (NUM_BODY_LINES
-1)*sizeof(reusableBuffer
.sdManager
.lines
[0]));
369 memset(reusableBuffer
.sdManager
.lines
[NUM_BODY_LINES
-1], 0xff, SD_SCREEN_FILE_LENGTH
);
370 NODE_TYPE(reusableBuffer
.sdManager
.lines
[NUM_BODY_LINES
-1]) = 1;
372 else if (menuVerticalOffset
== reusableBuffer
.sdManager
.offset
- 1) {
373 memmove(reusableBuffer
.sdManager
.lines
[1], reusableBuffer
.sdManager
.lines
[0], (NUM_BODY_LINES
-1)*sizeof(reusableBuffer
.sdManager
.lines
[0]));
374 memset(reusableBuffer
.sdManager
.lines
[0], 0, sizeof(reusableBuffer
.sdManager
.lines
[0]));
377 reusableBuffer
.sdManager
.offset
= menuVerticalOffset
;
378 memset(reusableBuffer
.sdManager
.lines
, 0, sizeof(reusableBuffer
.sdManager
.lines
));
381 reusableBuffer
.sdManager
.count
= 0;
383 FRESULT res
= f_opendir(&dir
, "."); // Open the directory
385 bool firstTime
= true;
387 res
= sdReadDir(&dir
, &fno
, firstTime
);
388 if (res
!= FR_OK
|| fno
.fname
[0] == 0) break; /* Break on error or end of dir */
389 if (strlen(fno
.fname
) > SD_SCREEN_FILE_LENGTH
) continue;
390 if (fno
.fattrib
& AM_HID
) continue; /* Ignore Windows hidden files */
391 if (fno
.fname
[0] == '.' && fno
.fname
[1] != '.') continue; /* Ignore UNIX hidden files, but not .. */
393 reusableBuffer
.sdManager
.count
++;
395 bool isfile
= !(fno
.fattrib
& AM_DIR
);
397 if (menuVerticalOffset
== 0) {
398 for (uint8_t i
=0; i
<NUM_BODY_LINES
; i
++) {
399 char * line
= reusableBuffer
.sdManager
.lines
[i
];
400 if (line
[0] == '\0' || isFilenameLower(isfile
, fno
.fname
, line
)) {
401 if (i
< NUM_BODY_LINES
-1) memmove(reusableBuffer
.sdManager
.lines
[i
+1], line
, sizeof(reusableBuffer
.sdManager
.lines
[i
]) * (NUM_BODY_LINES
-1-i
));
402 memset(line
, 0, sizeof(reusableBuffer
.sdManager
.lines
[0]));
403 strcpy(line
, fno
.fname
);
404 NODE_TYPE(line
) = isfile
;
409 else if (reusableBuffer
.sdManager
.offset
== menuVerticalOffset
) {
410 for (int8_t i
=NUM_BODY_LINES
-1; i
>=0; i
--) {
411 char *line
= reusableBuffer
.sdManager
.lines
[i
];
412 if (line
[0] == '\0' || isFilenameGreater(isfile
, fno
.fname
, line
)) {
413 if (i
> 0) memmove(reusableBuffer
.sdManager
.lines
[0], reusableBuffer
.sdManager
.lines
[1], sizeof(reusableBuffer
.sdManager
.lines
[0]) * i
);
414 memset(line
, 0, sizeof(reusableBuffer
.sdManager
.lines
[0]));
415 strcpy(line
, fno
.fname
);
416 NODE_TYPE(line
) = isfile
;
421 else if (menuVerticalOffset
> reusableBuffer
.sdManager
.offset
) {
422 if (isFilenameGreater(isfile
, fno
.fname
, reusableBuffer
.sdManager
.lines
[NUM_BODY_LINES
-2]) && isFilenameLower(isfile
, fno
.fname
, reusableBuffer
.sdManager
.lines
[NUM_BODY_LINES
-1])) {
423 memset(reusableBuffer
.sdManager
.lines
[NUM_BODY_LINES
-1], 0, sizeof(reusableBuffer
.sdManager
.lines
[0]));
424 strcpy(reusableBuffer
.sdManager
.lines
[NUM_BODY_LINES
-1], fno
.fname
);
425 NODE_TYPE(reusableBuffer
.sdManager
.lines
[NUM_BODY_LINES
-1]) = isfile
;
429 if (isFilenameLower(isfile
, fno
.fname
, reusableBuffer
.sdManager
.lines
[1]) && isFilenameGreater(isfile
, fno
.fname
, reusableBuffer
.sdManager
.lines
[0])) {
430 memset(reusableBuffer
.sdManager
.lines
[0], 0, sizeof(reusableBuffer
.sdManager
.lines
[0]));
431 strcpy(reusableBuffer
.sdManager
.lines
[0], fno
.fname
);
432 NODE_TYPE(reusableBuffer
.sdManager
.lines
[0]) = isfile
;
440 reusableBuffer
.sdManager
.offset
= menuVerticalOffset
;
442 for (uint8_t i
=0; i
<NUM_BODY_LINES
; i
++) {
443 coord_t y
= MENU_CONTENT_TOP
+ i
*FH
;
444 LcdFlags attr
= (index
== i
? INVERS
: 0);
445 if (reusableBuffer
.sdManager
.lines
[i
][0]) {
446 if (s_editMode
== EDIT_MODIFY_STRING
&& attr
) {
447 uint8_t extlen
, efflen
;
448 const char * ext
= getFileExtension(reusableBuffer
.sdManager
.originalName
, 0, 0, NULL
, &extlen
);
449 editName(MENUS_MARGIN_LEFT
, y
, reusableBuffer
.sdManager
.lines
[i
], SD_SCREEN_FILE_LENGTH
- extlen
, _event
, attr
, 0);
450 efflen
= effectiveLen(reusableBuffer
.sdManager
.lines
[i
], SD_SCREEN_FILE_LENGTH
- extlen
);
451 if (s_editMode
== 0) {
453 strAppend(&reusableBuffer
.sdManager
.lines
[i
][efflen
], ext
);
456 reusableBuffer
.sdManager
.lines
[i
][efflen
] = 0;
458 f_rename(reusableBuffer
.sdManager
.originalName
, reusableBuffer
.sdManager
.lines
[i
]);
462 else if (IS_DIRECTORY(reusableBuffer
.sdManager
.lines
[i
])) {
463 char s
[sizeof(reusableBuffer
.sdManager
.lines
[0])+2];
466 ptr
= strAppend(ptr
, reusableBuffer
.sdManager
.lines
[i
]);
469 lcdDrawText(MENUS_MARGIN_LEFT
, y
, s
, attr
);
472 lcdDrawText(MENUS_MARGIN_LEFT
, y
, reusableBuffer
.sdManager
.lines
[i
], attr
);
477 const char * ext
= getFileExtension(reusableBuffer
.sdManager
.lines
[index
]);
478 if (ext
&& isExtensionMatching(ext
, BITMAPS_EXT
)) {
479 if (currentBitmapIndex
!= menuVerticalPosition
) {
480 currentBitmapIndex
= menuVerticalPosition
;
481 delete currentBitmap
;
482 currentBitmap
= BitmapBuffer::load(reusableBuffer
.sdManager
.lines
[index
]);
485 uint16_t height
= currentBitmap
->getHeight();
486 uint16_t width
= currentBitmap
->getWidth();
487 if (height
> MENU_BODY_HEIGHT
-10) {
488 height
= MENU_BODY_HEIGHT
- 10;
490 if (width
> LCD_W
/2) {
493 lcd
->drawScaledBitmap(currentBitmap
, LCD_W
/ 2 - 20 + LCD_W
/4 - width
/2, MENU_BODY_TOP
+ MENU_BODY_HEIGHT
/2 - height
/2, width
, height
);