Root/src/gmenu2x.cpp

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

Archive Download this file



interactive