Root/src/gmenu2x.cpp

Source at commit ef6d378c5e401954308260d1fe3a53429fa66f4c created 5 years 5 months ago.
By Maarten ter Huurne, Removed the "Rename section" option
1/***************************************************************************
2 * Copyright (C) 2006 by Massimiliano Torromeo *
3 * massimiliano.torromeo@gmail.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
20
21#include "gp2x.h"
22
23#include "background.h"
24#include "cpu.h"
25#include "debug.h"
26#include "filedialog.h"
27#include "filelister.h"
28#include "font.h"
29#include "gmenu2x.h"
30#include "helppopup.h"
31#include "iconbutton.h"
32#include "inputdialog.h"
33#include "launcher.h"
34#include "linkapp.h"
35#include "mediamonitor.h"
36#include "menu.h"
37#include "menusettingbool.h"
38#include "menusettingdir.h"
39#include "menusettingfile.h"
40#include "menusettingimage.h"
41#include "menusettingint.h"
42#include "menusettingmultistring.h"
43#include "menusettingrgba.h"
44#include "menusettingstring.h"
45#include "messagebox.h"
46#include "powersaver.h"
47#include "settingsdialog.h"
48#include "textdialog.h"
49#include "wallpaperdialog.h"
50#include "utilities.h"
51
52#include <iostream>
53#include <sstream>
54#include <fstream>
55#include <algorithm>
56#include <stdlib.h>
57#include <unistd.h>
58#include <math.h>
59#include <SDL.h>
60#include <signal.h>
61
62#include <sys/statvfs.h>
63#include <errno.h>
64
65#ifdef PLATFORM_PANDORA
66//#include <pnd_container.h>
67//#include <pnd_conf.h>
68//#include <pnd_discovery.h>
69#endif
70
71//for browsing the filesystem
72#include <sys/stat.h>
73#include <sys/types.h>
74#include <dirent.h>
75
76//for soundcard
77#include <sys/ioctl.h>
78#include <linux/soundcard.h>
79
80#include <sys/mman.h>
81
82using namespace std;
83
84#ifndef DEFAULT_WALLPAPER_PATH
85#define DEFAULT_WALLPAPER_PATH \
86  GMENU2X_SYSTEM_DIR "/skins/Default/wallpapers/default.png"
87#endif
88
89#ifdef _CARD_ROOT
90const char *CARD_ROOT = _CARD_ROOT;
91#elif defined(PLATFORM_A320) || defined(PLATFORM_GCW0)
92const char *CARD_ROOT = "/media";
93#else
94const char *CARD_ROOT = "/card";
95#endif
96
97static GMenu2X *app;
98static string gmenu2x_home;
99
100// Note: Keep this in sync with the enum!
101static const char *colorNames[NUM_COLORS] = {
102    "topBarBg",
103    "bottomBarBg",
104    "selectionBg",
105    "messageBoxBg",
106    "messageBoxBorder",
107    "messageBoxSelection",
108};
109
110static enum color stringToColor(const string &name)
111{
112    for (unsigned int i = 0; i < NUM_COLORS; i++) {
113        if (strcmp(colorNames[i], name.c_str()) == 0) {
114            return (enum color)i;
115        }
116    }
117    return (enum color)-1;
118}
119
120static const char *colorToString(enum color c)
121{
122    return colorNames[c];
123}
124
125static void quit_all(int err) {
126    delete app;
127    SDL_Quit();
128    exit(err);
129}
130
131const string GMenu2X::getHome()
132{
133    return gmenu2x_home;
134}
135
136static void set_handler(int signal, void (*handler)(int))
137{
138    struct sigaction sig;
139    sigaction(signal, NULL, &sig);
140    sig.sa_handler = handler;
141    sig.sa_flags |= SA_RESTART;
142    sigaction(signal, &sig, NULL);
143}
144
145int main(int /*argc*/, char * /*argv*/[]) {
146    INFO("---- GMenu2X starting ----\n");
147
148    set_handler(SIGINT, &quit_all);
149    set_handler(SIGSEGV, &quit_all);
150    set_handler(SIGTERM, &quit_all);
151
152    char *home = getenv("HOME");
153    if (home == NULL) {
154        ERROR("Unable to find gmenu2x home directory. The $HOME variable is not defined.\n");
155        return 1;
156    }
157
158    gmenu2x_home = (string)home + (string)"/.gmenu2x";
159    if (mkdir(gmenu2x_home.c_str(), 0770) < 0 && errno != EEXIST) {
160        ERROR("Unable to create gmenu2x home directory.\n");
161        return 1;
162    }
163
164    DEBUG("Home path: %s.\n", gmenu2x_home.c_str());
165
166    GMenu2X::run();
167
168    return EXIT_FAILURE;
169}
170
171void GMenu2X::run() {
172    auto menu = new GMenu2X();
173    app = menu;
174    DEBUG("Starting main()\n");
175    menu->mainLoop();
176
177    app = nullptr;
178    Launcher *toLaunch = menu->toLaunch.release();
179    delete menu;
180
181    SDL_Quit();
182    unsetenv("SDL_FBCON_DONT_CLEAR");
183
184    if (toLaunch) {
185        toLaunch->exec();
186        // If control gets here, execution failed. Since we already destructed
187        // everything, the easiest solution is to exit and let the system
188        // respawn the menu.
189        delete toLaunch;
190    }
191}
192
193#ifdef ENABLE_CPUFREQ
194void GMenu2X::initCPULimits() {
195    // Note: These values are for the Dingoo.
196    // The NanoNote does not have cpufreq enabled in its kernel and
197    // other devices are not actively maintained.
198    // TODO: Read min and max from sysfs.
199    cpuFreqMin = 30;
200    cpuFreqMax = 500;
201    cpuFreqSafeMax = 420;
202    cpuFreqMenuDefault = 200;
203    cpuFreqAppDefault = 384;
204    cpuFreqMultiple = 24;
205
206    // Round min and max values to the specified multiple.
207    cpuFreqMin = ((cpuFreqMin + cpuFreqMultiple - 1) / cpuFreqMultiple)
208            * cpuFreqMultiple;
209    cpuFreqMax = (cpuFreqMax / cpuFreqMultiple) * cpuFreqMultiple;
210    cpuFreqSafeMax = (cpuFreqSafeMax / cpuFreqMultiple) * cpuFreqMultiple;
211    cpuFreqMenuDefault = (cpuFreqMenuDefault / cpuFreqMultiple) * cpuFreqMultiple;
212    cpuFreqAppDefault = (cpuFreqAppDefault / cpuFreqMultiple) * cpuFreqMultiple;
213}
214#endif
215
216GMenu2X::GMenu2X()
217    : input(*this, powerSaver)
218{
219    usbnet = samba = inet = web = false;
220    useSelectionPng = false;
221
222#ifdef ENABLE_CPUFREQ
223    initCPULimits();
224#endif
225    //load config data
226    readConfig();
227
228    halfX = resX/2;
229    halfY = resY/2;
230    bottomBarIconY = resY-18;
231    bottomBarTextY = resY-10;
232
233    /* Do not clear the screen on exit.
234     * This may require an SDL patch available at
235     * https://github.com/mthuurne/opendingux-buildroot/blob
236     * /opendingux-2010.11/package/sdl/sdl-fbcon-clear-onexit.patch
237     */
238    setenv("SDL_FBCON_DONT_CLEAR", "1", 0);
239
240    if( SDL_Init(SDL_INIT_TIMER) < 0) {
241        ERROR("Could not initialize SDL: %s\n", SDL_GetError());
242        // TODO: We don't use exceptions, so don't put things that can fail
243        // in a constructor.
244        exit(EXIT_FAILURE);
245    }
246
247    bg = NULL;
248    font = NULL;
249    setSkin(confStr["skin"], !fileExists(confStr["wallpaper"]));
250    layers.insert(layers.begin(), make_shared<Background>(*this));
251
252    /* We enable video at a later stage, so that the menu elements are
253     * loaded before SDL inits the video; this is made so that we won't show
254     * a black screen for a couple of seconds. */
255    if( SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) {
256        ERROR("Could not initialize SDL: %s\n", SDL_GetError());
257        // TODO: We don't use exceptions, so don't put things that can fail
258        // in a constructor.
259        exit(EXIT_FAILURE);
260    }
261
262    s = OutputSurface::open(resX, resY, confInt["videoBpp"]);
263
264    if (!fileExists(confStr["wallpaper"])) {
265        DEBUG("No wallpaper defined; we will take the default one.\n");
266        confStr["wallpaper"] = DEFAULT_WALLPAPER_PATH;
267    }
268
269    initBG();
270
271    /* the menu may take a while to load, so we show the background here */
272    for (auto layer : layers)
273        layer->paint(*s);
274    s->flip();
275
276    initMenu();
277
278#ifdef ENABLE_INOTIFY
279    monitor = new MediaMonitor(CARD_ROOT);
280#endif
281
282    if (!input.init(menu.get())) {
283        exit(EXIT_FAILURE);
284    }
285
286    powerSaver.setScreenTimeout(confInt["backlightTimeout"]);
287
288#ifdef ENABLE_CPUFREQ
289    setClock(confInt["menuClock"]);
290#endif
291}
292
293GMenu2X::~GMenu2X() {
294    fflush(NULL);
295    sc.clear();
296
297#ifdef ENABLE_INOTIFY
298    delete monitor;
299#endif
300}
301
302void GMenu2X::initBG() {
303    bg.reset();
304    bgmain.reset();
305
306    // Load wallpaper.
307    bg = OffscreenSurface::loadImage(confStr["wallpaper"]);
308    if (!bg) {
309        bg = OffscreenSurface::emptySurface(resX, resY);
310    }
311
312    drawTopBar(*bg);
313    drawBottomBar(*bg);
314
315    bgmain.reset(new OffscreenSurface(*bg));
316
317    {
318        auto sd = OffscreenSurface::loadImage(
319                sc.getSkinFilePath("imgs/sd.png"));
320        if (sd) sd->blit(*bgmain, 3, bottomBarIconY);
321    }
322
323    cpuX = 32 + font->write(*bgmain, getDiskFree(getHome().c_str()),
324            22, bottomBarTextY, Font::HAlignLeft, Font::VAlignMiddle);
325
326#ifdef ENABLE_CPUFREQ
327    {
328        auto cpu = OffscreenSurface::loadImage(
329                sc.getSkinFilePath("imgs/cpu.png"));
330        if (cpu) cpu->blit(*bgmain, cpuX, bottomBarIconY);
331    }
332    cpuX += 19;
333    manualX = cpuX + font->getTextWidth("300MHz") + 5;
334#else
335    manualX = cpuX;
336#endif
337
338    int serviceX = resX-38;
339    if (usbnet) {
340        if (web) {
341            auto webserver = OffscreenSurface::loadImage(
342                    sc.getSkinFilePath("imgs/webserver.png"));
343            if (webserver) webserver->blit(*bgmain, serviceX, bottomBarIconY);
344            serviceX -= 19;
345        }
346        if (samba) {
347            auto sambaS = OffscreenSurface::loadImage(
348                    sc.getSkinFilePath("imgs/samba.png"));
349            if (sambaS) sambaS->blit(*bgmain, serviceX, bottomBarIconY);
350            serviceX -= 19;
351        }
352        if (inet) {
353            auto inetS = OffscreenSurface::loadImage(
354                    sc.getSkinFilePath("imgs/inet.png"));
355            if (inetS) inetS->blit(*bgmain, serviceX, bottomBarIconY);
356            serviceX -= 19;
357        }
358    }
359
360    bgmain->convertToDisplayFormat();
361}
362
363void GMenu2X::initFont() {
364    string path = skinConfStr["font"];
365    if (!path.empty()) {
366        unsigned int size = skinConfInt["fontsize"];
367        if (!size)
368            size = 12;
369        if (path.substr(0,5)=="skin:")
370            path = sc.getSkinFilePath(path.substr(5, path.length()));
371        font.reset(new Font(path, size));
372    } else {
373        font = Font::defaultFont();
374    }
375}
376
377void GMenu2X::initMenu() {
378    //Menu structure handler
379    menu.reset(new Menu(*this));
380
381    // Add action links in the applications section.
382    auto appIdx = menu->sectionNamed("applications");
383    menu->addActionLink(appIdx, "Explorer",
384            bind(&GMenu2X::explorer, this),
385            tr["Launch an application"],
386            "skin:icons/explorer.png");
387
388    // Add action links in the settings section.
389    auto settingIdx = menu->sectionNamed("settings");
390    menu->addActionLink(settingIdx, "GMenu2X",
391            bind(&GMenu2X::showSettings, this),
392            tr["Configure GMenu2X's options"],
393            "skin:icons/configure.png");
394    menu->addActionLink(settingIdx, tr["Skin"],
395            bind(&GMenu2X::skinMenu, this),
396            tr["Configure skin"],
397            "skin:icons/skin.png");
398    menu->addActionLink(settingIdx, tr["Wallpaper"],
399            bind(&GMenu2X::changeWallpaper, this),
400            tr["Change GMenu2X wallpaper"],
401            "skin:icons/wallpaper.png");
402    if (fileExists(LOG_FILE)) {
403        menu->addActionLink(settingIdx, tr["Log Viewer"],
404                bind(&GMenu2X::viewLog, this),
405                tr["Displays last launched program's output"],
406                "skin:icons/ebook.png");
407    }
408    menu->addActionLink(settingIdx, tr["About"],
409            bind(&GMenu2X::about, this),
410            tr["Info about GMenu2X"],
411            "skin:icons/about.png");
412
413    menu->skinUpdated();
414    menu->orderLinks();
415
416    menu->setSectionIndex(confInt["section"]);
417    menu->setLinkIndex(confInt["link"]);
418
419    layers.push_back(menu);
420}
421
422void GMenu2X::about() {
423    string text(readFileAsString(GMENU2X_SYSTEM_DIR "/about.txt"));
424    string build_date("Build date: " __DATE__);
425    TextDialog td(*this, "GMenu2X", build_date, "icons/about.png", text);
426    td.exec();
427}
428
429void GMenu2X::viewLog() {
430    string text(readFileAsString(LOG_FILE));
431
432    TextDialog td(*this, tr["Log Viewer"],
433            tr["Displays last launched program's output"],
434            "icons/ebook.png", text);
435    td.exec();
436
437    MessageBox mb(*this, tr["Do you want to delete the log file?"],
438             "icons/ebook.png");
439    mb.setButton(InputManager::ACCEPT, tr["Yes"]);
440    mb.setButton(InputManager::CANCEL, tr["No"]);
441    if (mb.exec() == InputManager::ACCEPT) {
442        unlink(LOG_FILE);
443        menu->deleteSelectedLink();
444    }
445}
446
447void GMenu2X::readConfig() {
448    string conffile = GMENU2X_SYSTEM_DIR "/gmenu2x.conf";
449    readConfig(conffile);
450
451    conffile = getHome() + "/gmenu2x.conf";
452    readConfig(conffile);
453}
454
455void GMenu2X::readConfig(string conffile) {
456    ifstream inf(conffile.c_str(), ios_base::in);
457    if (inf.is_open()) {
458        string line;
459        while (getline(inf, line, '\n')) {
460            string::size_type pos = line.find("=");
461            string name = trim(line.substr(0,pos));
462            string value = trim(line.substr(pos+1,line.length()));
463
464            if (value.length()>1 && value.at(0)=='"' && value.at(value.length()-1)=='"')
465                confStr[name] = value.substr(1,value.length()-2);
466            else
467                confInt[name] = atoi(value.c_str());
468        }
469        inf.close();
470    }
471
472    if (!confStr["lang"].empty())
473        tr.setLang(confStr["lang"]);
474
475    if (!confStr["wallpaper"].empty() && !fileExists(confStr["wallpaper"]))
476        confStr["wallpaper"] = "";
477
478    if (confStr["skin"].empty() || SurfaceCollection::getSkinPath(confStr["skin"]).empty())
479        confStr["skin"] = "Default";
480
481    evalIntConf( confInt, "outputLogs", 0, 0,1 );
482#ifdef ENABLE_CPUFREQ
483    evalIntConf( confInt, "maxClock",
484                 cpuFreqSafeMax, cpuFreqMin, cpuFreqMax );
485    evalIntConf( confInt, "menuClock",
486                 cpuFreqMenuDefault, cpuFreqMin, cpuFreqSafeMax );
487#endif
488    evalIntConf( confInt, "backlightTimeout", 15, 0,120 );
489    evalIntConf( confInt, "buttonRepeatRate", 10, 0, 20 );
490    evalIntConf( confInt, "videoBpp", 32, 16, 32 );
491
492    if (confStr["tvoutEncoding"] != "PAL") confStr["tvoutEncoding"] = "NTSC";
493    resX = constrain( confInt["resolutionX"], 320,1920 );
494    resY = constrain( confInt["resolutionY"], 240,1200 );
495}
496
497void GMenu2X::saveSelection() {
498    if (confInt["saveSelection"] && (
499            confInt["section"] != menu->selSectionIndex()
500            || confInt["link"] != menu->selLinkIndex()
501    )) {
502        writeConfig();
503    }
504}
505
506void GMenu2X::writeConfig() {
507    string conffile = getHome() + "/gmenu2x.conf";
508    ofstream inf(conffile.c_str());
509    if (inf.is_open()) {
510        ConfStrHash::iterator endS = confStr.end();
511        for(ConfStrHash::iterator curr = confStr.begin(); curr != endS; curr++)
512            inf << curr->first << "=\"" << curr->second << "\"" << endl;
513
514        ConfIntHash::iterator endI = confInt.end();
515        for(ConfIntHash::iterator curr = confInt.begin(); curr != endI; curr++)
516            inf << curr->first << "=" << curr->second << endl;
517
518        inf.close();
519    }
520}
521
522void GMenu2X::writeSkinConfig() {
523    string conffile = getHome() + "/skins/";
524    if (mkdir(conffile.c_str(), 0770) < 0 && errno != EEXIST) {
525        ERROR("Failed to create directory %s to write skin configuration: %s\n", conffile.c_str(), strerror(errno));
526        return;
527    }
528    conffile = conffile + confStr["skin"];
529    if (mkdir(conffile.c_str(), 0770) < 0 && errno != EEXIST) {
530        ERROR("Failed to create directory %s to write skin configuration: %s\n", conffile.c_str(), strerror(errno));
531        return;
532    }
533    conffile = conffile + "/skin.conf";
534
535    ofstream inf(conffile.c_str());
536    if (inf.is_open()) {
537        ConfStrHash::iterator endS = skinConfStr.end();
538        for(ConfStrHash::iterator curr = skinConfStr.begin(); curr != endS; curr++)
539            inf << curr->first << "=\"" << curr->second << "\"" << endl;
540
541        ConfIntHash::iterator endI = skinConfInt.end();
542        for(ConfIntHash::iterator curr = skinConfInt.begin(); curr != endI; curr++)
543            inf << curr->first << "=" << curr->second << endl;
544
545        int i;
546        for (i = 0; i < NUM_COLORS; ++i) {
547            inf << colorToString((enum color)i) << "=#"
548                << skinConfColors[i] << endl;
549        }
550
551        inf.close();
552    }
553}
554
555void GMenu2X::readTmp() {
556    lastSelectorElement = -1;
557    ifstream inf("/tmp/gmenu2x.tmp", ios_base::in);
558    if (inf.is_open()) {
559        string line;
560        string section = "";
561        while (getline(inf, line, '\n')) {
562            string::size_type pos = line.find("=");
563            string name = trim(line.substr(0,pos));
564            string value = trim(line.substr(pos+1,line.length()));
565
566            if (name=="section")
567                menu->setSectionIndex(atoi(value.c_str()));
568            else if (name=="link")
569                menu->setLinkIndex(atoi(value.c_str()));
570            else if (name=="selectorelem")
571                lastSelectorElement = atoi(value.c_str());
572            else if (name=="selectordir")
573                lastSelectorDir = value;
574        }
575        inf.close();
576    }
577}
578
579void GMenu2X::writeTmp(int selelem, const string &selectordir) {
580    string conffile = "/tmp/gmenu2x.tmp";
581    ofstream inf(conffile.c_str());
582    if (inf.is_open()) {
583        inf << "section=" << menu->selSectionIndex() << endl;
584        inf << "link=" << menu->selLinkIndex() << endl;
585        if (selelem>-1)
586            inf << "selectorelem=" << selelem << endl;
587        if (!selectordir.empty())
588            inf << "selectordir=" << selectordir << endl;
589        inf.close();
590    }
591}
592
593void GMenu2X::mainLoop() {
594    if (!fileExists(CARD_ROOT))
595        CARD_ROOT = "";
596
597    // Recover last session
598    readTmp();
599    if (lastSelectorElement > -1 && menu->selLinkApp() &&
600                (!menu->selLinkApp()->getSelectorDir().empty()
601                 || !lastSelectorDir.empty()))
602        menu->selLinkApp()->selector(lastSelectorElement, lastSelectorDir);
603
604    while (true) {
605        // Remove dismissed layers from the stack.
606        for (auto it = layers.begin(); it != layers.end(); ) {
607            if ((*it)->getStatus() == Layer::Status::DISMISSED) {
608                it = layers.erase(it);
609            } else {
610                ++it;
611            }
612        }
613
614        // Run animations.
615        bool animating = false;
616        for (auto layer : layers) {
617            animating |= layer->runAnimations();
618        }
619
620        // Paint layers.
621        for (auto layer : layers) {
622            layer->paint(*s);
623        }
624        s->flip();
625
626        // Exit main loop once we have something to launch.
627        if (toLaunch) {
628            break;
629        }
630
631        // Handle other input events.
632        InputManager::Button button;
633        bool gotEvent;
634        const bool wait = !animating;
635        do {
636            gotEvent = input.getButton(&button, wait);
637        } while (wait && !gotEvent);
638        if (gotEvent) {
639            for (auto it = layers.rbegin(); it != layers.rend(); ++it) {
640                if ((*it)->handleButtonPress(button)) {
641                    break;
642                }
643            }
644        }
645    }
646}
647
648void GMenu2X::explorer() {
649    FileDialog fd(*this, tr["Select an application"], "sh,bin,py,elf,");
650    if (fd.exec()) {
651        if (confInt["saveSelection"] && (confInt["section"]!=menu->selSectionIndex() || confInt["link"]!=menu->selLinkIndex()))
652            writeConfig();
653
654        string command = cmdclean(fd.getPath()+"/"+fd.getFile());
655        chdir(fd.getPath().c_str());
656#ifdef ENABLE_CPUFREQ
657        setClock(cpuFreqAppDefault);
658#endif
659
660        toLaunch.reset(new Launcher(
661                vector<string> { "/bin/sh", "-c", command }));
662    }
663}
664
665void GMenu2X::queueLaunch(
666    unique_ptr<Launcher>&& launcher, shared_ptr<Layer> launchLayer
667) {
668    toLaunch = move(launcher);
669    layers.push_back(launchLayer);
670}
671
672void GMenu2X::showHelpPopup() {
673    layers.push_back(make_shared<HelpPopup>(*this));
674}
675
676void GMenu2X::showSettings() {
677#ifdef ENABLE_CPUFREQ
678    int curMenuClock = confInt["menuClock"];
679#endif
680
681    FileLister fl_tr;
682    fl_tr.setShowDirectories(false);
683    fl_tr.browse(GMENU2X_SYSTEM_DIR "/translations");
684    fl_tr.browse(getHome() + "/translations", false);
685
686    vector<string> translations = fl_tr.getFiles();
687    translations.insert(translations.begin(), "English");
688    string lang = tr.lang();
689
690    vector<string> encodings;
691    encodings.push_back("NTSC");
692    encodings.push_back("PAL");
693
694    SettingsDialog sd(*this, input, tr["Settings"]);
695    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingMultiString(
696            *this, tr["Language"],
697            tr["Set the language used by GMenu2X"],
698            &lang, &translations)));
699    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingBool(
700            *this, tr["Save last selection"],
701            tr["Save the last selected link and section on exit"],
702            &confInt["saveSelection"])));
703#ifdef ENABLE_CPUFREQ
704    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
705            *this, tr["Clock for GMenu2X"],
706            tr["Set the cpu working frequency when running GMenu2X"],
707            &confInt["menuClock"], cpuFreqMin, cpuFreqSafeMax, cpuFreqMultiple)));
708    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
709            *this, tr["Maximum overclock"],
710            tr["Set the maximum overclock for launching links"],
711            &confInt["maxClock"], cpuFreqMin, cpuFreqMax, cpuFreqMultiple)));
712#endif
713    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingBool(
714            *this, tr["Output logs"],
715            tr["Logs the output of the links. Use the Log Viewer to read them."],
716            &confInt["outputLogs"])));
717    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
718            *this, tr["Screen Timeout"],
719            tr["Set screen's backlight timeout in seconds"],
720            &confInt["backlightTimeout"], 0, 120)));
721    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
722            *this, tr["Button repeat rate"],
723            tr["Set button repetitions per second"],
724            &confInt["buttonRepeatRate"], 0, 20)));
725
726    if (sd.exec()) {
727#ifdef ENABLE_CPUFREQ
728        if (curMenuClock != confInt["menuClock"]) setClock(confInt["menuClock"]);
729#endif
730
731        powerSaver.setScreenTimeout(confInt["backlightTimeout"]);
732
733        input.repeatRateChanged();
734
735        if (lang == "English") lang = "";
736        if (lang != tr.lang()) {
737            tr.setLang(lang);
738            confStr["lang"] = lang;
739        }
740
741        writeConfig();
742    }
743}
744
745void GMenu2X::skinMenu() {
746    FileLister fl_sk;
747    fl_sk.setShowFiles(false);
748    fl_sk.setShowUpdir(false);
749    fl_sk.browse(getHome() + "/skins");
750    fl_sk.browse(GMENU2X_SYSTEM_DIR "/skins", false);
751
752    string curSkin = confStr["skin"];
753
754    SettingsDialog sd(*this, input, tr["Skin"]);
755    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingMultiString(
756            *this, tr["Skin"],
757            tr["Set the skin used by GMenu2X"],
758            &confStr["skin"], &fl_sk.getDirectories())));
759    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
760            *this, tr["Top Bar"],
761            tr["Color of the top bar"],
762            &skinConfColors[COLOR_TOP_BAR_BG])));
763    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
764            *this, tr["Bottom Bar"],
765            tr["Color of the bottom bar"],
766            &skinConfColors[COLOR_BOTTOM_BAR_BG])));
767    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
768            *this, tr["Selection"],
769            tr["Color of the selection and other interface details"],
770            &skinConfColors[COLOR_SELECTION_BG])));
771    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
772            *this, tr["Message Box"],
773            tr["Background color of the message box"],
774            &skinConfColors[COLOR_MESSAGE_BOX_BG])));
775    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
776            *this, tr["Message Box Border"],
777            tr["Border color of the message box"],
778            &skinConfColors[COLOR_MESSAGE_BOX_BORDER])));
779    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
780            *this, tr["Message Box Selection"],
781            tr["Color of the selection of the message box"],
782            &skinConfColors[COLOR_MESSAGE_BOX_SELECTION])));
783
784    if (sd.exec()) {
785        if (curSkin != confStr["skin"]) {
786            setSkin(confStr["skin"]);
787            writeConfig();
788        }
789        writeSkinConfig();
790        initBG();
791    }
792}
793
794void GMenu2X::setSkin(const string &skin, bool setWallpaper) {
795    confStr["skin"] = skin;
796
797    //Clear previous skin settings
798    skinConfStr.clear();
799    skinConfInt.clear();
800
801    DEBUG("GMenu2X: setting new skin %s.\n", skin.c_str());
802
803    //clear collection and change the skin path
804    sc.clear();
805    sc.setSkin(skin);
806
807    //reset colors to the default values
808    skinConfColors[COLOR_TOP_BAR_BG] = RGBAColor(255, 255, 255, 130);
809    skinConfColors[COLOR_BOTTOM_BAR_BG] = RGBAColor(255, 255, 255, 130);
810    skinConfColors[COLOR_SELECTION_BG] = RGBAColor(255, 255, 255, 130);
811    skinConfColors[COLOR_MESSAGE_BOX_BG] = RGBAColor(255, 255, 255);
812    skinConfColors[COLOR_MESSAGE_BOX_BORDER] = RGBAColor(80, 80, 80);
813    skinConfColors[COLOR_MESSAGE_BOX_SELECTION] = RGBAColor(160, 160, 160);
814
815    /* Load skin settings from user directory if present,
816     * or from the system directory. */
817    if (!readSkinConfig(getHome() + "/skins/" + skin + "/skin.conf")) {
818        readSkinConfig(GMENU2X_SYSTEM_DIR "/skins/" + skin + "/skin.conf");
819    }
820
821    if (setWallpaper && !skinConfStr["wallpaper"].empty()) {
822        string fp = sc.getSkinFilePath("wallpapers/" + skinConfStr["wallpaper"]);
823        if (!fp.empty())
824            confStr["wallpaper"] = fp;
825        else
826            WARNING("Unable to find wallpaper defined on skin %s\n", skin.c_str());
827    }
828
829    evalIntConf(skinConfInt, "topBarHeight", 50, 32, 120);
830    evalIntConf(skinConfInt, "bottomBarHeight", 20, 20, 120);
831    evalIntConf(skinConfInt, "linkHeight", 50, 32, 120);
832    evalIntConf(skinConfInt, "linkWidth", 80, 32, 120);
833
834    if (menu != NULL) menu->skinUpdated();
835
836    //Selection png
837    useSelectionPng = sc.addSkinRes("imgs/selection.png", false) != NULL;
838
839    //font
840    initFont();
841}
842
843bool GMenu2X::readSkinConfig(const string& conffile)
844{
845    ifstream skinconf(conffile.c_str(), ios_base::in);
846    if (skinconf.is_open()) {
847        string line;
848        while (getline(skinconf, line, '\n')) {
849            line = trim(line);
850            DEBUG("skinconf: '%s'\n", line.c_str());
851            string::size_type pos = line.find("=");
852            string name = trim(line.substr(0,pos));
853            string value = trim(line.substr(pos+1,line.length()));
854
855            if (value.length()>0) {
856                if (value.length()>1 && value.at(0)=='"' && value.at(value.length()-1)=='"')
857                    skinConfStr[name] = value.substr(1,value.length()-2);
858                else if (value.at(0) == '#')
859                    skinConfColors[stringToColor(name)] =
860                        RGBAColor::fromString(value.substr(1, value.length()));
861                else
862                    skinConfInt[name] = atoi(value.c_str());
863            }
864        }
865        skinconf.close();
866        return true;
867    } else {
868        return false;
869    }
870}
871
872void GMenu2X::showManual() {
873    menu->selLinkApp()->showManual();
874}
875
876void GMenu2X::showContextMenu() {
877    layers.push_back(make_shared<ContextMenu>(*this, *menu));
878}
879
880void GMenu2X::changeWallpaper() {
881    WallpaperDialog wp(*this);
882    if (wp.exec() && confStr["wallpaper"] != wp.wallpaper) {
883        confStr["wallpaper"] = wp.wallpaper;
884        initBG();
885        writeConfig();
886    }
887}
888
889void GMenu2X::addLink() {
890    FileDialog fd(*this, tr["Select an application"], "sh,bin,py,elf,");
891    if (fd.exec())
892        menu->addLink(fd.getPath(), fd.getFile());
893}
894
895void GMenu2X::editLink() {
896    LinkApp *linkApp = menu->selLinkApp();
897    if (!linkApp) return;
898
899    vector<string> pathV;
900    split(pathV,linkApp->getFile(),"/");
901    string oldSection = "";
902    if (pathV.size()>1)
903        oldSection = pathV[pathV.size()-2];
904    string newSection = oldSection;
905
906    string linkTitle = linkApp->getTitle();
907    string linkDescription = linkApp->getDescription();
908    string linkIcon = linkApp->getIcon();
909    string linkManual = linkApp->getManual();
910    string linkSelFilter = linkApp->getSelectorFilter();
911    string linkSelDir = linkApp->getSelectorDir();
912    bool linkSelBrowser = linkApp->getSelectorBrowser();
913    int linkClock = linkApp->clock();
914
915    string diagTitle = tr.translate("Edit $1",linkTitle.c_str(),NULL);
916    string diagIcon = linkApp->getIconPath();
917
918    SettingsDialog sd(*this, input, diagTitle, diagIcon);
919    if (!linkApp->isOpk()) {
920        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingString(
921                *this, tr["Title"],
922                tr["Link title"],
923                &linkTitle, diagTitle, diagIcon)));
924        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingString(
925                *this, tr["Description"],
926                tr["Link description"],
927                &linkDescription, diagTitle, diagIcon)));
928        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingMultiString(
929                *this, tr["Section"],
930                tr["The section this link belongs to"],
931                &newSection, &menu->getSections())));
932        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingImage(
933                *this, tr["Icon"],
934                tr.translate("Select an icon for this link", linkTitle.c_str(), NULL),
935                &linkIcon, "png")));
936        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingFile(
937                *this, tr["Manual"],
938                tr["Select a manual or README file"],
939                &linkManual, "man.png,txt")));
940    }
941    if (!linkApp->isOpk() || !linkApp->getSelectorDir().empty()) {
942        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingDir(
943                *this, tr["Selector Directory"],
944                tr["Directory to scan for the selector"],
945                &linkSelDir)));
946        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingBool(
947                *this, tr["Selector Browser"],
948                tr["Allow the selector to change directory"],
949                &linkSelBrowser)));
950    }
951#ifdef ENABLE_CPUFREQ
952    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
953            *this, tr["Clock frequency"],
954            tr["CPU clock frequency for this link"],
955            &linkClock, cpuFreqMin, confInt["maxClock"], cpuFreqMultiple)));
956#endif
957    if (!linkApp->isOpk()) {
958        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingString(
959                *this, tr["Selector Filter"],
960                tr["Selector filter (Separate values with a comma)"],
961                &linkSelFilter, diagTitle, diagIcon)));
962        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingBool(
963                *this, tr["Display Console"],
964                tr["Must be enabled for console-based applications"],
965                &linkApp->consoleApp)));
966    }
967
968    if (sd.exec()) {
969        linkApp->setTitle(linkTitle);
970        linkApp->setDescription(linkDescription);
971        linkApp->setIcon(linkIcon);
972        linkApp->setManual(linkManual);
973        linkApp->setSelectorFilter(linkSelFilter);
974        linkApp->setSelectorDir(linkSelDir);
975        linkApp->setSelectorBrowser(linkSelBrowser);
976        linkApp->setClock(linkClock);
977
978        INFO("New Section: '%s'\n", newSection.c_str());
979
980        //if section changed move file and update link->file
981        if (oldSection!=newSection) {
982            vector<string>::const_iterator newSectionIndex = find(menu->getSections().begin(),menu->getSections().end(),newSection);
983            if (newSectionIndex==menu->getSections().end()) return;
984            string newFileName = "sections/"+newSection+"/"+linkTitle;
985            uint x=2;
986            while (fileExists(newFileName)) {
987                string id = "";
988                stringstream ss; ss << x; ss >> id;
989                newFileName = "sections/"+newSection+"/"+linkTitle+id;
990                x++;
991            }
992            rename(linkApp->getFile().c_str(),newFileName.c_str());
993            linkApp->renameFile(newFileName);
994
995            INFO("New section index: %i.\n", newSectionIndex - menu->getSections().begin());
996
997            menu->linkChangeSection(menu->selLinkIndex(), menu->selSectionIndex(), newSectionIndex - menu->getSections().begin());
998        }
999        linkApp->save();
1000    }
1001}
1002
1003void GMenu2X::deleteLink() {
1004    if (menu->selLinkApp()!=NULL) {
1005        MessageBox mb(*this, tr.translate("Deleting $1",menu->selLink()->getTitle().c_str(),NULL)+"\n"+tr["Are you sure?"], menu->selLink()->getIconPath());
1006        mb.setButton(InputManager::ACCEPT, tr["Yes"]);
1007        mb.setButton(InputManager::CANCEL, tr["No"]);
1008        if (mb.exec() == InputManager::ACCEPT)
1009            menu->deleteSelectedLink();
1010    }
1011}
1012
1013void GMenu2X::addSection() {
1014    InputDialog id(*this, input, tr["Insert a name for the new section"]);
1015    if (id.exec()) {
1016        // Look up section; create if it doesn't exist yet.
1017        auto idx = menu->sectionNamed(id.getInput());
1018        // Switch to the new section.
1019        menu->setSectionIndex(idx);
1020    }
1021}
1022
1023void GMenu2X::deleteSection() {
1024    MessageBox mb(*this,tr["You will lose all the links in this section."]+"\n"+tr["Are you sure?"]);
1025    mb.setButton(InputManager::ACCEPT, tr["Yes"]);
1026    mb.setButton(InputManager::CANCEL, tr["No"]);
1027    if (mb.exec() == InputManager::ACCEPT) {
1028
1029        if (rmtree(getHome() + "/sections/" + menu->selSection()))
1030            menu->deleteSelectedSection();
1031    }
1032}
1033
1034typedef struct {
1035    unsigned short batt;
1036    unsigned short remocon;
1037} MMSP2ADC;
1038
1039#ifdef ENABLE_CPUFREQ
1040void GMenu2X::setClock(unsigned mhz) {
1041    mhz = constrain(mhz, cpuFreqMin, confInt["maxClock"]);
1042#if defined(PLATFORM_A320) || defined(PLATFORM_GCW0) || defined(PLATFORM_NANONOTE)
1043    jz_cpuspeed(mhz);
1044#endif
1045}
1046#endif
1047
1048string GMenu2X::getDiskFree(const char *path) {
1049    string df = "";
1050    struct statvfs b;
1051
1052    int ret = statvfs(path, &b);
1053    if (ret == 0) {
1054        // Make sure that the multiplication happens in 64 bits.
1055        unsigned long freeMiB =
1056                ((unsigned long long)b.f_bfree * b.f_bsize) / (1024 * 1024);
1057        unsigned long totalMiB =
1058                ((unsigned long long)b.f_blocks * b.f_frsize) / (1024 * 1024);
1059        stringstream ss;
1060        if (totalMiB >= 10000) {
1061            ss << (freeMiB / 1024) << "." << ((freeMiB % 1024) * 10) / 1024 << "/"
1062               << (totalMiB / 1024) << "." << ((totalMiB % 1024) * 10) / 1024 << "GiB";
1063        } else {
1064            ss << freeMiB << "/" << totalMiB << "MiB";
1065        }
1066        ss >> df;
1067    } else WARNING("statvfs failed with error '%s'.\n", strerror(errno));
1068    return df;
1069}
1070
1071int GMenu2X::drawButton(Surface& s, const string &btn, const string &text, int x, int y) {
1072    int w = 0;
1073    auto icon = sc["skin:imgs/buttons/" + btn + ".png"];
1074    if (icon) {
1075        if (y < 0) y = resY + y;
1076        w = icon->width();
1077        icon->blit(s, x, y - 7);
1078        if (!text.empty()) {
1079            w += 3;
1080            w += font->write(
1081                    s, text, x + w, y, Font::HAlignLeft, Font::VAlignMiddle);
1082            w += 6;
1083        }
1084    }
1085    return x + w;
1086}
1087
1088int GMenu2X::drawButtonRight(Surface& s, const string &btn, const string &text, int x, int y) {
1089    int w = 0;
1090    auto icon = sc["skin:imgs/buttons/" + btn + ".png"];
1091    if (icon) {
1092        if (y < 0) y = resY + y;
1093        w = icon->width();
1094        icon->blit(s, x - w, y - 7);
1095        if (!text.empty()) {
1096            w += 3;
1097            w += font->write(
1098                    s, text, x - w, y, Font::HAlignRight, Font::VAlignMiddle);
1099            w += 6;
1100        }
1101    }
1102    return x - w;
1103}
1104
1105void GMenu2X::drawScrollBar(uint pageSize, uint totalSize, uint pagePos) {
1106    if (totalSize <= pageSize) {
1107        // Everything fits on one screen, no scroll bar needed.
1108        return;
1109    }
1110
1111    unsigned int top, height;
1112    tie(top, height) = getContentArea();
1113    top += 1;
1114    height -= 2;
1115
1116    s->rectangle(resX - 8, top, 7, height, skinConfColors[COLOR_SELECTION_BG]);
1117    top += 2;
1118    height -= 4;
1119
1120    const uint barSize = max(height * pageSize / totalSize, 4u);
1121    const uint barPos = (height - barSize) * pagePos / (totalSize - pageSize);
1122
1123    s->box(resX - 6, top + barPos, 3, barSize,
1124            skinConfColors[COLOR_SELECTION_BG]);
1125}
1126
1127void GMenu2X::drawTopBar(Surface& s) {
1128    Surface *bar = sc.skinRes("imgs/topbar.png", false);
1129    if (bar) {
1130        bar->blit(s, 0, 0);
1131    } else {
1132        const int h = skinConfInt["topBarHeight"];
1133        s.box(0, 0, resX, h, skinConfColors[COLOR_TOP_BAR_BG]);
1134    }
1135}
1136
1137void GMenu2X::drawBottomBar(Surface& s) {
1138    Surface *bar = sc.skinRes("imgs/bottombar.png", false);
1139    if (bar) {
1140        bar->blit(s, 0, resY-bar->height());
1141    } else {
1142        const int h = skinConfInt["bottomBarHeight"];
1143        s.box(0, resY - h, resX, h, skinConfColors[COLOR_BOTTOM_BAR_BG]);
1144    }
1145}
1146

Archive Download this file



interactive