/*
* xfce4-minicmd-plugin
* Copyright (C) 2003 Biju Philip Chacko (botsie@users.sf.net)
* Copyright (C) 2003 Eduard Roccatello (master@spine-group.org)
*
* Based on code from:
*
* xfrun4
* Copyright (C) 2000, 2002 Olivier Fourdan (fourdan@xfce.org)
* Copyright (C) 2002 Jasper Huijsmans (huysmans@users.sourceforge.net)
* Copyright (C) 2003 Eduard Roccatello (master@spine-group.org)
*
* command.c
* Copyright (C) Dan <daniel@ats.energo.ru>
*
* xfce4-sample-plugin
* Copyright (c) 2003 Benedikt Meurer <benedikt.meurer@unix-ag.uni-siegen.de>
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
/*
* Copyright (c) 2003 Benedikt Meurer <benedikt.meurer@unix-ag.uni-siegen.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <libxfce4util/libxfce4util.h>
#include <libxfcegui4/libxfcegui4.h>
#include <libxfce4panel/xfce-panel-plugin.h>
#ifndef PATH_MAX
#define DEFAULT_LENGTH 1024
#else
#if (PATH_MAX < 1024)
#define DEFAULT_LENGTH 1024
#else
#define DEFAULT_LENGTH PATH_MAX
#endif
#endif
#define HFILE ("xfce4" G_DIR_SEPARATOR_S "xfrun_history")
#define MAXHISTORY 10
/*
* Types
*/
typedef struct {
gchar *command;
gboolean in_terminal;
} XFCommand;
typedef struct {
GtkWidget *ebox;
GtkWidget *entry; /* command entry */
} t_command;
/*
* Constants
*/
const int DISPLAY_CHARS = 10; /* Number of characters to display in entry */
/*
* Globals
*/
static GList *History = NULL; /* Command Entry History */
static char *Fileman = NULL; /* Default File Manager */
static GList *Curr = NULL; /* Current History Item Being Displayed */
GCompletion *complete; /* Command Completion Structure */
gint nComplete; /* Number of iteration done */
/* Load history items in *complete item for autocompletion */
GCompletion *load_completion (void) {
GList *hitem, *hstrings;
XFCommand *current;
for (hitem = History, hstrings = NULL; hitem != NULL; hitem = hitem->next) {
current = hitem->data;
hstrings = g_list_append(hstrings,current->command);
}
complete = g_completion_new(NULL);
if (hstrings != NULL) {
g_completion_add_items(complete, hstrings);
}
return complete;
}
static gboolean do_run(const char *cmd, gboolean in_terminal)
{
gchar *execute, *path;
gboolean success;
g_return_val_if_fail(cmd != NULL, FALSE);
/* this is only used to prevent us to open a directory in the
* users's home dir with the same name as an executable,
* e.g. evolution */
path = g_find_program_in_path(cmd);
/* open directory in terminal or file manager */
if (g_file_test(cmd, G_FILE_TEST_IS_DIR) && !path) {
if (in_terminal)
execute = g_strconcat("xfterm4 ", cmd, NULL);
else
execute = g_strconcat(Fileman, " ", cmd, NULL);
} else {
if (in_terminal)
execute = g_strconcat("xfterm4 -e ", cmd, NULL);
else
execute = g_strdup(cmd);
}
g_free(path);
success = exec_command(execute);
g_free(execute);
return success;
}
static char *get_fileman(void)
{
const char *var = g_getenv("FILEMAN");
if (var && strlen(var))
return g_strdup(var);
else
return g_strdup("xffm");
}
GList *get_history(void)
{
FILE *fp;
char *hfile = xfce_resource_lookup (XFCE_RESOURCE_CACHE, HFILE);
char line[DEFAULT_LENGTH];
char *check;
GList *cbtemp = NULL;
XFCommand *current;
int i = 0;
if (!hfile)
return NULL;
if (!(fp = fopen (hfile, "r"))) {
g_free (hfile);
return NULL;
}
line[DEFAULT_LENGTH - 1] = '\0';
/* Add a single blank line at the begining */
current = g_new0(XFCommand, 1);
current->command = g_strdup("");
current->in_terminal = FALSE;
cbtemp = g_list_append(cbtemp, current);
/* no more than MAXHISTORY history items */
for (i = 0; i < MAXHISTORY && fgets(line, DEFAULT_LENGTH - 1, fp); i++) {
if ((line[0] == '\0') || (line[0] == '\n'))
break;
current = g_new0(XFCommand, 1);
if ((check = strrchr(line, '\n')))
*check = '\0';
if ((check = strrchr(line, ' '))) {
*check = '\0';
check++;
current->in_terminal = (atoi(check) != 0);
} else {
current->in_terminal = FALSE;
}
current->command = g_strdup(line);
cbtemp = g_list_append(cbtemp, current);
}
g_free(hfile);
fclose(fp);
return cbtemp;
}
void put_history(const char *newest, gboolean in_terminal, GList * cb)
{
FILE *fp;
char *hfile = xfce_resource_save_location (XFCE_RESOURCE_CACHE, HFILE, TRUE);
GList *node;
int i;
if (!(fp = fopen(hfile, "w"))) {
g_warning(_
("xfce4-minicmd-plugin: Could not write history to file %s\n"),
hfile);
g_free(hfile);
return;
}
fprintf(fp, "%s %d\n", newest, in_terminal);
i = 1;
for (node = cb; node != NULL && i < MAXHISTORY; node = node->next) {
XFCommand *current = (XFCommand *) node->data;
if (current->command && strlen(current->command) &&
(strcmp(current->command, newest) != 0)) {
fprintf(fp, "%s %d\n", current->command, current->in_terminal);
i++;
}
}
fclose(fp);
g_free(hfile);
}
static void free_hitem(XFCommand * hitem)
{
DBG("Freeing command line");
g_free(hitem->command);
DBG("Freeing Data Structure");
g_free(hitem);
}
static void free_history(GList *history)
{
GList *tmp;
XFCommand *hitem;
tmp = History;
while(tmp) {
hitem = (XFCommand *)tmp->data;
DBG("Freeing Item: %s", hitem->command);
free_hitem(hitem);
DBG("Freed Item");
tmp->data = NULL;
tmp = g_list_next(tmp);
}
DBG("Freeing List");
g_list_free(history);
return;
}
static void scroll_history(gboolean forward, gint count)
{
int n = 0; /* counter */
GList *tmp = NULL;
if (History) {
tmp = Curr ? Curr : History;
if (forward) {
for (n = 0; (n < count) && tmp; n++) {
tmp = g_list_next(tmp);
}
} else {
for (n = 0; (n < count) && tmp; n++) {
tmp = g_list_previous(tmp);
}
}
Curr = tmp ? tmp : Curr;
}
return;
}
static gboolean entry_buttonpress_cb(GtkWidget *entry, GdkEventButton *event, gpointer data)
{
static Atom atom = 0;
GtkWidget *toplevel = gtk_widget_get_toplevel (entry);
if (event->button != 3 && toplevel && toplevel->window) {
XClientMessageEvent xev;
if (G_UNLIKELY(!atom))
atom = XInternAtom (GDK_DISPLAY(), "_NET_ACTIVE_WINDOW", FALSE);
xev.type = ClientMessage;
xev.window = GDK_WINDOW_XID (toplevel->window);
xev.message_type = atom;
xev.format = 32;
xev.data.l[0] = 0;
xev.data.l[1] = 0;
xev.data.l[2] = 0;
xev.data.l[3] = 0;
xev.data.l[4] = 0;
XSendEvent (GDK_DISPLAY (), GDK_ROOT_WINDOW (), False,
StructureNotifyMask, (XEvent *) & xev);
gtk_widget_grab_focus (entry);
}
return FALSE;
}
static gboolean entry_keypress_cb(GtkWidget *entry, GdkEventKey *event, gpointer user_data)
{
static gboolean terminal = FALSE; /* Run in a terminal? */
gboolean selected = FALSE; /* Is there any selection? */
const gchar *cmd = NULL; /* command line to execute */
const gchar *prefix = NULL; /* common prefix */
XFCommand *hitem = NULL; /* history item data */
GList *similar = NULL; /* list of similar commands */
gint selstart; /* selection start */
gint i; /* just a counter */
gint len; /* command length */
switch (event->keyval) {
case GDK_Down:
scroll_history(TRUE,1);
if (Curr) {
hitem = (XFCommand *) Curr->data;
terminal = hitem->in_terminal;
gtk_entry_set_text(GTK_ENTRY(entry), hitem->command);
}
return TRUE;
case GDK_Up:
scroll_history(FALSE,1);
if (Curr) {
hitem = (XFCommand *) Curr->data;
terminal = hitem->in_terminal;
gtk_entry_set_text(GTK_ENTRY(entry), hitem->command);
}
return TRUE;
case GDK_Return:
cmd = gtk_entry_get_text(GTK_ENTRY(entry));
if ((event->state) & GDK_CONTROL_MASK) {
terminal = TRUE;
}
if (do_run(cmd, terminal)) {
put_history(cmd, terminal, History); /* save this cmdline to history */
free_history(History); /* Delete current history */
g_completion_free(complete); /* Free completion items */
History = get_history(); /* reload modified history */
complete = load_completion(); /* Reload completion items */
Curr = NULL; /* reset current history item pointer */
terminal = FALSE; /* Reset run in term flag */
gtk_entry_set_text(GTK_ENTRY(entry), ""); /* clear the entry */
}
return TRUE;
case GDK_Tab:
cmd = gtk_entry_get_text(GTK_ENTRY(entry));
if ((len = g_utf8_strlen(cmd, -1)) != 0) {
/* Check for a selection */
if ((selected = gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), &selstart, NULL)) && selstart != 0) {
nComplete++;
prefix = g_strndup(cmd, selstart);
}
else {
nComplete = 0;
prefix = cmd;
}
/* Make the completion if there is items in similar */
if ((similar = g_completion_complete(complete, prefix, NULL)) != NULL) {
/* Choose next element */
if (selected && selstart != 0) {
if (nComplete >= g_list_length(similar))
nComplete = 0;
for (i=0; i<nComplete; i++) {
if (similar->next!=NULL)
similar = similar->next;
}
}
/* Write in the entry and select the added text */
gtk_entry_set_text(GTK_ENTRY(entry), similar->data);
gtk_editable_select_region(GTK_EDITABLE(entry), (selstart == 0 ? len : selstart), -1);
}
}
return TRUE;
default:
/* hand over to default signal handler */
return FALSE;
}
}
/*
* command_new: Initialises the widgets
*/
static t_command *command_new(void)
{
t_command *command;
command = g_new(t_command, 1);
command->ebox = gtk_event_box_new();
gtk_widget_show(command->ebox);
command->entry = gtk_entry_new();
gtk_entry_set_width_chars(GTK_ENTRY(command->entry), DISPLAY_CHARS);
gtk_widget_show(command->entry);
gtk_container_add(GTK_CONTAINER(command->ebox), command->entry);
g_signal_connect(command->entry, "key-press-event", G_CALLBACK(entry_keypress_cb), command);
g_signal_connect (command->entry, "button-press-event", G_CALLBACK(entry_buttonpress_cb), NULL);
return (command);
}
static void command_free(XfcePanelPlugin *plug, gpointer data)
{
t_command *command;
g_return_if_fail(data != NULL);
command = (t_command *) data;
g_free(command);
}
static void command_construct(XfcePanelPlugin * plug)
{
t_command *command;
command = command_new();
History = get_history();
complete = load_completion();
Fileman = get_fileman();
gtk_container_add(GTK_CONTAINER(plug), command->ebox);
xfce_panel_plugin_add_action_widget(plug, command->ebox);
g_signal_connect(plug, "free-data", G_CALLBACK(command_free), command);
}
XFCE_PANEL_PLUGIN_REGISTER_INTERNAL(command_construct);
syntax highlighted by Code2HTML, v. 0.9.1