Root/src/gmenu2x.cpp

Source at commit cb1b26e5e67d79ca9d03e85db06ec6c2e5b3557a created 8 years 5 months ago.
By Maarten ter Huurne, Quit GMenu2X when window is closed in main screen
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            if (button == InputManager::QUIT) {
640                break;
641            }
642            for (auto it = layers.rbegin(); it != layers.rend(); ++it) {
643                if ((*it)->handleButtonPress(button)) {
644                    break;
645                }
646            }
647        }
648    }
649}
650
651void GMenu2X::explorer() {
652    FileDialog fd(*this, tr["Select an application"], "sh,bin,py,elf,");
653    if (fd.exec()) {
654        if (confInt["saveSelection"] && (confInt["section"]!=menu->selSectionIndex() || confInt["link"]!=menu->selLinkIndex()))
655            writeConfig();
656
657        string command = cmdclean(fd.getPath()+"/"+fd.getFile());
658        chdir(fd.getPath().c_str());
659#ifdef ENABLE_CPUFREQ
660        setClock(cpuFreqAppDefault);
661#endif
662
663        toLaunch.reset(new Launcher(
664                vector<string> { "/bin/sh", "-c", command }));
665    }
666}
667
668void GMenu2X::queueLaunch(
669    unique_ptr<Launcher>&& launcher, shared_ptr<Layer> launchLayer
670) {
671    toLaunch = move(launcher);
672    layers.push_back(launchLayer);
673}
674
675void GMenu2X::showHelpPopup() {
676    layers.push_back(make_shared<HelpPopup>(*this));
677}
678
679void GMenu2X::showSettings() {
680#ifdef ENABLE_CPUFREQ
681    int curMenuClock = confInt["menuClock"];
682#endif
683
684    FileLister fl_tr;
685    fl_tr.setShowDirectories(false);
686    fl_tr.browse(GMENU2X_SYSTEM_DIR "/translations");
687    fl_tr.browse(getHome() + "/translations", false);
688
689    vector<string> translations = fl_tr.getFiles();
690    translations.insert(translations.begin(), "English");
691    string lang = tr.lang();
692
693    vector<string> encodings;
694    encodings.push_back("NTSC");
695    encodings.push_back("PAL");
696
697    SettingsDialog sd(*this, input, tr["Settings"]);
698    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingMultiString(
699            *this, tr["Language"],
700            tr["Set the language used by GMenu2X"],
701            &lang, &translations)));
702    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingBool(
703            *this, tr["Save last selection"],
704            tr["Save the last selected link and section on exit"],
705            &confInt["saveSelection"])));
706#ifdef ENABLE_CPUFREQ
707    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
708            *this, tr["Clock for GMenu2X"],
709            tr["Set the cpu working frequency when running GMenu2X"],
710            &confInt["menuClock"], cpuFreqMin, cpuFreqSafeMax, cpuFreqMultiple)));
711    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
712            *this, tr["Maximum overclock"],
713            tr["Set the maximum overclock for launching links"],
714            &confInt["maxClock"], cpuFreqMin, cpuFreqMax, cpuFreqMultiple)));
715#endif
716    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingBool(
717            *this, tr["Output logs"],
718            tr["Logs the output of the links. Use the Log Viewer to read them."],
719            &confInt["outputLogs"])));
720    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
721            *this, tr["Screen Timeout"],
722            tr["Set screen's backlight timeout in seconds"],
723            &confInt["backlightTimeout"], 0, 120)));
724    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
725            *this, tr["Button repeat rate"],
726            tr["Set button repetitions per second"],
727            &confInt["buttonRepeatRate"], 0, 20)));
728
729    if (sd.exec()) {
730#ifdef ENABLE_CPUFREQ
731        if (curMenuClock != confInt["menuClock"]) setClock(confInt["menuClock"]);
732#endif
733
734        powerSaver.setScreenTimeout(confInt["backlightTimeout"]);
735
736        input.repeatRateChanged();
737
738        if (lang == "English") lang = "";
739        if (lang != tr.lang()) {
740            tr.setLang(lang);
741            confStr["lang"] = lang;
742        }
743
744        writeConfig();
745    }
746}
747
748void GMenu2X::skinMenu() {
749    FileLister fl_sk;
750    fl_sk.setShowFiles(false);
751    fl_sk.setShowUpdir(false);
752    fl_sk.browse(getHome() + "/skins");
753    fl_sk.browse(GMENU2X_SYSTEM_DIR "/skins", false);
754
755    string curSkin = confStr["skin"];
756
757    SettingsDialog sd(*this, input, tr["Skin"]);
758    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingMultiString(
759            *this, tr["Skin"],
760            tr["Set the skin used by GMenu2X"],
761            &confStr["skin"], &fl_sk.getDirectories())));
762    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
763            *this, tr["Top Bar"],
764            tr["Color of the top bar"],
765            &skinConfColors[COLOR_TOP_BAR_BG])));
766    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
767            *this, tr["Bottom Bar"],
768            tr["Color of the bottom bar"],
769            &skinConfColors[COLOR_BOTTOM_BAR_BG])));
770    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
771            *this, tr["Selection"],
772            tr["Color of the selection and other interface details"],
773            &skinConfColors[COLOR_SELECTION_BG])));
774    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
775            *this, tr["Message Box"],
776            tr["Background color of the message box"],
777            &skinConfColors[COLOR_MESSAGE_BOX_BG])));
778    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
779            *this, tr["Message Box Border"],
780            tr["Border color of the message box"],
781            &skinConfColors[COLOR_MESSAGE_BOX_BORDER])));
782    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingRGBA(
783            *this, tr["Message Box Selection"],
784            tr["Color of the selection of the message box"],
785            &skinConfColors[COLOR_MESSAGE_BOX_SELECTION])));
786
787    if (sd.exec()) {
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);
815    skinConfColors[COLOR_MESSAGE_BOX_BORDER] = RGBAColor(80, 80, 80);
816    skinConfColors[COLOR_MESSAGE_BOX_SELECTION] = RGBAColor(160, 160, 160);
817
818    /* Load skin settings from user directory if present,
819     * or from the system directory. */
820    if (!readSkinConfig(getHome() + "/skins/" + skin + "/skin.conf")) {
821        readSkinConfig(GMENU2X_SYSTEM_DIR "/skins/" + skin + "/skin.conf");
822    }
823
824    if (setWallpaper && !skinConfStr["wallpaper"].empty()) {
825        string fp = sc.getSkinFilePath("wallpapers/" + skinConfStr["wallpaper"]);
826        if (!fp.empty())
827            confStr["wallpaper"] = fp;
828        else
829            WARNING("Unable to find wallpaper defined on skin %s\n", skin.c_str());
830    }
831
832    evalIntConf(skinConfInt, "topBarHeight", 50, 32, 120);
833    evalIntConf(skinConfInt, "bottomBarHeight", 20, 20, 120);
834    evalIntConf(skinConfInt, "linkHeight", 50, 32, 120);
835    evalIntConf(skinConfInt, "linkWidth", 80, 32, 120);
836
837    if (menu != NULL) menu->skinUpdated();
838
839    //Selection png
840    useSelectionPng = sc.addSkinRes("imgs/selection.png", false) != NULL;
841
842    //font
843    initFont();
844}
845
846bool GMenu2X::readSkinConfig(const string& conffile)
847{
848    ifstream skinconf(conffile.c_str(), ios_base::in);
849    if (skinconf.is_open()) {
850        string line;
851        while (getline(skinconf, line, '\n')) {
852            line = trim(line);
853            DEBUG("skinconf: '%s'\n", line.c_str());
854            string::size_type pos = line.find("=");
855            string name = trim(line.substr(0,pos));
856            string value = trim(line.substr(pos+1,line.length()));
857
858            if (value.length()>0) {
859                if (value.length()>1 && value.at(0)=='"' && value.at(value.length()-1)=='"')
860                    skinConfStr[name] = value.substr(1,value.length()-2);
861                else if (value.at(0) == '#')
862                    skinConfColors[stringToColor(name)] =
863                        RGBAColor::fromString(value.substr(1, value.length()));
864                else
865                    skinConfInt[name] = atoi(value.c_str());
866            }
867        }
868        skinconf.close();
869        return true;
870    } else {
871        return false;
872    }
873}
874
875void GMenu2X::showManual() {
876    menu->selLinkApp()->showManual();
877}
878
879void GMenu2X::showContextMenu() {
880    layers.push_back(make_shared<ContextMenu>(*this, *menu));
881}
882
883void GMenu2X::changeWallpaper() {
884    WallpaperDialog wp(*this);
885    if (wp.exec() && confStr["wallpaper"] != wp.wallpaper) {
886        confStr["wallpaper"] = wp.wallpaper;
887        initBG();
888        writeConfig();
889    }
890}
891
892void GMenu2X::addLink() {
893    FileDialog fd(*this, tr["Select an application"], "sh,bin,py,elf,");
894    if (fd.exec())
895        menu->addLink(fd.getPath(), fd.getFile());
896}
897
898void GMenu2X::editLink() {
899    LinkApp *linkApp = menu->selLinkApp();
900    if (!linkApp) return;
901
902    vector<string> pathV;
903    split(pathV,linkApp->getFile(),"/");
904    string oldSection = "";
905    if (pathV.size()>1)
906        oldSection = pathV[pathV.size()-2];
907    string newSection = oldSection;
908
909    string linkTitle = linkApp->getTitle();
910    string linkDescription = linkApp->getDescription();
911    string linkIcon = linkApp->getIcon();
912    string linkManual = linkApp->getManual();
913    string linkSelFilter = linkApp->getSelectorFilter();
914    string linkSelDir = linkApp->getSelectorDir();
915    bool linkSelBrowser = linkApp->getSelectorBrowser();
916    int linkClock = linkApp->clock();
917
918    string diagTitle = tr.translate("Edit $1",linkTitle.c_str(),NULL);
919    string diagIcon = linkApp->getIconPath();
920
921    SettingsDialog sd(*this, input, diagTitle, diagIcon);
922    if (!linkApp->isOpk()) {
923        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingString(
924                *this, tr["Title"],
925                tr["Link title"],
926                &linkTitle, diagTitle, diagIcon)));
927        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingString(
928                *this, tr["Description"],
929                tr["Link description"],
930                &linkDescription, diagTitle, diagIcon)));
931        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingMultiString(
932                *this, tr["Section"],
933                tr["The section this link belongs to"],
934                &newSection, &menu->getSections())));
935        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingImage(
936                *this, tr["Icon"],
937                tr.translate("Select an icon for this link", linkTitle.c_str(), NULL),
938                &linkIcon, "png")));
939        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingFile(
940                *this, tr["Manual"],
941                tr["Select a manual or README file"],
942                &linkManual, "man.png,txt")));
943    }
944    if (!linkApp->isOpk() || !linkApp->getSelectorDir().empty()) {
945        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingDir(
946                *this, tr["Selector Directory"],
947                tr["Directory to scan for the selector"],
948                &linkSelDir)));
949        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingBool(
950                *this, tr["Selector Browser"],
951                tr["Allow the selector to change directory"],
952                &linkSelBrowser)));
953    }
954#ifdef ENABLE_CPUFREQ
955    sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingInt(
956            *this, tr["Clock frequency"],
957            tr["CPU clock frequency for this link"],
958            &linkClock, cpuFreqMin, confInt["maxClock"], cpuFreqMultiple)));
959#endif
960    if (!linkApp->isOpk()) {
961        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingString(
962                *this, tr["Selector Filter"],
963                tr["Selector filter (Separate values with a comma)"],
964                &linkSelFilter, diagTitle, diagIcon)));
965        sd.addSetting(unique_ptr<MenuSetting>(new MenuSettingBool(
966                *this, tr["Display Console"],
967                tr["Must be enabled for console-based applications"],
968                &linkApp->consoleApp)));
969    }
970
971    if (sd.exec()) {
972        linkApp->setTitle(linkTitle);
973        linkApp->setDescription(linkDescription);
974        linkApp->setIcon(linkIcon);
975        linkApp->setManual(linkManual);
976        linkApp->setSelectorFilter(linkSelFilter);
977        linkApp->setSelectorDir(linkSelDir);
978        linkApp->setSelectorBrowser(linkSelBrowser);
979        linkApp->setClock(linkClock);
980
981        INFO("New Section: '%s'\n", newSection.c_str());
982
983        //if section changed move file and update link->file
984        if (oldSection!=newSection) {
985            vector<string>::const_iterator newSectionIndex = find(menu->getSections().begin(),menu->getSections().end(),newSection);
986            if (newSectionIndex==menu->getSections().end()) return;
987            string newFileName = "sections/"+newSection+"/"+linkTitle;
988            uint x=2;
989            while (fileExists(newFileName)) {
990                string id = "";
991                stringstream ss; ss << x; ss >> id;
992                newFileName = "sections/"+newSection+"/"+linkTitle+id;
993                x++;
994            }
995            rename(linkApp->getFile().c_str(),newFileName.c_str());
996            linkApp->renameFile(newFileName);
997
998            INFO("New section index: %i.\n", newSectionIndex - menu->getSections().begin());
999
1000            menu->linkChangeSection(menu->selLinkIndex(), menu->selSectionIndex(), newSectionIndex - menu->getSections().begin());
1001        }
1002        linkApp->save();
1003    }
1004}
1005
1006void GMenu2X::deleteLink() {
1007    if (menu->selLinkApp()!=NULL) {
1008        MessageBox mb(*this, tr.translate("Deleting $1",menu->selLink()->getTitle().c_str(),NULL)+"\n"+tr["Are you sure?"], menu->selLink()->getIconPath());
1009        mb.setButton(InputManager::ACCEPT, tr["Yes"]);
1010        mb.setButton(InputManager::CANCEL, tr["No"]);
1011        if (mb.exec() == InputManager::ACCEPT)
1012            menu->deleteSelectedLink();
1013    }
1014}
1015
1016void GMenu2X::addSection() {
1017    InputDialog id(*this, input, tr["Insert a name for the new section"]);
1018    if (id.exec()) {
1019        // Look up section; create if it doesn't exist yet.
1020        auto idx = menu->sectionNamed(id.getInput());
1021        // Switch to the new section.
1022        menu->setSectionIndex(idx);
1023    }
1024}
1025
1026void GMenu2X::deleteSection() {
1027    MessageBox mb(*this,tr["You will lose all the links in this section."]+"\n"+tr["Are you sure?"]);
1028    mb.setButton(InputManager::ACCEPT, tr["Yes"]);
1029    mb.setButton(InputManager::CANCEL, tr["No"]);
1030    if (mb.exec() == InputManager::ACCEPT) {
1031
1032        if (rmtree(getHome() + "/sections/" + menu->selSection()))
1033            menu->deleteSelectedSection();
1034    }
1035}
1036
1037typedef struct {
1038    unsigned short batt;
1039    unsigned short remocon;
1040} MMSP2ADC;
1041
1042#ifdef ENABLE_CPUFREQ
1043void GMenu2X::setClock(unsigned mhz) {
1044    mhz = constrain(mhz, cpuFreqMin, confInt["maxClock"]);
1045#if defined(PLATFORM_A320) || defined(PLATFORM_GCW0) || defined(PLATFORM_NANONOTE)
1046    jz_cpuspeed(mhz);
1047#endif
1048}
1049#endif
1050
1051string GMenu2X::getDiskFree(const char *path) {
1052    string df = "";
1053    struct statvfs b;
1054
1055    int ret = statvfs(path, &b);
1056    if (ret == 0) {
1057        // Make sure that the multiplication happens in 64 bits.
1058        unsigned long freeMiB =
1059                ((unsigned long long)b.f_bfree * b.f_bsize) / (1024 * 1024);
1060        unsigned long totalMiB =
1061                ((unsigned long long)b.f_blocks * b.f_frsize) / (1024 * 1024);
1062        stringstream ss;
1063        if (totalMiB >= 10000) {
1064            ss << (freeMiB / 1024) << "." << ((freeMiB % 1024) * 10) / 1024 << "/"
1065               << (totalMiB / 1024) << "." << ((totalMiB % 1024) * 10) / 1024 << "GiB";
1066        } else {
1067            ss << freeMiB << "/" << totalMiB << "MiB";
1068        }
1069        ss >> df;
1070    } else WARNING("statvfs failed with error '%s'.\n", strerror(errno));
1071    return df;
1072}
1073
1074int GMenu2X::drawButton(Surface& s, const string &btn, const string &text, int x, int y) {
1075    int w = 0;
1076    auto icon = sc["skin:imgs/buttons/" + btn + ".png"];
1077    if (icon) {
1078        if (y < 0) y = resY + y;
1079        w = icon->width();
1080        icon->blit(s, x, y - 7);
1081        if (!text.empty()) {
1082            w += 3;
1083            w += font->write(
1084                    s, text, x + w, y, Font::HAlignLeft, Font::VAlignMiddle);
1085            w += 6;
1086        }
1087    }
1088    return x + w;
1089}
1090
1091int GMenu2X::drawButtonRight(Surface& s, const string &btn, const string &text, int x, int y) {
1092    int w = 0;
1093    auto icon = sc["skin:imgs/buttons/" + btn + ".png"];
1094    if (icon) {
1095        if (y < 0) y = resY + y;
1096        w = icon->width();
1097        icon->blit(s, x - w, y - 7);
1098        if (!text.empty()) {
1099            w += 3;
1100            w += font->write(
1101                    s, text, x - w, y, Font::HAlignRight, Font::VAlignMiddle);
1102            w += 6;
1103        }
1104    }
1105    return x - w;
1106}
1107
1108void GMenu2X::drawScrollBar(uint pageSize, uint totalSize, uint pagePos) {
1109    if (totalSize <= pageSize) {
1110        // Everything fits on one screen, no scroll bar needed.
1111        return;
1112    }
1113
1114    unsigned int top, height;
1115    tie(top, height) = getContentArea();
1116    top += 1;
1117    height -= 2;
1118
1119    s->rectangle(resX - 8, top, 7, height, skinConfColors[COLOR_SELECTION_BG]);
1120    top += 2;
1121    height -= 4;
1122
1123    const uint barSize = max(height * pageSize / totalSize, 4u);
1124    const uint barPos = (height - barSize) * pagePos / (totalSize - pageSize);
1125
1126    s->box(resX - 6, top + barPos, 3, barSize,
1127            skinConfColors[COLOR_SELECTION_BG]);
1128}
1129
1130void GMenu2X::drawTopBar(Surface& s) {
1131    Surface *bar = sc.skinRes("imgs/topbar.png", false);
1132    if (bar) {
1133        bar->blit(s, 0, 0);
1134    } else {
1135        const int h = skinConfInt["topBarHeight"];
1136        s.box(0, 0, resX, h, skinConfColors[COLOR_TOP_BAR_BG]);
1137    }
1138}
1139
1140void GMenu2X::drawBottomBar(Surface& s) {
1141    Surface *bar = sc.skinRes("imgs/bottombar.png", false);
1142    if (bar) {
1143        bar->blit(s, 0, resY-bar->height());
1144    } else {
1145        const int h = skinConfInt["bottomBarHeight"];
1146        s.box(0, resY - h, resX, h, skinConfColors[COLOR_BOTTOM_BAR_BG]);
1147    }
1148}
1149

Archive Download this file



interactive