Root/src/gmenu2x.cpp

Source at commit 9394a95a6c43f853ce615706b8ff30992489d6cc created 5 years 5 months ago.
By Maarten ter Huurne, Fixed formatting string type mismatches
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    (void)serviceX;
360
361    bgmain->convertToDisplayFormat();
362}
363
364void GMenu2X::initFont() {
365    string path = skinConfStr["font"];
366    if (!path.empty()) {
367        unsigned int size = skinConfInt["fontsize"];
368        if (!size)
369            size = 12;
370        if (path.substr(0,5)=="skin:")
371            path = sc.getSkinFilePath(path.substr(5, path.length()));
372        font.reset(new Font(path, size));
373    } else {
374        font = Font::defaultFont();
375    }
376}
377
378void GMenu2X::initMenu() {
379    //Menu structure handler
380    menu.reset(new Menu(*this));
381
382    // Add action links in the applications section.
383    auto appIdx = menu->sectionNamed("applications");
384    menu->addActionLink(appIdx, "Explorer",
385            bind(&GMenu2X::explorer, this),
386            tr["Launch an application"],
387            "skin:icons/explorer.png");
388
389    // Add action links in the settings section.
390    auto settingIdx = menu->sectionNamed("settings");
391    menu->addActionLink(settingIdx, "GMenu2X",
392            bind(&GMenu2X::showSettings, this),
393            tr["Configure GMenu2X's options"],
394            "skin:icons/configure.png");
395    menu->addActionLink(settingIdx, tr["Skin"],
396            bind(&GMenu2X::skinMenu, this),
397            tr["Configure skin"],
398            "skin:icons/skin.png");
399    menu->addActionLink(settingIdx, tr["Wallpaper"],
400            bind(&GMenu2X::changeWallpaper, this),
401            tr["Change GMenu2X wallpaper"],
402            "skin:icons/wallpaper.png");
403    if (fileExists(LOG_FILE)) {
404        menu->addActionLink(settingIdx, tr["Log Viewer"],
405                bind(&GMenu2X::viewLog, this),
406                tr["Displays last launched program's output"],
407                "skin:icons/ebook.png");
408    }
409    menu->addActionLink(settingIdx, tr["About"],
410            bind(&GMenu2X::about, this),
411            tr["Info about GMenu2X"],
412            "skin:icons/about.png");
413
414    menu->skinUpdated();
415    menu->orderLinks();
416
417    menu->setSectionIndex(confInt["section"]);
418    menu->setLinkIndex(confInt["link"]);
419
420    layers.push_back(menu);
421}
422
423void GMenu2X::about() {
424    string text(readFileAsString(GMENU2X_SYSTEM_DIR "/about.txt"));
425    string build_date("Build date: " __DATE__);
426    TextDialog td(*this, "GMenu2X", build_date, "icons/about.png", text);
427    td.exec();
428}
429
430void GMenu2X::viewLog() {
431    string text(readFileAsString(LOG_FILE));
432
433    TextDialog td(*this, tr["Log Viewer"],
434            tr["Displays last launched program's output"],
435            "icons/ebook.png", text);
436    td.exec();
437
438    MessageBox mb(*this, tr["Do you want to delete the log file?"],
439             "icons/ebook.png");
440    mb.setButton(InputManager::ACCEPT, tr["Yes"]);
441    mb.setButton(InputManager::CANCEL, tr["No"]);
442    if (mb.exec() == InputManager::ACCEPT) {
443        unlink(LOG_FILE);
444        menu->deleteSelectedLink();
445    }
446}
447
448void GMenu2X::readConfig() {
449    string conffile = GMENU2X_SYSTEM_DIR "/gmenu2x.conf";
450    readConfig(conffile);
451
452    conffile = getHome() + "/gmenu2x.conf";
453    readConfig(conffile);
454}
455
456void GMenu2X::readConfig(string conffile) {
457    ifstream inf(conffile.c_str(), ios_base::in);
458    if (inf.is_open()) {
459        string line;
460        while (getline(inf, line, '\n')) {
461            string::size_type pos = line.find("=");
462            string name = trim(line.substr(0,pos));
463            string value = trim(line.substr(pos+1,line.length()));
464
465            if (value.length()>1 && value.at(0)=='"' && value.at(value.length()-1)=='"')
466                confStr[name] = value.substr(1,value.length()-2);
467            else
468                confInt[name] = atoi(value.c_str());
469        }
470        inf.close();
471    }
472
473    if (!confStr["lang"].empty())
474        tr.setLang(confStr["lang"]);
475
476    if (!confStr["wallpaper"].empty() && !fileExists(confStr["wallpaper"]))
477        confStr["wallpaper"] = "";
478
479    if (confStr["skin"].empty() || SurfaceCollection::getSkinPath(confStr["skin"]).empty())
480        confStr["skin"] = "Default";
481
482    evalIntConf( confInt, "outputLogs", 0, 0,1 );
483#ifdef ENABLE_CPUFREQ
484    evalIntConf( confInt, "maxClock",
485                 cpuFreqSafeMax, cpuFreqMin, cpuFreqMax );
486    evalIntConf( confInt, "menuClock",
487                 cpuFreqMenuDefault, cpuFreqMin, cpuFreqSafeMax );
488#endif
489    evalIntConf( confInt, "backlightTimeout", 15, 0,120 );
490    evalIntConf( confInt, "buttonRepeatRate", 10, 0, 20 );
491    evalIntConf( confInt, "videoBpp", 32, 16, 32 );
492
493    if (confStr["tvoutEncoding"] != "PAL") confStr["tvoutEncoding"] = "NTSC";
494    resX = constrain( confInt["resolutionX"], 320,1920 );
495    resY = constrain( confInt["resolutionY"], 240,1200 );
496}
497
498void GMenu2X::saveSelection() {
499    if (confInt["saveSelection"] && (
500            confInt["section"] != menu->selSectionIndex()
501            || confInt["link"] != menu->selLinkIndex()
502    )) {
503        writeConfig();
504    }
505}
506
507void GMenu2X::writeConfig() {
508    string conffile = getHome() + "/gmenu2x.conf";
509    ofstream inf(conffile.c_str());
510    if (inf.is_open()) {
511        ConfStrHash::iterator endS = confStr.end();
512        for(ConfStrHash::iterator curr = confStr.begin(); curr != endS; curr++)
513            inf << curr->first << "=\"" << curr->second << "\"" << endl;
514
515        ConfIntHash::iterator endI = confInt.end();
516        for(ConfIntHash::iterator curr = confInt.begin(); curr != endI; curr++)
517            inf << curr->first << "=" << curr->second << endl;
518
519        inf.close();
520    }
521}
522
523void GMenu2X::writeSkinConfig() {
524    string conffile = getHome() + "/skins/";
525    if (mkdir(conffile.c_str(), 0770) < 0 && errno != EEXIST) {
526        ERROR("Failed to create directory %s to write skin configuration: %s\n", conffile.c_str(), strerror(errno));
527        return;
528    }
529    conffile = conffile + confStr["skin"];
530    if (mkdir(conffile.c_str(), 0770) < 0 && errno != EEXIST) {
531        ERROR("Failed to create directory %s to write skin configuration: %s\n", conffile.c_str(), strerror(errno));
532        return;
533    }
534    conffile = conffile + "/skin.conf";
535
536    ofstream inf(conffile.c_str());
537    if (inf.is_open()) {
538        ConfStrHash::iterator endS = skinConfStr.end();
539        for(ConfStrHash::iterator curr = skinConfStr.begin(); curr != endS; curr++)
540            inf << curr->first << "=\"" << curr->second << "\"" << endl;
541
542        ConfIntHash::iterator endI = skinConfInt.end();
543        for(ConfIntHash::iterator curr = skinConfInt.begin(); curr != endI; curr++)
544            inf << curr->first << "=" << curr->second << endl;
545
546        int i;
547        for (i = 0; i < NUM_COLORS; ++i) {
548            inf << colorToString((enum color)i) << "=#"
549                << skinConfColors[i] << endl;
550        }
551
552        inf.close();
553    }
554}
555
556void GMenu2X::readTmp() {
557    lastSelectorElement = -1;
558    ifstream inf("/tmp/gmenu2x.tmp", ios_base::in);
559    if (inf.is_open()) {
560        string line;
561        string section = "";
562        while (getline(inf, line, '\n')) {
563            string::size_type pos = line.find("=");
564            string name = trim(line.substr(0,pos));
565            string value = trim(line.substr(pos+1,line.length()));
566
567            if (name=="section")
568                menu->setSectionIndex(atoi(value.c_str()));
569            else if (name=="link")
570                menu->setLinkIndex(atoi(value.c_str()));
571            else if (name=="selectorelem")
572                lastSelectorElement = atoi(value.c_str());
573            else if (name=="selectordir")
574                lastSelectorDir = value;
575        }
576        inf.close();
577    }
578}
579
580void GMenu2X::writeTmp(int selelem, const string &selectordir) {
581    string conffile = "/tmp/gmenu2x.tmp";
582    ofstream inf(conffile.c_str());
583    if (inf.is_open()) {
584        inf << "section=" << menu->selSectionIndex() << endl;
585        inf << "link=" << menu->selLinkIndex() << endl;
586        if (selelem>-1)
587            inf << "selectorelem=" << selelem << endl;
588        if (!selectordir.empty())
589            inf << "selectordir=" << selectordir << endl;
590        inf.close();
591    }
592}
593
594void GMenu2X::mainLoop() {
595    if (!fileExists(CARD_ROOT))
596        CARD_ROOT = "";
597
598    // Recover last session
599    readTmp();
600    if (lastSelectorElement > -1 && menu->selLinkApp() &&
601                (!menu->selLinkApp()->getSelectorDir().empty()
602                 || !lastSelectorDir.empty()))
603        menu->selLinkApp()->selector(lastSelectorElement, lastSelectorDir);
604
605    while (true) {
606        // Remove dismissed layers from the stack.
607        for (auto it = layers.begin(); it != layers.end(); ) {
608            if ((*it)->getStatus() == Layer::Status::DISMISSED) {
609                it = layers.erase(it);
610            } else {
611                ++it;
612            }
613        }
614
615        // Run animations.
616        bool animating = false;
617        for (auto layer : layers) {
618            animating |= layer->runAnimations();
619        }
620
621        // Paint layers.
622        for (auto layer : layers) {
623            layer->paint(*s);
624        }
625        s->flip();
626
627        // Exit main loop once we have something to launch.
628        if (toLaunch) {
629            break;
630        }
631
632        // Handle other input events.
633        InputManager::Button button;
634        bool gotEvent;
635        const bool wait = !animating;
636        do {
637            gotEvent = input.getButton(&button, wait);
638        } while (wait && !gotEvent);
639        if (gotEvent) {
640            if (button == InputManager::QUIT) {
641                break;
642            }
643            for (auto it = layers.rbegin(); it != layers.rend(); ++it) {
644                if ((*it)->handleButtonPress(button)) {
645                    break;
646                }
647            }
648        }
649    }
650}
651
652void GMenu2X::explorer() {
653    FileDialog fd(*this, tr["Select an application"], "sh,bin,py,elf,");
654    if (fd.exec()) {
655        if (confInt["saveSelection"] && (confInt["section"]!=menu->selSectionIndex() || confInt["link"]!=menu->selLinkIndex()))
656            writeConfig();
657
658        string command = cmdclean(fd.getPath()+"/"+fd.getFile());
659        chdir(fd.getPath().c_str());
660#ifdef ENABLE_CPUFREQ
661        setClock(cpuFreqAppDefault);
662#endif
663
664        toLaunch.reset(new Launcher(
665                vector<string> { "/bin/sh", "-c", command }));
666    }
667}
668
669void GMenu2X::queueLaunch(
670    unique_ptr<Launcher>&& launcher, shared_ptr<Layer> launchLayer
671) {
672    toLaunch = move(launcher);
673    layers.push_back(launchLayer);
674}
675
676void GMenu2X::showHelpPopup() {
677    layers.push_back(make_shared<HelpPopup>(*this));
678}
679
680void GMenu2X::showSettings() {
681#ifdef ENABLE_CPUFREQ
682    int curMenuClock = confInt["menuClock"];
683#endif
684
685    FileLister fl_tr;
686    fl_tr.setShowDirectories(false);
687    fl_tr.browse(GMENU2X_SYSTEM_DIR "/translations");
688    fl_tr.browse(getHome() + "/translations", false);
689
690    vector<string> translations = fl_tr.getFiles();
691    translations.insert(translations.begin(), "English");
692    string lang = tr.lang();
693
694    vector<string> encodings;
695    encodings.push_back("NTSC");
696    encodings.push_back("PAL");
697
698    SettingsDialog sd(*this, input, tr["Settings"]);
699    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingMultiString(
700            *this, tr["Language"],
701            tr["Set the language used by GMenu2X"],
702            &lang, &translations)));
703    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingBool(
704            *this, tr["Save last selection"],
705            tr["Save the last selected link and section on exit"],
706            &confInt["saveSelection"])));
707#ifdef ENABLE_CPUFREQ
708    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
709            *this, tr["Clock for GMenu2X"],
710            tr["Set the cpu working frequency when running GMenu2X"],
711            &confInt["menuClock"], cpuFreqMin, cpuFreqSafeMax, cpuFreqMultiple)));
712    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
713            *this, tr["Maximum overclock"],
714            tr["Set the maximum overclock for launching links"],
715            &confInt["maxClock"], cpuFreqMin, cpuFreqMax, cpuFreqMultiple)));
716#endif
717    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingBool(
718            *this, tr["Output logs"],
719            tr["Logs the output of the links. Use the Log Viewer to read them."],
720            &confInt["outputLogs"])));
721    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
722            *this, tr["Screen Timeout"],
723            tr["Set screen's backlight timeout in seconds"],
724            &confInt["backlightTimeout"], 0, 120)));
725    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
726            *this, tr["Button repeat rate"],
727            tr["Set button repetitions per second"],
728            &confInt["buttonRepeatRate"], 0, 20)));
729
730    if (sd.exec()) {
731#ifdef ENABLE_CPUFREQ
732        if (curMenuClock != confInt["menuClock"]) setClock(confInt["menuClock"]);
733#endif
734
735        powerSaver.setScreenTimeout(confInt["backlightTimeout"]);
736
737        input.repeatRateChanged();
738
739        if (lang == "English") lang = "";
740        if (lang != tr.lang()) {
741            tr.setLang(lang);
742            confStr["lang"] = lang;
743        }
744
745        writeConfig();
746    }
747}
748
749void GMenu2X::skinMenu() {
750    FileLister fl_sk;
751    fl_sk.setShowFiles(false);
752    fl_sk.setShowUpdir(false);
753    fl_sk.browse(getHome() + "/skins");
754    fl_sk.browse(GMENU2X_SYSTEM_DIR "/skins", false);
755
756    string curSkin = confStr["skin"];
757
758    SettingsDialog sd(*this, input, tr["Skin"]);
759    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingMultiString(
760            *this, tr["Skin"],
761            tr["Set the skin used by GMenu2X"],
762            &confStr["skin"], &fl_sk.getDirectories())));
763    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
764            *this, tr["Top Bar"],
765            tr["Color of the top bar"],
766            &skinConfColors[COLOR_TOP_BAR_BG])));
767    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
768            *this, tr["Bottom Bar"],
769            tr["Color of the bottom bar"],
770            &skinConfColors[COLOR_BOTTOM_BAR_BG])));
771    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
772            *this, tr["Selection"],
773            tr["Color of the selection and other interface details"],
774            &skinConfColors[COLOR_SELECTION_BG])));
775    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
776            *this, tr["Message Box"],
777            tr["Background color of the message box"],
778            &skinConfColors[COLOR_MESSAGE_BOX_BG])));
779    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
780            *this, tr["Message Box Border"],
781            tr["Border color of the message box"],
782            &skinConfColors[COLOR_MESSAGE_BOX_BORDER])));
783    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
784            *this, tr["Message Box Selection"],
785            tr["Color of the selection of the message box"],
786            &skinConfColors[COLOR_MESSAGE_BOX_SELECTION])));
787
788    if (sd.exec()) {
789        if (curSkin != confStr["skin"]) {
790            setSkin(confStr["skin"]);
791            writeConfig();
792        }
793        writeSkinConfig();
794        initBG();
795    }
796}
797
798void GMenu2X::setSkin(const string &skin, bool setWallpaper) {
799    confStr["skin"] = skin;
800
801    //Clear previous skin settings
802    skinConfStr.clear();
803    skinConfInt.clear();
804
805    DEBUG("GMenu2X: setting new skin %s.\n", skin.c_str());
806
807    //clear collection and change the skin path
808    sc.clear();
809    sc.setSkin(skin);
810
811    //reset colors to the default values
812    skinConfColors[COLOR_TOP_BAR_BG] = RGBAColor(255, 255, 255, 130);
813    skinConfColors[COLOR_BOTTOM_BAR_BG] = RGBAColor(255, 255, 255, 130);
814    skinConfColors[COLOR_SELECTION_BG] = RGBAColor(255, 255, 255, 130);
815    skinConfColors[COLOR_MESSAGE_BOX_BG] = RGBAColor(255, 255, 255);
816    skinConfColors[COLOR_MESSAGE_BOX_BORDER] = RGBAColor(80, 80, 80);
817    skinConfColors[COLOR_MESSAGE_BOX_SELECTION] = RGBAColor(160, 160, 160);
818
819    /* Load skin settings from user directory if present,
820     * or from the system directory. */
821    if (!readSkinConfig(getHome() + "/skins/" + skin + "/skin.conf")) {
822        readSkinConfig(GMENU2X_SYSTEM_DIR "/skins/" + skin + "/skin.conf");
823    }
824
825    if (setWallpaper && !skinConfStr["wallpaper"].empty()) {
826        string fp = sc.getSkinFilePath("wallpapers/" + skinConfStr["wallpaper"]);
827        if (!fp.empty())
828            confStr["wallpaper"] = fp;
829        else
830            WARNING("Unable to find wallpaper defined on skin %s\n", skin.c_str());
831    }
832
833    evalIntConf(skinConfInt, "topBarHeight", 50, 32, 120);
834    evalIntConf(skinConfInt, "bottomBarHeight", 20, 20, 120);
835    evalIntConf(skinConfInt, "linkHeight", 50, 32, 120);
836    evalIntConf(skinConfInt, "linkWidth", 80, 32, 120);
837
838    if (menu != NULL) menu->skinUpdated();
839
840    //Selection png
841    useSelectionPng = sc.addSkinRes("imgs/selection.png", false) != NULL;
842
843    //font
844    initFont();
845}
846
847bool GMenu2X::readSkinConfig(const string& conffile)
848{
849    ifstream skinconf(conffile.c_str(), ios_base::in);
850    if (skinconf.is_open()) {
851        string line;
852        while (getline(skinconf, line, '\n')) {
853            line = trim(line);
854            DEBUG("skinconf: '%s'\n", line.c_str());
855            string::size_type pos = line.find("=");
856            string name = trim(line.substr(0,pos));
857            string value = trim(line.substr(pos+1,line.length()));
858
859            if (value.length()>0) {
860                if (value.length()>1 && value.at(0)=='"' && value.at(value.length()-1)=='"')
861                    skinConfStr[name] = value.substr(1,value.length()-2);
862                else if (value.at(0) == '#')
863                    skinConfColors[stringToColor(name)] =
864                        RGBAColor::fromString(value.substr(1, value.length()));
865                else
866                    skinConfInt[name] = atoi(value.c_str());
867            }
868        }
869        skinconf.close();
870        return true;
871    } else {
872        return false;
873    }
874}
875
876void GMenu2X::showManual() {
877    menu->selLinkApp()->showManual();
878}
879
880void GMenu2X::showContextMenu() {
881    layers.push_back(make_shared<ContextMenu>(*this, *menu));
882}
883
884void GMenu2X::changeWallpaper() {
885    WallpaperDialog wp(*this);
886    if (wp.exec() && confStr["wallpaper"] != wp.wallpaper) {
887        confStr["wallpaper"] = wp.wallpaper;
888        initBG();
889        writeConfig();
890    }
891}
892
893void GMenu2X::addLink() {
894    FileDialog fd(*this, tr["Select an application"], "sh,bin,py,elf,");
895    if (fd.exec())
896        menu->addLink(fd.getPath(), fd.getFile());
897}
898
899void GMenu2X::editLink() {
900    LinkApp *linkApp = menu->selLinkApp();
901    if (!linkApp) return;
902
903    vector<string> pathV;
904    split(pathV,linkApp->getFile(),"/");
905    string oldSection = "";
906    if (pathV.size()>1)
907        oldSection = pathV[pathV.size()-2];
908    string newSection = oldSection;
909
910    string linkTitle = linkApp->getTitle();
911    string linkDescription = linkApp->getDescription();
912    string linkIcon = linkApp->getIcon();
913    string linkManual = linkApp->getManual();
914    string linkSelFilter = linkApp->getSelectorFilter();
915    string linkSelDir = linkApp->getSelectorDir();
916    bool linkSelBrowser = linkApp->getSelectorBrowser();
917    int linkClock = linkApp->clock();
918
919    string diagTitle = tr.translate("Edit $1",linkTitle.c_str(),NULL);
920    string diagIcon = linkApp->getIconPath();
921
922    SettingsDialog sd(*this, input, diagTitle, diagIcon);
923    if (!linkApp->isOpk()) {
924        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingString(
925                *this, tr["Title"],
926                tr["Link title"],
927                &linkTitle, diagTitle, diagIcon)));
928        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingString(
929                *this, tr["Description"],
930                tr["Link description"],
931                &linkDescription, diagTitle, diagIcon)));
932        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingMultiString(
933                *this, tr["Section"],
934                tr["The section this link belongs to"],
935                &newSection, &menu->getSections())));
936        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingImage(
937                *this, tr["Icon"],
938                tr.translate("Select an icon for this link", linkTitle.c_str(), NULL),
939                &linkIcon, "png")));
940        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingFile(
941                *this, tr["Manual"],
942                tr["Select a manual or README file"],
943                &linkManual, "man.png,txt")));
944    }
945    if (!linkApp->isOpk() || !linkApp->getSelectorDir().empty()) {
946        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingDir(
947                *this, tr["Selector Directory"],
948                tr["Directory to scan for the selector"],
949                &linkSelDir)));
950        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingBool(
951                *this, tr["Selector Browser"],
952                tr["Allow the selector to change directory"],
953                &linkSelBrowser)));
954    }
955#ifdef ENABLE_CPUFREQ
956    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
957            *this, tr["Clock frequency"],
958            tr["CPU clock frequency for this link"],
959            &linkClock, cpuFreqMin, confInt["maxClock"], cpuFreqMultiple)));
960#endif
961    if (!linkApp->isOpk()) {
962        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingString(
963                *this, tr["Selector Filter"],
964                tr["Selector filter (Separate values with a comma)"],
965                &linkSelFilter, diagTitle, diagIcon)));
966        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingBool(
967                *this, tr["Display Console"],
968                tr["Must be enabled for console-based applications"],
969                &linkApp->consoleApp)));
970    }
971
972    if (sd.exec()) {
973        linkApp->setTitle(linkTitle);
974        linkApp->setDescription(linkDescription);
975        linkApp->setIcon(linkIcon);
976        linkApp->setManual(linkManual);
977        linkApp->setSelectorFilter(linkSelFilter);
978        linkApp->setSelectorDir(linkSelDir);
979        linkApp->setSelectorBrowser(linkSelBrowser);
980        linkApp->setClock(linkClock);
981
982        INFO("New Section: '%s'\n", newSection.c_str());
983
984        //if section changed move file and update link->file
985        if (oldSection!=newSection) {
986            vector<string>::const_iterator newSectionIndex = find(menu->getSections().begin(),menu->getSections().end(),newSection);
987            if (newSectionIndex==menu->getSections().end()) return;
988            string newFileName = "sections/"+newSection+"/"+linkTitle;
989            uint x=2;
990            while (fileExists(newFileName)) {
991                string id = "";
992                stringstream ss; ss << x; ss >> id;
993                newFileName = "sections/"+newSection+"/"+linkTitle+id;
994                x++;
995            }
996            rename(linkApp->getFile().c_str(),newFileName.c_str());
997            linkApp->renameFile(newFileName);
998
999            INFO("New section index: %zd.\n", newSectionIndex - menu->getSections().begin());
1000
1001            menu->linkChangeSection(menu->selLinkIndex(), menu->selSectionIndex(), newSectionIndex - menu->getSections().begin());
1002        }
1003        linkApp->save();
1004    }
1005}
1006
1007void GMenu2X::deleteLink() {
1008    if (menu->selLinkApp()!=NULL) {
1009        MessageBox mb(*this, tr.translate("Deleting $1",menu->selLink()->getTitle().c_str(),NULL)+"\n"+tr["Are you sure?"], menu->selLink()->getIconPath());
1010        mb.setButton(InputManager::ACCEPT, tr["Yes"]);
1011        mb.setButton(InputManager::CANCEL, tr["No"]);
1012        if (mb.exec() == InputManager::ACCEPT)
1013            menu->deleteSelectedLink();
1014    }
1015}
1016
1017void GMenu2X::addSection() {
1018    InputDialog id(*this, input, tr["Insert a name for the new section"]);
1019    if (id.exec()) {
1020        // Look up section; create if it doesn't exist yet.
1021        auto idx = menu->sectionNamed(id.getInput());
1022        // Switch to the new section.
1023        menu->setSectionIndex(idx);
1024    }
1025}
1026
1027void GMenu2X::deleteSection() {
1028    MessageBox mb(*this,tr["You will lose all the links in this section."]+"\n"+tr["Are you sure?"]);
1029    mb.setButton(InputManager::ACCEPT, tr["Yes"]);
1030    mb.setButton(InputManager::CANCEL, tr["No"]);
1031    if (mb.exec() == InputManager::ACCEPT) {
1032
1033        if (rmtree(getHome() + "/sections/" + menu->selSection()))
1034            menu->deleteSelectedSection();
1035    }
1036}
1037
1038typedef struct {
1039    unsigned short batt;
1040    unsigned short remocon;
1041} MMSP2ADC;
1042
1043#ifdef ENABLE_CPUFREQ
1044void GMenu2X::setClock(unsigned mhz) {
1045    mhz = constrain(mhz, cpuFreqMin, confInt["maxClock"]);
1046#if defined(PLATFORM_A320) || defined(PLATFORM_GCW0) || defined(PLATFORM_NANONOTE)
1047    jz_cpuspeed(mhz);
1048#endif
1049}
1050#endif
1051
1052string GMenu2X::getDiskFree(const char *path) {
1053    string df = "";
1054    struct statvfs b;
1055
1056    int ret = statvfs(path, &b);
1057    if (ret == 0) {
1058        // Make sure that the multiplication happens in 64 bits.
1059        unsigned long freeMiB =
1060                ((unsigned long long)b.f_bfree * b.f_bsize) / (1024 * 1024);
1061        unsigned long totalMiB =
1062                ((unsigned long long)b.f_blocks * b.f_frsize) / (1024 * 1024);
1063        stringstream ss;
1064        if (totalMiB >= 10000) {
1065            ss << (freeMiB / 1024) << "." << ((freeMiB % 1024) * 10) / 1024 << "/"
1066               << (totalMiB / 1024) << "." << ((totalMiB % 1024) * 10) / 1024 << "GiB";
1067        } else {
1068            ss << freeMiB << "/" << totalMiB << "MiB";
1069        }
1070        ss >> df;
1071    } else WARNING("statvfs failed with error '%s'.\n", strerror(errno));
1072    return df;
1073}
1074
1075int GMenu2X::drawButton(Surface& s, const string &btn, const string &text, int x, int y) {
1076    int w = 0;
1077    auto icon = sc["skin:imgs/buttons/" + btn + ".png"];
1078    if (icon) {
1079        if (y < 0) y = resY + y;
1080        w = icon->width();
1081        icon->blit(s, x, y - 7);
1082        if (!text.empty()) {
1083            w += 3;
1084            w += font->write(
1085                    s, text, x + w, y, Font::HAlignLeft, Font::VAlignMiddle);
1086            w += 6;
1087        }
1088    }
1089    return x + w;
1090}
1091
1092int GMenu2X::drawButtonRight(Surface& s, const string &btn, const string &text, int x, int y) {
1093    int w = 0;
1094    auto icon = sc["skin:imgs/buttons/" + btn + ".png"];
1095    if (icon) {
1096        if (y < 0) y = resY + y;
1097        w = icon->width();
1098        icon->blit(s, x - w, y - 7);
1099        if (!text.empty()) {
1100            w += 3;
1101            w += font->write(
1102                    s, text, x - w, y, Font::HAlignRight, Font::VAlignMiddle);
1103            w += 6;
1104        }
1105    }
1106    return x - w;
1107}
1108
1109void GMenu2X::drawScrollBar(uint pageSize, uint totalSize, uint pagePos) {
1110    if (totalSize <= pageSize) {
1111        // Everything fits on one screen, no scroll bar needed.
1112        return;
1113    }
1114
1115    unsigned int top, height;
1116    tie(top, height) = getContentArea();
1117    top += 1;
1118    height -= 2;
1119
1120    s->rectangle(resX - 8, top, 7, height, skinConfColors[COLOR_SELECTION_BG]);
1121    top += 2;
1122    height -= 4;
1123
1124    const uint barSize = max(height * pageSize / totalSize, 4u);
1125    const uint barPos = (height - barSize) * pagePos / (totalSize - pageSize);
1126
1127    s->box(resX - 6, top + barPos, 3, barSize,
1128            skinConfColors[COLOR_SELECTION_BG]);
1129}
1130
1131void GMenu2X::drawTopBar(Surface& s) {
1132    Surface *bar = sc.skinRes("imgs/topbar.png", false);
1133    if (bar) {
1134        bar->blit(s, 0, 0);
1135    } else {
1136        const int h = skinConfInt["topBarHeight"];
1137        s.box(0, 0, resX, h, skinConfColors[COLOR_TOP_BAR_BG]);
1138    }
1139}
1140
1141void GMenu2X::drawBottomBar(Surface& s) {
1142    Surface *bar = sc.skinRes("imgs/bottombar.png", false);
1143    if (bar) {
1144        bar->blit(s, 0, resY-bar->height());
1145    } else {
1146        const int h = skinConfInt["bottomBarHeight"];
1147        s.box(0, resY - h, resX, h, skinConfColors[COLOR_BOTTOM_BAR_BG]);
1148    }
1149}
1150

Archive Download this file



interactive