Root/src/linkapp.cpp

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 "delegate.h"
25#include "gmenu2x.h"
26#include "menu.h"
27#include "selector.h"
28#include "surface.h"
29#include "textmanualdialog.h"
30#include "utilities.h"
31
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <sys/ioctl.h>
35#include <signal.h>
36#include <stdlib.h>
37#include <unistd.h>
38#include <fcntl.h>
39
40#include <fstream>
41#include <sstream>
42
43#if defined(PLATFORM_A320) || defined(PLATFORM_GCW0)
44#include <linux/vt.h>
45#endif
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 const char *tokens[] = { "%f", "%F", "%u", "%U", };
58
59#ifdef HAVE_LIBOPK
60LinkApp::LinkApp(GMenu2X *gmenu2x_, const char* linkfile, struct OPK *opk)
61#else
62LinkApp::LinkApp(GMenu2X *gmenu2x_, const char* linkfile)
63#endif
64    : Link(gmenu2x_, BIND(&LinkApp::start))
65{
66    manual = "";
67    file = linkfile;
68#ifdef ENABLE_CPUFREQ
69    setClock(gmenu2x->getDefaultAppClock());
70#endif
71    selectordir = "";
72    selectorfilter = "*";
73    icon = iconPath = "";
74    selectorbrowser = true;
75    editable = true;
76    edited = false;
77#if defined(PLATFORM_A320) || defined(PLATFORM_GCW0)
78    consoleApp = false;
79#endif
80
81#ifdef HAVE_LIBOPK
82    isOPK = !!opk;
83    if (isOPK) {
84        string::size_type pos;
85        const char *key, *val;
86        size_t lkey, lval;
87        int ret;
88
89        opkFile = file;
90        pos = file.rfind('/');
91        opkMount = file.substr(pos+1);
92        pos = opkMount.rfind('.');
93        opkMount = opkMount.substr(0, pos);
94
95        file = gmenu2x->getHome() + "/sections/";
96
97        while ((ret = opk_read_pair(opk, &key, &lkey, &val, &lval))) {
98            if (ret < 0) {
99                ERROR("Unable to read meta-data\n");
100                break;
101            }
102
103            char buf[lval + 1];
104            sprintf(buf, "%.*s", lval, val);
105
106            if (!strncmp(key, "Categories", lkey)) {
107                category = buf;
108
109                pos = category.find(';');
110                if (pos != category.npos)
111                    category = category.substr(0, pos);
112                file += category + '/' + opkMount;
113
114            } else if ((!strncmp(key, "Name", lkey) && title.empty())
115                        || !strncmp(key, ("Name[" + gmenu2x->tr["Lng"] +
116                                "]").c_str(), lkey)) {
117                title = buf;
118
119            } else if ((!strncmp(key, "Comment", lkey) && description.empty())
120                        || !strncmp(key, ("Comment[" +
121                                gmenu2x->tr["Lng"] + "]").c_str(), lkey)) {
122                description = buf;
123
124#if defined(PLATFORM_A320) || defined(PLATFORM_GCW0)
125            } else if (!strncmp(key, "Terminal", lkey)) {
126                consoleApp = !strncmp(val, "true", lval);
127#endif
128
129            } else if (!strncmp(key, "X-OD-Manual", lkey)) {
130                manual = buf;
131
132            } else if (!strncmp(key, "Icon", lkey)) {
133                /* Read the icon from the OPK only
134                 * if it doesn't exist on the skin */
135                this->icon = gmenu2x->sc.getSkinFilePath("icons/" + (string) buf + ".png");
136                if (this->icon.empty())
137                    this->icon = (string) linkfile + '#' + buf + ".png";
138                iconPath = this->icon;
139                updateSurfaces();
140
141            } else if (!strncmp(key, "Exec", lkey)) {
142                string tmp = buf;
143                pos = tmp.find(' ');
144                exec = tmp.substr(0, pos);
145
146                if (pos != tmp.npos) {
147                    unsigned int i;
148
149                    params = tmp.substr(pos + 1);
150
151                    for (i = 0; i < sizeof(tokens) / sizeof(tokens[0]); i++) {
152                        if (params.find(tokens[i]) != params.npos) {
153                            selectordir = CARD_ROOT;
154                            break;
155                        }
156                    }
157                }
158
159                continue;
160            }
161
162#ifdef HAVE_LIBXDGMIME
163            if (!strncmp(key, "MimeType", lkey)) {
164                string mimetypes = buf;
165                selectorfilter = "";
166
167                while ((pos = mimetypes.find(';')) != mimetypes.npos) {
168                    int nb = 16;
169                    char *extensions[nb];
170                    string mimetype = mimetypes.substr(0, pos);
171                    mimetypes = mimetypes.substr(pos + 1);
172
173                    nb = xdg_mime_get_extensions_from_mime_type(
174                                mimetype.c_str(), extensions, nb);
175
176                    while (nb--) {
177                        selectorfilter += (string) extensions[nb] + ',';
178                        free(extensions[nb]);
179                    }
180                }
181
182                /* Remove last comma */
183                if (!selectorfilter.empty()) {
184                    selectorfilter.erase(selectorfilter.end());
185                    DEBUG("Compatible extensions: %s\n", selectorfilter.c_str());
186                }
187
188                continue;
189            }
190#endif /* HAVE_LIBXDGMIME */
191        }
192
193        opkMount = (string) "/mnt/" + opkMount + '/';
194        edited = true;
195    }
196#endif /* HAVE_LIBOPK */
197
198    string line;
199    ifstream infile (file.c_str(), ios_base::in);
200    while (getline(infile, line, '\n')) {
201        line = trim(line);
202        if (line.empty()) continue;
203        if (line[0]=='#') continue;
204
205        string::size_type position = line.find("=");
206        string name = trim(line.substr(0,position));
207        string value = trim(line.substr(position+1));
208
209        if (name == "clock") {
210            setClock( atoi(value.c_str()) );
211        } else if (name == "selectordir") {
212            setSelectorDir( value );
213        } else if (name == "selectorbrowser") {
214            if (value=="false") selectorbrowser = false;
215        } else if (name == "selectorscreens") {
216            setSelectorScreens( value );
217        } else if (name == "selectoraliases") {
218            setAliasFile( value );
219        } else if (!isOpk()) {
220            if (name == "title") {
221                title = value;
222            } else if (name == "description") {
223                description = value;
224            } else if (name == "icon") {
225                setIcon(value);
226            } else if (name == "exec") {
227                exec = value;
228            } else if (name == "params") {
229                params = value;
230            } else if (name == "manual") {
231                manual = value;
232#if defined(PLATFORM_A320) || defined(PLATFORM_GCW0)
233            } else if (name == "consoleapp") {
234                if (value == "true") consoleApp = true;
235#endif
236            } else if (name == "selectorfilter") {
237                setSelectorFilter( value );
238            } else if (name == "editable") {
239                if (value == "false")
240                    editable = false;
241            } else
242                WARNING("Unrecognized option: '%s'\n", name.c_str());
243        } else
244            WARNING("Unrecognized option: '%s'\n", name.c_str());
245    }
246    infile.close();
247
248    if (iconPath.empty()) searchIcon();
249}
250
251void LinkApp::loadIcon() {
252    if (icon.compare(0, 5, "skin:") == 0) {
253        string linkIcon = gmenu2x->sc.getSkinFilePath(
254                icon.substr(5, string::npos));
255        if (!fileExists(linkIcon))
256            searchIcon();
257        else
258            setIconPath(linkIcon);
259
260    } else if (!fileExists(icon)) {
261        searchIcon();
262    }
263}
264
265const string &LinkApp::searchIcon() {
266    if (!iconPath.empty())
267        return iconPath;
268
269    string execicon = exec;
270    string::size_type pos = exec.rfind(".");
271    if (pos != string::npos) execicon = exec.substr(0,pos);
272    execicon += ".png";
273    string exectitle = execicon;
274    pos = execicon.rfind("/");
275    if (pos != string::npos)
276        string exectitle = execicon.substr(pos+1,execicon.length());
277
278    if (!gmenu2x->sc.getSkinFilePath("icons/"+exectitle).empty())
279        iconPath = gmenu2x->sc.getSkinFilePath("icons/"+exectitle);
280    else if (fileExists(execicon))
281        iconPath = execicon;
282    else
283        iconPath = gmenu2x->sc.getSkinFilePath("icons/generic.png");
284
285    return iconPath;
286}
287
288int LinkApp::clock() {
289    return iclock;
290}
291
292const string &LinkApp::clockStr(int maxClock) {
293    if (iclock>maxClock) setClock(maxClock);
294    return sclock;
295}
296
297void LinkApp::setClock(int mhz) {
298    iclock = mhz;
299    stringstream ss;
300    sclock = "";
301    ss << iclock << "MHz";
302    ss >> sclock;
303
304    edited = true;
305}
306
307bool LinkApp::targetExists()
308{
309    return fileExists(exec);
310}
311
312bool LinkApp::save() {
313    if (!edited) return false;
314
315    DEBUG("Saving file: %s\n", file.c_str());
316
317    ofstream f(file.c_str());
318    if (f.is_open()) {
319        if (!isOpk()) {
320            if (!title.empty() ) f << "title=" << title << endl;
321            if (!description.empty() ) f << "description=" << description << endl;
322            if (!icon.empty() ) f << "icon=" << icon << endl;
323            if (!exec.empty() ) f << "exec=" << exec << endl;
324            if (!params.empty() ) f << "params=" << params << endl;
325            if (!manual.empty() ) f << "manual=" << manual << endl;
326#if defined(PLATFORM_A320) || defined(PLATFORM_GCW0)
327            if (consoleApp ) f << "consoleapp=true" << endl;
328#endif
329            if (selectorfilter != "*") f << "selectorfilter=" << selectorfilter << endl;
330        }
331        if (iclock != 0 ) f << "clock=" << iclock << endl;
332        if (!selectordir.empty() ) f << "selectordir=" << selectordir << endl;
333        if (!selectorbrowser ) f << "selectorbrowser=false" << endl;
334        if (!selectorscreens.empty() ) f << "selectorscreens=" << selectorscreens << endl;
335        if (!aliasfile.empty() ) f << "selectoraliases=" << aliasfile << endl;
336        f.close();
337        sync();
338        return true;
339    } else
340        ERROR("Error while opening the file '%s' for write.\n", file.c_str());
341    return false;
342}
343
344void LinkApp::drawRun() {
345    //Darkened background
346    gmenu2x->s->box(0, 0, gmenu2x->resX, gmenu2x->resY, 0,0,0,150);
347
348    string text = gmenu2x->tr.translate("Launching $1",getTitle().c_str(),NULL);
349    int textW = gmenu2x->font->getTextWidth(text);
350    int boxW = 62+textW;
351    int halfBoxW = boxW/2;
352
353    //outer box
354    gmenu2x->s->box(gmenu2x->halfX-2-halfBoxW, gmenu2x->halfY-23, halfBoxW*2+5, 47, gmenu2x->skinConfColors[COLOR_MESSAGE_BOX_BG]);
355    //inner rectangle
356    gmenu2x->s->rectangle(gmenu2x->halfX-halfBoxW, gmenu2x->halfY-21, boxW, 42, gmenu2x->skinConfColors[COLOR_MESSAGE_BOX_BORDER]);
357
358    int x = gmenu2x->halfX+10-halfBoxW;
359    /*if (!getIcon().empty())
360        gmenu2x->sc[getIcon()]->blit(gmenu2x->s,x,104);
361    else
362        gmenu2x->sc["icons/generic.png"]->blit(gmenu2x->s,x,104);*/
363    iconSurface->blit(gmenu2x->s,x,gmenu2x->halfY-16);
364    gmenu2x->s->write( gmenu2x->font, text, x+42, gmenu2x->halfY+1, Font::HAlignLeft, Font::VAlignMiddle );
365    gmenu2x->s->flip();
366}
367
368void LinkApp::start() {
369    if (!selectordir.empty())
370        selector();
371    else
372        gmenu2x->queueLaunch(this, "");
373}
374
375void LinkApp::showManual() {
376    if (manual.empty())
377        return;
378
379#ifdef HAVE_LIBOPK
380    if (isOPK) {
381        vector<string> readme;
382        char *token, *ptr;
383        struct OPK *opk;
384        int err;
385        void *buf;
386        size_t len;
387
388        opk = opk_open(opkFile.c_str());
389        if (!opk) {
390            WARNING("Unable to open OPK to read manual\n");
391            return;
392        }
393
394        err = opk_extract_file(opk, manual.c_str(), &buf, &len);
395        if (err < 0) {
396            WARNING("Unable to read manual from OPK\n");
397            return;
398        }
399        opk_close(opk);
400
401        ptr = (char *) buf;
402        while((token = strchr(ptr, '\n'))) {
403            *token = '\0';
404
405            string str(ptr);
406            readme.push_back(str);
407            ptr = token + 1;
408        }
409
410        /* Add the last line */
411        string str(ptr);
412        readme.push_back(str);
413        free(buf);
414
415        if (manual.substr(manual.size()-8,8)==".man.txt") {
416            TextManualDialog tmd(gmenu2x, getTitle(), getIconPath(), &readme);
417            tmd.exec();
418        } else {
419            TextDialog td(gmenu2x, getTitle(), "ReadMe", getIconPath(), &readme);
420            td.exec();
421        }
422        return;
423    }
424#endif
425    if (!fileExists(manual))
426        return;
427
428    // Png manuals
429    if (manual.substr(manual.size()-8,8)==".man.png") {
430#ifdef ENABLE_CPUFREQ
431        //Raise the clock to speed-up the loading of the manual
432        gmenu2x->setSafeMaxClock();
433#endif
434
435        Surface *pngman = Surface::loadImage(manual);
436        if (!pngman) {
437            return;
438        }
439        Surface *bg = Surface::loadImage(gmenu2x->confStr["wallpaper"]);
440        if (!bg) {
441            bg = Surface::emptySurface(gmenu2x->s->width(), gmenu2x->s->height());
442        }
443        bg->convertToDisplayFormat();
444
445        stringstream ss;
446        string pageStatus;
447
448        bool close = false, repaint = true;
449        int page = 0, pagecount = pngman->width() / 320;
450
451        ss << pagecount;
452        string spagecount;
453        ss >> spagecount;
454
455#ifdef ENABLE_CPUFREQ
456        //Lower the clock
457        gmenu2x->setMenuClock();
458#endif
459
460        while (!close) {
461            if (repaint) {
462                bg->blit(gmenu2x->s, 0, 0);
463                pngman->blit(gmenu2x->s, -page*320, 0);
464
465                gmenu2x->drawBottomBar(gmenu2x->s);
466                gmenu2x->drawButton(gmenu2x->s, "start", gmenu2x->tr["Exit"],
467                gmenu2x->drawButton(gmenu2x->s, "cancel", "",
468                gmenu2x->drawButton(gmenu2x->s, "right", gmenu2x->tr["Change page"],
469                gmenu2x->drawButton(gmenu2x->s, "left", "", 5)-10))-10);
470
471                ss.clear();
472                ss << page+1;
473                ss >> pageStatus;
474                pageStatus = gmenu2x->tr["Page"]+": "+pageStatus+"/"+spagecount;
475                gmenu2x->s->write(gmenu2x->font, pageStatus, 310, 230, Font::HAlignRight, Font::VAlignMiddle);
476
477                gmenu2x->s->flip();
478                repaint = false;
479            }
480
481            switch(gmenu2x->input.waitForPressedButton()) {
482                case InputManager::SETTINGS:
483                case InputManager::CANCEL:
484                    close = true;
485                    break;
486                case InputManager::LEFT:
487                    if (page > 0) {
488                        page--;
489                        repaint = true;
490                    }
491                    break;
492                case InputManager::RIGHT:
493                    if (page < pagecount-1) {
494                        page++;
495                        repaint=true;
496                    }
497                    break;
498                default:
499                    break;
500            }
501        }
502        delete bg;
503        return;
504    }
505
506    // Txt manuals
507    if (manual.substr(manual.size()-8,8)==".man.txt") {
508        vector<string> txtman;
509
510        string line;
511        ifstream infile(manual.c_str(), ios_base::in);
512        if (infile.is_open()) {
513            while (getline(infile, line, '\n')) txtman.push_back(line);
514            infile.close();
515
516            TextManualDialog tmd(gmenu2x, getTitle(), getIconPath(), &txtman);
517            tmd.exec();
518        }
519
520        return;
521    }
522
523    //Readmes
524    vector<string> readme;
525
526    string line;
527    ifstream infile(manual.c_str(), ios_base::in);
528    if (infile.is_open()) {
529        while (getline(infile, line, '\n')) readme.push_back(line);
530        infile.close();
531
532        TextDialog td(gmenu2x, getTitle(), "ReadMe", getIconPath(), &readme);
533        td.exec();
534    }
535}
536
537void LinkApp::selector(int startSelection, const string &selectorDir) {
538    //Run selector interface
539    Selector sel(gmenu2x, this, selectorDir);
540    int selection = sel.exec(startSelection);
541    if (selection!=-1) {
542        const string &selectedDir = sel.getDir();
543        if (!selectedDir.empty()) {
544            selectordir = selectedDir;
545        }
546        gmenu2x->writeTmp(selection, selectedDir);
547        gmenu2x->queueLaunch(this, selectedDir + sel.getFile());
548    }
549}
550
551void LinkApp::launch(const string &selectedFile) {
552    save();
553
554    if (isOpk()) {
555#ifdef HAVE_LIBOPK
556        int err;
557
558        /* To be sure... */
559        string cmd = "umount " + opkMount;
560        system(cmd.c_str());
561
562        mkdir(opkMount.c_str(), 0700);
563        cmd = "mount -o loop,nosuid,ro " + opkFile + ' ' + opkMount;
564        err = system(cmd.c_str());
565
566        if (err) {
567            ERROR("Unable to mount OPK\n");
568            return;
569        }
570
571        chdir(opkMount.c_str());
572        if (exec[0] != '/') {
573            string tmp = opkMount + exec.substr(0, exec.find(" "));
574            if (fileExists(tmp))
575                exec = opkMount + exec;
576        }
577#endif
578    } else {
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 = cmdclean(selectedFile);
591
592        if (params.empty()) {
593            params = path;
594        } else {
595            unsigned int i;
596            string::size_type pos;
597
598            for (i = 0; i < sizeof(tokens) / sizeof(tokens[0]); i++)
599                while((pos = params.find(tokens[i])) != params.npos)
600                    params.replace(pos, 2, path);
601        }
602    }
603
604    INFO("Executing '%s' (%s %s)\n", title.c_str(), exec.c_str(), params.c_str());
605
606    //check if we have to quit
607    string command = cmdclean(exec);
608
609    // Check to see if permissions are desirable
610    struct stat fstat;
611    if( stat( command.c_str(), &fstat ) == 0 ) {
612        struct stat newstat = fstat;
613        if( S_IRUSR != ( fstat.st_mode & S_IRUSR ) )
614            newstat.st_mode |= S_IRUSR;
615        if( S_IXUSR != ( fstat.st_mode & S_IXUSR ) )
616            newstat.st_mode |= S_IXUSR;
617        if( fstat.st_mode != newstat.st_mode )
618            chmod( command.c_str(), newstat.st_mode );
619    } // else, well.. we are no worse off :)
620
621    if (!params.empty()) command += " " + params;
622#if defined(PLATFORM_A320) || defined(PLATFORM_GCW0)
623    if (gmenu2x->confInt["outputLogs"] && !consoleApp)
624        command += " &> " + cmdclean(gmenu2x->getHome()) + "/log.txt";
625#else
626    if (gmenu2x->confInt["outputLogs"])
627        command += " &> " + cmdclean(gmenu2x->getHome()) + "/log.txt";
628#endif
629#ifdef HAVE_LIBOPK
630    if (isOPK)
631        command += " ; umount -l " + opkMount;
632#endif
633
634    gmenu2x->saveSelection();
635
636    if (selectedFile.empty()) {
637        gmenu2x->writeTmp();
638    }
639#ifdef ENABLE_CPUFREQ
640    if (clock() != gmenu2x->confInt["menuClock"]) {
641        gmenu2x->setClock(clock());
642    }
643#endif
644    gmenu2x->quit();
645
646    /* Make the terminal we're connected to (via stdin/stdout) our
647        controlling terminal again. Else many console programs are
648        not going to work correctly. Actually this would not be
649        necessary, if SDL correctly restored terminal state after
650        SDL_Quit(). */
651    (void) setsid();
652
653    ioctl(1, TIOCSCTTY, STDOUT_FILENO);
654    (void) dup2(STDOUT_FILENO, 0);
655    (void) dup2(STDOUT_FILENO, 1);
656    (void) dup2(STDOUT_FILENO, 2);
657
658    if (STDOUT_FILENO > 2)
659        close(STDOUT_FILENO);
660
661    int pgid = tcgetpgrp(STDOUT_FILENO);
662    signal(SIGTTOU, SIG_IGN);
663    tcsetpgrp(STDOUT_FILENO, pgid);
664
665#if defined(PLATFORM_A320) || defined(PLATFORM_GCW0)
666    if (consoleApp) {
667        /* Enable the framebuffer console */
668        char c = '1';
669        int fd = open("/sys/devices/virtual/vtconsole/vtcon1/bind", O_WRONLY);
670        if (fd < 0) {
671            WARNING("Unable to open fbcon handle\n");
672        } else {
673            write(fd, &c, 1);
674            close(fd);
675        }
676
677        fd = open("/dev/tty1", O_RDWR);
678        if (fd < 0) {
679            WARNING("Unable to open tty1 handle\n");
680        } else {
681            if (ioctl(fd, VT_ACTIVATE, 1) < 0)
682                WARNING("Unable to activate tty1\n");
683            close(fd);
684        }
685    }
686#endif
687
688    execlp("/bin/sh","/bin/sh", "-c", command.c_str(), NULL);
689    //if execution continues then something went wrong and as we already called SDL_Quit we cannot continue
690    //try relaunching gmenu2x
691    gmenu2x->main();
692}
693
694const string &LinkApp::getExec() {
695    return exec;
696}
697
698void LinkApp::setExec(const string &exec) {
699    this->exec = exec;
700    edited = true;
701}
702
703const string &LinkApp::getParams() {
704    return params;
705}
706
707void LinkApp::setParams(const string &params) {
708    this->params = params;
709    edited = true;
710}
711
712const string &LinkApp::getManual() {
713    return manual;
714}
715
716void LinkApp::setManual(const string &manual) {
717    this->manual = manual;
718    edited = true;
719}
720
721const string &LinkApp::getSelectorDir() {
722    return selectordir;
723}
724
725void LinkApp::setSelectorDir(const string &selectordir) {
726    this->selectordir = selectordir;
727    if (!selectordir.empty() && selectordir[selectordir.length() - 1] != '/') {
728        this->selectordir += "/";
729    }
730    edited = true;
731}
732
733bool LinkApp::getSelectorBrowser() {
734    return selectorbrowser;
735}
736
737void LinkApp::setSelectorBrowser(bool value) {
738    selectorbrowser = value;
739    edited = true;
740}
741
742const string &LinkApp::getSelectorFilter() {
743    return selectorfilter;
744}
745
746void LinkApp::setSelectorFilter(const string &selectorfilter) {
747    this->selectorfilter = selectorfilter;
748    edited = true;
749}
750
751const string &LinkApp::getSelectorScreens() {
752    return selectorscreens;
753}
754
755void LinkApp::setSelectorScreens(const string &selectorscreens) {
756    this->selectorscreens = selectorscreens;
757    edited = true;
758}
759
760const string &LinkApp::getAliasFile() {
761    return aliasfile;
762}
763
764void LinkApp::setAliasFile(const string &aliasfile) {
765    if (fileExists(aliasfile)) {
766        this->aliasfile = aliasfile;
767        edited = true;
768    }
769}
770
771void LinkApp::renameFile(const string &name) {
772    file = name;
773}
774

Archive Download this file



interactive