mirror of
https://github.com/edeproject/ede.git
synced 2023-08-10 21:13:03 +03:00
672 lines
16 KiB
C++
672 lines
16 KiB
C++
/*
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2012 Sanel Zukan
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/resource.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
|
|
#include <FL/x.H>
|
|
#include <FL/Fl.H>
|
|
#include <FL/Fl_Box.H>
|
|
#include <FL/Fl_Button.H>
|
|
#include <FL/Fl_Check_Button.H>
|
|
#include <FL/Fl_Input.H>
|
|
#include <FL/Fl_Pixmap.H>
|
|
|
|
#include <edelib/Run.h>
|
|
#include <edelib/Resource.h>
|
|
#include <edelib/Window.h>
|
|
#include <edelib/Debug.h>
|
|
#include <edelib/Missing.h>
|
|
#include <edelib/MessageBox.h>
|
|
#include <edelib/String.h>
|
|
#include <edelib/File.h>
|
|
#include <edelib/WindowUtils.h>
|
|
#include <edelib/DesktopFile.h>
|
|
#include <edelib/StrUtil.h>
|
|
#include <edelib/Debug.h>
|
|
#include <edelib/Regex.h>
|
|
#include <edelib/Util.h>
|
|
#include <edelib/Ede.h>
|
|
#include "StartupNotify.h"
|
|
|
|
#include "icons/run.xpm"
|
|
|
|
/* so value could be directed returned to shell */
|
|
#define RETURN_FROM_BOOL(v) (v != false)
|
|
|
|
/* config name where all things are stored and read from */
|
|
#define EDE_LAUNCH_CONFIG "ede-launch"
|
|
|
|
/* patterns for guessing user input */
|
|
#define REGEX_PATTERN_MAIL "\\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}\\b"
|
|
#define REGEX_PATTERN_URL "((http|https|ftp|gopher|!file):\\/\\/|www)[a-zA-Z0-9\\-\\._]+\\/?[a-zA-Z0-9_\\.\\-\\?\\+\\/~=&#;,]*[a-zA-Z0-9\\/]{1}"
|
|
|
|
EDELIB_NS_USING_AS(Window, LaunchWindow)
|
|
EDELIB_NS_USING_LIST(14, (Resource,
|
|
Regex,
|
|
String,
|
|
list,
|
|
DesktopFile,
|
|
RES_USER_ONLY,
|
|
DESK_FILE_TYPE_APPLICATION,
|
|
run_sync, run_async, alert, file_path, window_center_on_screen, str_ends, system_data_dirs))
|
|
|
|
static Fl_Pixmap image_run((const char**)run_xpm);
|
|
static Fl_Input* dialog_input;
|
|
static Fl_Check_Button* in_term;
|
|
|
|
static void help(void) {
|
|
puts("Usage: ede-launch [OPTIONS] [PROGRAM]");
|
|
puts("EDE program launcher");
|
|
puts("Options:");
|
|
puts(" -h, --help show this help");
|
|
puts(" -l, --launch [TYPE] [PARAMETERS] launch preferred application of TYPE with");
|
|
puts(" given PARAMETERS; see Types below");
|
|
puts(" -w, --working-dir [DIR] run programs with DIR as working directory\n");
|
|
puts("Types:");
|
|
puts(" browser preferred web browser");
|
|
puts(" mail preferred mail reader");
|
|
puts(" terminal preferred terminal");
|
|
puts(" file_manager preferred file manager\n");
|
|
puts("Example:");
|
|
puts(" ede-launch --launch browser http://www.foo.com");
|
|
puts(" ede-launch --launch mail mailto:foo@foo.com");
|
|
puts(" ede-launch foo@foo.com");
|
|
puts(" ede-launch gvim");
|
|
puts(" ede-launch ~/Desktop/foo.desktop");
|
|
}
|
|
|
|
static int re_match(const char *p, const char *str) {
|
|
static Regex re;
|
|
|
|
E_RETURN_VAL_IF_FAIL(re.compile(p) == true, -1);
|
|
return re.match(str);
|
|
}
|
|
|
|
static const char *resource_get(Resource &rc, const char *g, const char *k) {
|
|
static char buf[64];
|
|
|
|
if(!rc) rc.load(EDE_LAUNCH_CONFIG);
|
|
E_RETURN_VAL_IF_FAIL(rc != false, NULL);
|
|
|
|
if(rc.get(g, k, buf, sizeof(buf)))
|
|
return (const char*)buf;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static char* get_basename(const char* path) {
|
|
char *p = (char*)strrchr(path, '/');
|
|
if(p)
|
|
return (p + 1);
|
|
|
|
return (char*)p;
|
|
}
|
|
|
|
static char** cmd_split(const char* cmd) {
|
|
int sz = 10;
|
|
int i = 0;
|
|
char* c = strdup(cmd);
|
|
|
|
char** arr = (char**)malloc(sizeof(char*) * sz);
|
|
|
|
for(char* p = strtok(c, " "); p; p = strtok(NULL, " ")) {
|
|
if(i >= sz) {
|
|
sz *= 2;
|
|
arr = (char**)realloc(arr, sizeof(char*) * sz);
|
|
}
|
|
arr[i++] = strdup(p);
|
|
}
|
|
|
|
arr[i] = NULL;
|
|
free(c);
|
|
|
|
return arr;
|
|
}
|
|
|
|
static bool allowed_launch_type(const char *t) {
|
|
E_RETURN_VAL_IF_FAIL(t != 0, false);
|
|
|
|
/* should be synced with keys from ede-preferred-applications */
|
|
static const char *launch_types[] = {
|
|
"browser",
|
|
"mail",
|
|
"terminal",
|
|
"file_manager",
|
|
0
|
|
};
|
|
|
|
for(int i = 0; launch_types[i]; i++) {
|
|
/* we do not allow >= 64 chars from user input */
|
|
if(strncmp(t, launch_types[i], 64) == 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void start_crasher(const char* cmd, int sig) {
|
|
const char* base = get_basename(cmd);
|
|
const char* ede_app_flag = "";
|
|
|
|
/* this means the app was called without full path */
|
|
if(!base) base = cmd;
|
|
|
|
/*
|
|
* determine is our app by checking the prefix; we don't want user to send bug reports about crashes
|
|
* of foreign applications
|
|
*/
|
|
if(strncmp(base, "ede-", 4) == 0)
|
|
ede_app_flag = "--edeapp";
|
|
|
|
/* call edelib implementation instead start_child_process() to prevents loops if 'ede-crasher' crashes */
|
|
run_sync(PREFIX "/bin/ede-crasher %s --appname %s --apppath %s --signal %i", ede_app_flag, base, cmd, sig);
|
|
}
|
|
|
|
static int start_child_process(const char* cmd) {
|
|
int pid, in[2], out[2], err[2];
|
|
char** params = cmd_split(cmd);
|
|
|
|
pipe(in);
|
|
pipe(out);
|
|
pipe(err);
|
|
|
|
signal(SIGCHLD, SIG_DFL);
|
|
|
|
pid = fork();
|
|
switch(pid) {
|
|
case 0:
|
|
/* child process */
|
|
close(0);
|
|
dup(in[0]);
|
|
close(in[0]);
|
|
close(in[1]);
|
|
|
|
close(1);
|
|
dup(out[1]);
|
|
close(out[0]);
|
|
close(out[1]);
|
|
|
|
close(2);
|
|
dup(err[1]);
|
|
close(err[0]);
|
|
close(err[1]);
|
|
|
|
errno = 0;
|
|
|
|
/* start it */
|
|
execvp(params[0], params);
|
|
|
|
/* some programs use value 2 (tar) */
|
|
if(errno == 2)
|
|
_exit(199);
|
|
else
|
|
_exit(errno);
|
|
break;
|
|
case -1:
|
|
E_WARNING(E_STRLOC ": fork() failed\n");
|
|
/* close the pipes */
|
|
close(in[0]);
|
|
close(in[1]);
|
|
|
|
close(out[0]);
|
|
close(out[1]);
|
|
|
|
close(err[0]);
|
|
close(err[1]);
|
|
break;
|
|
default:
|
|
/* parent */
|
|
close(in[0]);
|
|
close(out[1]);
|
|
close(err[1]);
|
|
break;
|
|
}
|
|
|
|
/* cleanup when returns from the child */
|
|
for(int i = 0; params[i]; i++)
|
|
free(params[i]);
|
|
free(params);
|
|
|
|
int status = 0, ret = 1;
|
|
errno = 0;
|
|
if(waitpid(pid, &status, 0) < 0) {
|
|
E_WARNING(E_STRLOC ": waitpid() failed with '%s'\n", strerror(errno));
|
|
return 1;
|
|
}
|
|
|
|
if(WIFEXITED(status)) {
|
|
ret = WEXITSTATUS(status);
|
|
} else if(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) {
|
|
start_crasher(cmd, SIGSEGV);
|
|
} else {
|
|
E_WARNING(E_STRLOC ": child '%s' killed\n", cmd);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int start_child_process_with_core(const char* cmd) {
|
|
#ifndef __minix
|
|
/*
|
|
* Minix does not have setrlimit() so we disable everything as by default it
|
|
* will dump core.
|
|
*/
|
|
struct rlimit r;
|
|
errno = 0;
|
|
|
|
if(getrlimit(RLIMIT_CORE, &r) == -1) {
|
|
E_WARNING(E_STRLOC ": gerlimit() failed with '%s'\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
rlim_t old = r.rlim_cur;
|
|
r.rlim_cur = RLIM_INFINITY;
|
|
|
|
if(setrlimit(RLIMIT_CORE, &r) == -1) {
|
|
E_WARNING(E_STRLOC ": setrlimit() failed with '%s'\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
#endif
|
|
int ret = start_child_process(cmd);
|
|
|
|
#ifndef __minix
|
|
r.rlim_cur = old;
|
|
if(setrlimit(RLIMIT_CORE, &r) == -1) {
|
|
E_WARNING(E_STRLOC ": setrlimit() failed with '%s'\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool start_child(const char* cmd) {
|
|
E_DEBUG(E_STRLOC ": Starting '%s'\n", cmd);
|
|
|
|
StartupNotify *n = startup_notify_start(cmd, "applications-order");
|
|
int ret = start_child_process_with_core(cmd);
|
|
|
|
startup_notify_end(n);
|
|
|
|
if(ret == 199) {
|
|
alert(_("Program '%s' not found.\n\nPlease check if given path to the "
|
|
"executable was correct or adjust $PATH environment variable to "
|
|
"point to the directory where target executable exists"), cmd);
|
|
|
|
return false;
|
|
}
|
|
|
|
if(ret == EACCES) {
|
|
/* now check the if file is executable since EACCES is common error if not so */
|
|
if(access(cmd, X_OK) != 0)
|
|
alert(_("You are trying to execute '%s', but it is not executable file"), cmd);
|
|
else
|
|
alert(_("You do not have enough permissions to execute '%s'"), cmd);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool start_desktop_file(const char *cmd) {
|
|
DesktopFile d;
|
|
|
|
if(!d.load(cmd)) {
|
|
alert(_("Unable to load .desktop file '%s'. Got: %s"), cmd, d.strerror());
|
|
goto FAIL;
|
|
}
|
|
|
|
if(d.type() != DESK_FILE_TYPE_APPLICATION) {
|
|
alert(_("Starting other types of .desktop files except 'Application' is not supported now"));
|
|
goto FAIL;
|
|
}
|
|
|
|
char buf[PATH_MAX];
|
|
if(!d.exec(buf, PATH_MAX)) {
|
|
alert(_("Unable to run '%s'.\nProbably this file is malformed or 'Exec' key has non-installed program"), cmd);
|
|
goto FAIL;
|
|
}
|
|
|
|
return start_child(buf);
|
|
|
|
FAIL:
|
|
return false;
|
|
}
|
|
|
|
/* concat all arguments preparing it for start_child() */
|
|
static void join_args(int start, int argc, char **argv, const char *program, String &ret, bool is_mailto = false) {
|
|
String args;
|
|
unsigned int alen;
|
|
char sep = ' ';
|
|
|
|
/* append program if given */
|
|
if(program) {
|
|
args = program;
|
|
args += ' ';
|
|
}
|
|
|
|
/* 'mailto' is handled special */
|
|
if(is_mailto) args += "mailto:";
|
|
|
|
for(int i = start; i < argc; i++) {
|
|
args += argv[i];
|
|
args += sep;
|
|
}
|
|
|
|
alen = args.length();
|
|
|
|
/* remove start/ending quotes and spaces */
|
|
if((args[0] == '"') || isspace(args[0]) || (args[alen - 1] == '"') || isspace(args[alen - 1])) {
|
|
int i;
|
|
char *copy = strdup(args.c_str());
|
|
char *ptr = copy;
|
|
|
|
/* remove ending first */
|
|
for(i = (int)alen - 1; i > 0 && (ptr[i] == '"' || isspace(ptr[i])); i--)
|
|
;
|
|
|
|
ptr[i + 1] = 0;
|
|
|
|
/* remove then starting */
|
|
for(; *ptr && (*ptr == '"' || isspace(*ptr)); ptr++)
|
|
;
|
|
|
|
ret = copy;
|
|
free(copy);
|
|
} else {
|
|
ret = args;
|
|
}
|
|
}
|
|
|
|
static void cancel_cb(Fl_Widget*, void* w) {
|
|
LaunchWindow* win = (LaunchWindow*)w;
|
|
win->hide();
|
|
}
|
|
|
|
#define RETURN_IF_VALID_TERM(t, r) \
|
|
do { \
|
|
if(t && ((strcmp(t, "linux") != 0) || (strcmp(t, "dumb") != 0))) { \
|
|
r = file_path(t, false); \
|
|
if(E_UNLIKELY(r.empty())) return true; \
|
|
} \
|
|
} while(0)
|
|
|
|
static bool find_terminal(String &ret) {
|
|
/* before goint to list, try to read it from config file */
|
|
Resource rc;
|
|
const char *t = resource_get(rc, "Preferred", "terminal");
|
|
if(t) {
|
|
ret = t;
|
|
return true;
|
|
}
|
|
|
|
/* list of known terminals */
|
|
static const char *terms[] = {
|
|
"xterm",
|
|
"rxvt",
|
|
"Terminal",
|
|
"gnome-terminal",
|
|
"konsole",
|
|
0
|
|
};
|
|
|
|
const char* term = getenv("TERM");
|
|
|
|
RETURN_IF_VALID_TERM(term, ret);
|
|
|
|
term = getenv("COLORTERM");
|
|
RETURN_IF_VALID_TERM(term, ret);
|
|
|
|
for(int i = 0; terms[i]; i++) {
|
|
term = terms[i];
|
|
RETURN_IF_VALID_TERM(term, ret);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void ok_cb(Fl_Widget*, void* w) {
|
|
LaunchWindow* win = (LaunchWindow*)w;
|
|
const char* cmd = dialog_input->value();
|
|
bool started = false;
|
|
|
|
win->hide();
|
|
|
|
/* do not block dialog when program is starting */
|
|
Fl::check();
|
|
|
|
/* TODO: is 'cmd' safe after hide? */
|
|
if(in_term->value()) {
|
|
char buf[128];
|
|
String term;
|
|
|
|
if(find_terminal(term)) {
|
|
snprintf(buf, sizeof(buf), "%s -e %s", term.c_str(), cmd);
|
|
started = start_child(buf);
|
|
} else {
|
|
E_WARNING(E_STRLOC ": unable to find any suitable terminal\n");
|
|
}
|
|
} else {
|
|
started = start_child(cmd);
|
|
}
|
|
|
|
if(!started) {
|
|
/* show dialog again */
|
|
win->show();
|
|
|
|
if(cmd)
|
|
dialog_input->position(0, dialog_input->size());
|
|
} else {
|
|
Resource rc;
|
|
/* so could load previous content */
|
|
rc.load(EDE_LAUNCH_CONFIG);
|
|
rc.set("History", "open", cmd);
|
|
rc.save(EDE_LAUNCH_CONFIG);
|
|
}
|
|
}
|
|
|
|
static int start_dialog(int argc, char** argv) {
|
|
LaunchWindow* win = new LaunchWindow(370, 195, _("Run Command"));
|
|
win->begin();
|
|
Fl_Box* icon = new Fl_Box(10, 10, 55, 55);
|
|
icon->image(image_run);
|
|
|
|
Fl_Box* txt = new Fl_Box(70, 10, 290, 69, _("Enter the name of the application you would like to run"));
|
|
txt->align(132|FL_ALIGN_INSIDE);
|
|
|
|
dialog_input = new Fl_Input(70, 90, 290, 25, _("Open:"));
|
|
|
|
Resource rc;
|
|
char buf[128];
|
|
|
|
if(rc.load(EDE_LAUNCH_CONFIG) && rc.get("History", "open", buf, sizeof(buf))) {
|
|
dialog_input->value(buf);
|
|
|
|
/* make text appear selected */
|
|
dialog_input->position(0, dialog_input->size());
|
|
}
|
|
|
|
in_term = new Fl_Check_Button(70, 125, 290, 25, _("Run in terminal"));
|
|
in_term->down_box(FL_DOWN_BOX);
|
|
|
|
Fl_Button* ok = new Fl_Button(175, 160, 90, 25, _("&OK"));
|
|
ok->callback(ok_cb, win);
|
|
Fl_Button* cancel = new Fl_Button(270, 160, 90, 25, _("&Cancel"));
|
|
cancel->callback(cancel_cb, win);
|
|
win->end();
|
|
win->window_icon(run_xpm);
|
|
|
|
window_center_on_screen(win);
|
|
win->show(argc, argv);
|
|
|
|
return Fl::run();
|
|
}
|
|
|
|
#define CHECK_ARGV(argv, pshort, plong) ((strcmp(argv, pshort) == 0) || (strcmp(argv, plong) == 0))
|
|
|
|
static const char* next_param(int curr, char **argv, int argc) {
|
|
int j = curr + 1;
|
|
if(j >= argc)
|
|
return NULL;
|
|
if(argv[j][0] == '-')
|
|
return NULL;
|
|
return argv[j];
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
EDE_APPLICATION("ede-launch");
|
|
|
|
int ca = 1; /* current argument index */
|
|
const char *cwd, *launch_type;
|
|
cwd = launch_type = 0;
|
|
|
|
/* in case if ede-launch launches itself; just skip ourself and use the rest of arguments */
|
|
while(argv[ca] && strstr(argv[ca], "ede-launch") != NULL)
|
|
ca++;
|
|
|
|
/* start dialog if we have nothing */
|
|
if(!argv[ca])
|
|
return start_dialog(argc, argv);
|
|
|
|
/* parse args and stop as soon as detected first non-parameter value (not counting parameter values) */
|
|
for(; ca < argc; ca++) {
|
|
if(argv[ca][0] != '-') break;
|
|
|
|
if(CHECK_ARGV(argv[ca], "-h", "--help")) {
|
|
help();
|
|
return 0;
|
|
}
|
|
|
|
if(CHECK_ARGV(argv[ca], "-l", "--launch")) {
|
|
launch_type = next_param(ca, argv, argc);
|
|
if(!launch_type) {
|
|
puts("Missing lauch type. Run program with '-h' to see options");
|
|
return 1;
|
|
}
|
|
|
|
if(!allowed_launch_type(launch_type)) {
|
|
puts("This is not allowed launch type. Run program with '-h' to see options");
|
|
return 1;
|
|
}
|
|
|
|
ca++;
|
|
continue;
|
|
}
|
|
|
|
if(CHECK_ARGV(argv[ca], "-w", "--working-dir")) {
|
|
cwd = next_param(ca, argv, argc);
|
|
if(!cwd) {
|
|
puts("Missing working directory parameter. Run program with '-h' to see options");
|
|
return 1;
|
|
}
|
|
|
|
ca++;
|
|
continue;
|
|
}
|
|
|
|
printf("Bad parameter '%s'. Run program with '-h' to see options\n", argv[ca]);
|
|
return 1;
|
|
}
|
|
|
|
/* make sure we have something to run but only if --launch isn't used */
|
|
if(!argv[ca] && !launch_type) {
|
|
puts("Missing execution parameter(s). Run program with '-h' to see options");
|
|
return 1;
|
|
}
|
|
|
|
/* setup working dir */
|
|
if(cwd) {
|
|
errno = 0;
|
|
if(chdir(cwd) != 0) {
|
|
alert(_("Unable to change directory to '%s'. Got '%s' (%i)"), cwd, strerror(errno), errno);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* check if we have .desktop file */
|
|
if(argv[ca] && str_ends(argv[ca], ".desktop"))
|
|
return RETURN_FROM_BOOL(start_desktop_file(argv[ca]));
|
|
|
|
/* make arguments and exec program */
|
|
String args;
|
|
Resource rc;
|
|
const char *prog = NULL;
|
|
|
|
if(launch_type) {
|
|
/* explicitly launch what user requested */
|
|
prog = resource_get(rc, "Preferred", launch_type);
|
|
|
|
if(!prog) {
|
|
E_WARNING(E_STRLOC ": Unable to find out launch type\n");
|
|
return 1;
|
|
}
|
|
|
|
join_args(ca, argc, argv, prog, args);
|
|
} else {
|
|
bool mailto = false;
|
|
|
|
/*
|
|
* guess what arg we got; if got something like web url or mail address, launch preferred apps with it
|
|
* Note however how this matching works on only one argument; other would be ignored
|
|
*/
|
|
if(re_match(REGEX_PATTERN_MAIL, argv[ca]) > 0) {
|
|
prog = resource_get(rc, "Preferred", "mail");
|
|
if(!prog) {
|
|
E_WARNING(E_STRLOC ": Unable to find mail agent\n");
|
|
return 1;
|
|
}
|
|
|
|
mailto = true;
|
|
|
|
/* use only one argumet */
|
|
argc = ca + 1;
|
|
} else if(re_match(REGEX_PATTERN_URL, argv[ca]) > 0) {
|
|
prog = resource_get(rc, "Preferred", "browser");
|
|
if(!prog) {
|
|
E_WARNING(E_STRLOC ": Unable to find browser\n");
|
|
return 1;
|
|
}
|
|
|
|
/* use only one argumet */
|
|
argc = ca + 1;
|
|
}
|
|
|
|
/* if 'prog' was left to be NULL, argc[ca] will be seen as program to be run */
|
|
join_args(ca, argc, argv, prog, args, mailto);
|
|
}
|
|
|
|
return RETURN_FROM_BOOL(start_child(args.c_str()));
|
|
}
|