Root/src/linkapp.cpp

Source at commit 7b10f9448bb9965e94f9a37a59f0999e9c51e68f created 5 years 4 months ago.
By Maarten ter Huurne, Suppress Clang analyzer warnings about dead assignments
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 "linkapp.h"
22
23#include "debug.h"
24#include "gmenu2x.h"
25#include "launcher.h"
26#include "layer.h"
27#include "menu.h"
28#include "selector.h"
29#include "surface.h"
30#include "textmanualdialog.h"
31#include "utilities.h"
32
33#include <sys/types.h>
34#include <sys/stat.h>
35#include <sys/ioctl.h>
36#include <signal.h>
37#include <stdlib.h>
38#include <unistd.h>
39#include <fcntl.h>
40
41#include <array>
42#include <cerrno>
43#include <fstream>
44#include <sstream>
45#include <utility>
46
47#ifdef HAVE_LIBOPK
48#include <opk.h>
49#endif
50
51#ifdef HAVE_LIBXDGMIME
52#include <xdgmime.h>
53#endif
54
55using namespace std;
56
57static array<const char *, 4> tokens = { "%f", "%F", "%u", "%U", };
58
59
60/**
61 * Displays the launch message (loading screen).
62 */
63class LaunchLayer: public Layer {
64public:
65    LaunchLayer(LinkApp& app) : app(app) {}
66
67    void paint(Surface &s) override {
68        app.drawLaunch(s);
69    }
70
71    bool handleButtonPress(InputManager::Button) override {
72        return true;
73    }
74
75private:
76    LinkApp& app;
77};
78
79
80#ifdef HAVE_LIBOPK
81LinkApp::LinkApp(GMenu2X& gmenu2x, string const& linkfile, bool deletable,
82            struct OPK *opk, const char *metadata_)
83#else
84LinkApp::LinkApp(GMenu2X& gmenu2x, string const& linkfile, bool deletable)
85#endif
86    : Link(gmenu2x, bind(&LinkApp::start, this))
87    , deletable(deletable)
88{
89    manual = "";
90    file = linkfile;
91#ifdef ENABLE_CPUFREQ
92    setClock(gmenu2x.getDefaultAppClock());
93#else
94    setClock(0);
95#endif
96    selectordir = "";
97    selectorfilter = "*";
98    icon = iconPath = "";
99    selectorbrowser = true;
100    editable = true;
101    edited = false;
102
103    bool appTakesFileArg = true;
104#ifdef HAVE_LIBOPK
105    isOPK = !!opk;
106
107    if (isOPK) {
108        string::size_type pos;
109        const char *key, *val;
110        size_t lkey, lval;
111        int ret;
112
113        metadata.assign(metadata_);
114        opkFile = file;
115        pos = file.rfind('/');
116        opkMount = file.substr(pos+1);
117        pos = opkMount.rfind('.');
118        opkMount = opkMount.substr(0, pos);
119
120        appTakesFileArg = false;
121        category = "applications";
122
123        while ((ret = opk_read_pair(opk, &key, &lkey, &val, &lval))) {
124            if (ret < 0) {
125                ERROR("Unable to read meta-data\n");
126                break;
127            }
128
129            char buf[lval + 1];
130            sprintf(buf, "%.*s", lval, val);
131
132            if (!strncmp(key, "Categories", lkey)) {
133                category = buf;
134
135                pos = category.find(';');
136                if (pos != category.npos)
137                    category = category.substr(0, pos);
138
139            } else if ((!strncmp(key, "Name", lkey) && title.empty())
140                        || !strncmp(key, ("Name[" + gmenu2x.tr["Lng"] +
141                                "]").c_str(), lkey)) {
142                title = buf;
143
144            } else if ((!strncmp(key, "Comment", lkey) && description.empty())
145                        || !strncmp(key, ("Comment[" +
146                                gmenu2x.tr["Lng"] + "]").c_str(), lkey)) {
147                description = buf;
148
149            } else if (!strncmp(key, "Terminal", lkey)) {
150                consoleApp = !strncmp(val, "true", lval);
151
152            } else if (!strncmp(key, "X-OD-Manual", lkey)) {
153                manual = buf;
154
155            } else if (!strncmp(key, "Icon", lkey)) {
156                /* Read the icon from the OPK only
157                 * if it doesn't exist on the skin */
158                this->icon = gmenu2x.sc.getSkinFilePath("icons/" + (string) buf + ".png");
159                if (this->icon.empty()) {
160                    this->icon = linkfile + '#' + buf + ".png";
161                }
162                iconPath = this->icon;
163                updateSurfaces();
164
165            } else if (!strncmp(key, "Exec", lkey)) {
166                string tmp = buf;
167
168                for (auto token : tokens) {
169                    if (tmp.find(token) != tmp.npos) {
170                        selectordir = CARD_ROOT;
171                        appTakesFileArg = true;
172                        break;
173                    }
174                }
175
176                continue;
177            }
178
179#ifdef HAVE_LIBXDGMIME
180            if (!strncmp(key, "MimeType", lkey)) {
181                string mimetypes = buf;
182                selectorfilter = "";
183
184                while ((pos = mimetypes.find(';')) != mimetypes.npos) {
185                    int nb = 16;
186                    char *extensions[nb];
187                    string mimetype = mimetypes.substr(0, pos);
188                    mimetypes = mimetypes.substr(pos + 1);
189
190                    nb = xdg_mime_get_extensions_from_mime_type(
191                                mimetype.c_str(), extensions, nb);
192
193                    while (nb--) {
194                        selectorfilter += (string) extensions[nb] + ',';
195                        free(extensions[nb]);
196                    }
197                }
198
199                /* Remove last comma */
200                if (!selectorfilter.empty()) {
201                    selectorfilter.pop_back();
202                    DEBUG("Compatible extensions: %s\n", selectorfilter.c_str());
203                }
204
205                continue;
206            }
207#endif /* HAVE_LIBXDGMIME */
208        }
209
210        file = gmenu2x.getHome() + "/sections/" + category + '/' + opkMount;
211        opkMount = (string) "/mnt/" + opkMount + '/';
212        edited = true;
213    } else
214#endif /* HAVE_LIBOPK */
215    {
216        // Non-packaged application.
217
218        // Consider non-deletable applications to be immutable.
219        editable = deletable;
220    }
221
222    string line;
223    ifstream infile (file.c_str(), ios_base::in);
224    while (getline(infile, line, '\n')) {
225        line = trim(line);
226        if (line.empty()) continue;
227        if (line[0]=='#') continue;
228
229        string::size_type position = line.find("=");
230        string name = trim(line.substr(0,position));
231        string value = trim(line.substr(position+1));
232
233        if (name == "clock") {
234            setClock( atoi(value.c_str()) );
235        } else if (name == "selectordir") {
236            if (appTakesFileArg) setSelectorDir(value);
237        } else if (name == "selectorbrowser") {
238            if (value=="false") selectorbrowser = false;
239        } else if (!isOpk()) {
240            if (name == "title") {
241                title = value;
242            } else if (name == "description") {
243                description = value;
244            } else if (name == "launchmsg") {
245                launchMsg = value;
246            } else if (name == "icon") {
247                setIcon(value);
248            } else if (name == "exec") {
249                exec = value;
250            } else if (name == "params") {
251                params = value;
252            } else if (name == "manual") {
253                manual = value;
254            } else if (name == "consoleapp") {
255                if (value == "true") consoleApp = true;
256            } else if (name == "selectorfilter") {
257                setSelectorFilter( value );
258            } else if (name == "editable") {
259                if (value == "false")
260                    editable = false;
261            } else
262                WARNING("Unrecognized option: '%s'\n", name.c_str());
263        } else
264            WARNING("Unrecognized option: '%s'\n", name.c_str());
265    }
266    infile.close();
267
268    if (iconPath.empty()) searchIcon();
269}
270
271void LinkApp::loadIcon() {
272    if (icon.compare(0, 5, "skin:") == 0) {
273        string linkIcon = gmenu2x.sc.getSkinFilePath(
274                icon.substr(5, string::npos));
275        if (!fileExists(linkIcon))
276            searchIcon();
277        else
278            setIconPath(linkIcon);
279
280    } else if (!fileExists(icon)) {
281        searchIcon();
282    }
283}
284
285const string &LinkApp::searchIcon() {
286    if (!iconPath.empty())
287        return iconPath;
288
289    string execicon = exec;
290    string::size_type pos = exec.rfind(".");
291    if (pos != string::npos) execicon = exec.substr(0,pos);
292    execicon += ".png";
293    string exectitle = execicon;
294    pos = execicon.rfind("/");
295    if (pos != string::npos)
296        string exectitle = execicon.substr(pos+1,execicon.length());
297
298    if (!gmenu2x.sc.getSkinFilePath("icons/"+exectitle).empty())
299        iconPath = gmenu2x.sc.getSkinFilePath("icons/"+exectitle);
300    else if (fileExists(execicon))
301        iconPath = execicon;
302    else
303        iconPath = gmenu2x.sc.getSkinFilePath("icons/generic.png");
304
305    return iconPath;
306}
307
308int LinkApp::clock() {
309    return iclock;
310}
311
312const string &LinkApp::clockStr(int maxClock) {
313    if (iclock>maxClock) setClock(maxClock);
314    return sclock;
315}
316
317void LinkApp::setClock(int mhz) {
318    iclock = mhz;
319    stringstream ss;
320    sclock = "";
321    ss << iclock << "MHz";
322    ss >> sclock;
323
324    edited = true;
325}
326
327bool LinkApp::targetExists()
328{
329    return fileExists(exec);
330}
331
332bool LinkApp::save() {
333    // TODO: In theory a non-editable Link wouldn't have 'edited' set, but
334    // currently 'edited' is set on more than a few non-edits, so this
335    // extra check helps prevent write attempts that will never succeed.
336    // Maybe we shouldn't have an 'edited' flag at all and make the
337    // outside world fully responsible for calling save() when needed.
338    if (!editable || !edited) return true;
339
340    std::ostringstream out;
341    if (!isOpk()) {
342        if (!title.empty() ) out << "title=" << title << endl;
343        if (!description.empty() ) out << "description=" << description << endl;
344        if (!launchMsg.empty() ) out << "launchmsg=" << launchMsg << endl;
345        if (!icon.empty() ) out << "icon=" << icon << endl;
346        if (!exec.empty() ) out << "exec=" << exec << endl;
347        if (!params.empty() ) out << "params=" << params << endl;
348        if (!manual.empty() ) out << "manual=" << manual << endl;
349        if (consoleApp ) out << "consoleapp=true" << endl;
350        if (selectorfilter != "*") out << "selectorfilter=" << selectorfilter << endl;
351    }
352    if (iclock != 0 ) out << "clock=" << iclock << endl;
353    if (!selectordir.empty() ) out << "selectordir=" << selectordir << endl;
354    if (!selectorbrowser ) out << "selectorbrowser=false" << endl;
355
356    if (out.tellp() > 0) {
357        DEBUG("Saving app settings: %s\n", file.c_str());
358        if (writeStringToFile(file, out.str())) {
359            string dir = parentDir(file);
360            if (!syncDir(dir)) {
361                ERROR("Failed to sync dir: %s\n", dir.c_str());
362                // Note: Even if syncDir fails, the app settings have been
363                // written, so have save() return true.
364            }
365            return true;
366        } else {
367            return false;
368        }
369    } else {
370        DEBUG("Empty app settings: %s\n", file.c_str());
371        return unlink(file.c_str()) == 0 || errno == ENOENT;
372    }
373}
374
375void LinkApp::drawLaunch(Surface& s) {
376    //Darkened background
377    s.box(0, 0, gmenu2x.resX, gmenu2x.resY, 0,0,0,150);
378
379    string text = getLaunchMsg().empty()
380        ? gmenu2x.tr.translate("Launching $1", getTitle().c_str(), nullptr)
381        : gmenu2x.tr.translate(getLaunchMsg().c_str(), nullptr);
382
383    int textW = gmenu2x.font->getTextWidth(text);
384    int boxW = 62+textW;
385    int halfBoxW = boxW/2;
386
387    //outer box
388    s.box(gmenu2x.halfX-2-halfBoxW, gmenu2x.halfY-23, halfBoxW*2+5, 47, gmenu2x.skinConfColors[COLOR_MESSAGE_BOX_BG]);
389    //inner rectangle
390    s.rectangle(gmenu2x.halfX-halfBoxW, gmenu2x.halfY-21, boxW, 42, gmenu2x.skinConfColors[COLOR_MESSAGE_BOX_BORDER]);
391
392    int x = gmenu2x.halfX + 10 - halfBoxW;
393    /*if (!getIcon().empty())
394        gmenu2x.sc[getIcon()]->blit(gmenu2x.s,x,104);
395    else
396        gmenu2x.sc["icons/generic.png"]->blit(gmenu2x.s,x,104);*/
397    if (iconSurface) {
398        iconSurface->blit(s, x, gmenu2x.halfY - 16);
399    }
400    gmenu2x.font->write(s, text, x + 42, gmenu2x.halfY + 1, Font::HAlignLeft, Font::VAlignMiddle);
401}
402
403void LinkApp::start() {
404    if (selectordir.empty()) {
405        gmenu2x.queueLaunch(prepareLaunch(""), make_shared<LaunchLayer>(*this));
406    } else {
407        selector();
408    }
409}
410
411void LinkApp::showManual() {
412    if (manual.empty())
413        return;
414
415#ifdef HAVE_LIBOPK
416    if (isOPK) {
417        vector<string> readme;
418        char *ptr;
419        struct OPK *opk;
420        int err;
421        void *buf;
422        size_t len;
423
424        opk = opk_open(opkFile.c_str());
425        if (!opk) {
426            WARNING("Unable to open OPK to read manual\n");
427            return;
428        }
429
430        err = opk_extract_file(opk, manual.c_str(), &buf, &len);
431        if (err < 0) {
432            WARNING("Unable to read manual from OPK\n");
433            return;
434        }
435        opk_close(opk);
436
437        ptr = (char *) buf;
438        string str(ptr, len);
439        free(buf);
440
441        if (manual.substr(manual.size()-8,8)==".man.txt") {
442            TextManualDialog tmd(gmenu2x, getTitle(), getIconPath(), str);
443            tmd.exec();
444        } else {
445            TextDialog td(gmenu2x, getTitle(), "ReadMe", getIconPath(), str);
446            td.exec();
447        }
448        return;
449    }
450#endif
451    if (!fileExists(manual))
452        return;
453
454    // Png manuals
455    if (manual.substr(manual.size()-8,8)==".man.png") {
456#ifdef ENABLE_CPUFREQ
457        //Raise the clock to speed-up the loading of the manual
458        gmenu2x.setSafeMaxClock();
459#endif
460
461        auto pngman = OffscreenSurface::loadImage(manual);
462        if (!pngman) {
463            return;
464        }
465        auto bg = OffscreenSurface::loadImage(gmenu2x.confStr["wallpaper"]);
466        if (!bg) {
467            bg = OffscreenSurface::emptySurface(gmenu2x.s->width(), gmenu2x.s->height());
468        }
469        bg->convertToDisplayFormat();
470
471        stringstream ss;
472        string pageStatus;
473
474        bool close = false, repaint = true;
475        int page = 0, pagecount = pngman->width() / 320;
476
477        ss << pagecount;
478        string spagecount;
479        ss >> spagecount;
480
481#ifdef ENABLE_CPUFREQ
482        //Lower the clock
483        gmenu2x.setMenuClock();
484#endif
485
486        while (!close) {
487            OutputSurface& s = *gmenu2x.s;
488
489            if (repaint) {
490                bg->blit(s, 0, 0);
491                pngman->blit(s, -page*320, 0);
492
493                gmenu2x.drawBottomBar(s);
494                int x = 5;
495                x = gmenu2x.drawButton(s, "left", "", x);
496                x = gmenu2x.drawButton(s, "right", gmenu2x.tr["Change page"], x);
497                x = gmenu2x.drawButton(s, "cancel", "", x);
498                x = gmenu2x.drawButton(s, "start", gmenu2x.tr["Exit"], x);
499                (void)x;
500
501                ss.clear();
502                ss << page+1;
503                ss >> pageStatus;
504                pageStatus = gmenu2x.tr["Page"]+": "+pageStatus+"/"+spagecount;
505                gmenu2x.font->write(s, pageStatus, 310, 230, Font::HAlignRight, Font::VAlignMiddle);
506
507                s.flip();
508                repaint = false;
509            }
510
511            switch(gmenu2x.input.waitForPressedButton()) {
512                case InputManager::SETTINGS:
513                case InputManager::CANCEL:
514                    close = true;
515                    break;
516                case InputManager::LEFT:
517                    if (page > 0) {
518                        page--;
519                        repaint = true;
520                    }
521                    break;
522                case InputManager::RIGHT:
523                    if (page < pagecount-1) {
524                        page++;
525                        repaint=true;
526                    }
527                    break;
528                default:
529                    break;
530            }
531        }
532        return;
533    }
534
535    // Txt manuals
536    if (manual.substr(manual.size()-8,8)==".man.txt") {
537        string text(readFileAsString(manual));
538        TextManualDialog tmd(gmenu2x, getTitle(), getIconPath(), text);
539        tmd.exec();
540        return;
541    }
542
543    //Readmes
544    string str, line;
545    ifstream infile(manual.c_str(), ios_base::in);
546    if (infile.is_open()) {
547        while (getline(infile, line, '\n')) {
548            str.append(line).append("\n");
549        }
550        infile.close();
551
552        TextDialog td(gmenu2x, getTitle(), "ReadMe", getIconPath(), str);
553        td.exec();
554    }
555}
556
557void LinkApp::selector(int startSelection, const string &selectorDir) {
558    //Run selector interface
559    Selector sel(gmenu2x, *this, selectorDir);
560    int selection = sel.exec(startSelection);
561    if (selection!=-1) {
562        const string &selectedDir = sel.getDir();
563        if (!selectedDir.empty()) {
564            selectordir = selectedDir;
565        }
566        gmenu2x.writeTmp(selection, selectedDir);
567        gmenu2x.queueLaunch(
568                prepareLaunch(selectedDir + sel.getFile()),
569                make_shared<LaunchLayer>(*this));
570    }
571}
572
573unique_ptr<Launcher> LinkApp::prepareLaunch(const string &selectedFile) {
574    if (!save()) {
575        ERROR("Error saving app settings to '%s'.\n", file.c_str());
576    }
577
578    if (!isOpk()) {
579        //Set correct working directory
580        string::size_type pos = exec.rfind("/");
581        if (pos != string::npos) {
582            string wd = exec.substr(0, pos + 1);
583            chdir(wd.c_str());
584            exec = wd + exec.substr(pos + 1);
585            DEBUG("Changed working directory to %s\n", wd.c_str());
586        }
587    }
588
589    if (!selectedFile.empty()) {
590        string path = selectedFile;
591        if (!isOpk())
592            path = cmdclean(path);
593
594        if (params.empty()) {
595            params = path;
596        } else {
597            string::size_type pos;
598
599            for (auto token : tokens) {
600                while ((pos = params.find(token)) != params.npos) {
601                    params.replace(pos, 2, path);
602                }
603            }
604        }
605    }
606
607    if (gmenu2x.confInt["outputLogs"] && !consoleApp) {
608        int fd = open(LOG_FILE, O_WRONLY | O_TRUNC | O_CREAT, 0644);
609        if (fd < 0) {
610            ERROR("Unable to open log file for write: %s\n", LOG_FILE);
611        } else {
612            fflush(stdout);
613            dup2(fd, STDOUT_FILENO);
614            dup2(fd, STDERR_FILENO);
615            close(fd);
616        }
617    }
618
619    gmenu2x.saveSelection();
620
621    if (selectedFile.empty()) {
622        gmenu2x.writeTmp();
623    }
624#ifdef ENABLE_CPUFREQ
625    if (clock() != gmenu2x.confInt["menuClock"]) {
626        gmenu2x.setClock(clock());
627    }
628#endif
629
630    vector<string> commandLine;
631    if (isOpk()) {
632#ifdef HAVE_LIBOPK
633        commandLine = { "opkrun", "-m", metadata, opkFile };
634        if (!params.empty()) {
635            commandLine.push_back(params);
636        }
637#endif
638    } else {
639        commandLine = { "/bin/sh", "-c", exec + " " + params };
640    }
641
642    return std::unique_ptr<Launcher>(new Launcher(
643            move(commandLine), consoleApp));
644}
645
646const string &LinkApp::getExec() {
647    return exec;
648}
649
650void LinkApp::setExec(const string &exec) {
651    this->exec = exec;
652    edited = true;
653}
654
655const string &LinkApp::getParams() {
656    return params;
657}
658
659void LinkApp::setParams(const string &params) {
660    this->params = params;
661    edited = true;
662}
663
664const string &LinkApp::getManual() {
665    return manual;
666}
667
668void LinkApp::setManual(const string &manual) {
669    this->manual = manual;
670    edited = true;
671}
672
673const string &LinkApp::getSelectorDir() {
674    return selectordir;
675}
676
677void LinkApp::setSelectorDir(const string &selectordir) {
678    this->selectordir = selectordir;
679    if (!selectordir.empty() && selectordir[selectordir.length() - 1] != '/') {
680        this->selectordir += "/";
681    }
682    edited = true;
683}
684
685bool LinkApp::getSelectorBrowser() {
686    return selectorbrowser;
687}
688
689void LinkApp::setSelectorBrowser(bool value) {
690    selectorbrowser = value;
691    edited = true;
692}
693
694const string &LinkApp::getSelectorFilter() {
695    return selectorfilter;
696}
697
698void LinkApp::setSelectorFilter(const string &selectorfilter) {
699    this->selectorfilter = selectorfilter;
700    edited = true;
701}
702
703void LinkApp::renameFile(const string &name) {
704    file = name;
705}
706

Archive Download this file



interactive