Terminal emulator with all the bells and whistles https://www.enlightenment.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4201 lines
107 KiB

#include <assert.h>
#include <Elementary.h>
#include "win.h"
#include "termcmd.h"
#include "config.h"
#include "main.h"
#include "miniview.h"
#include "gravatar.h"
#include "media.h"
#include "termio.h"
#include "utils.h"
#include "private.h"
#include "dbus.h"
#include "sel.h"
#include "controls.h"
#include "term_container.h"
/**
* Design:
* A terminal widget is Term. It hosts various Evas_Object, like a `termio`
* handling the textgrid.
* It is hosted in a Term_Container of type Solo.
* On Term_Container:
* It is a generic structure with a set of function pointers. It is a simple
* way to objectify and have genericity between a Window, a Split or Tabs.
* Solo, Win, Split, Tabs have a Term_Container as their first field and thus
* can be casted to Term_Container to have access to those APIs.
*
* Solo is the simplest container, hosting just a Term.
* Win is a window and has only one container child.
* Split is a widget to separate an area of the screen in 2 and thus has 2
* children that can be either Solo or Tabs.
* Tabs is a Term_Container containing many containers (at the moment, only
* Solo ones) and have a system of tabs.
*
* All the windows are in the `wins` list.
*/
/* specific log domain to help debug only terminal code parser */
int _win_log_dom = -1;
#undef CRITICAL
#undef ERR
#undef WRN
#undef INF
#undef DBG
#define CRITICAL(...) EINA_LOG_DOM_CRIT(_win_log_dom, __VA_ARGS__)
#define ERR(...) EINA_LOG_DOM_ERR(_win_log_dom, __VA_ARGS__)
#define WRN(...) EINA_LOG_DOM_WARN(_win_log_dom, __VA_ARGS__)
#define INF(...) EINA_LOG_DOM_INFO(_win_log_dom, __VA_ARGS__)
#define DBG(...) EINA_LOG_DOM_DBG(_win_log_dom, __VA_ARGS__)
#if (ELM_VERSION_MAJOR == 1) && (ELM_VERSION_MINOR < 8)
#define PANES_TOP "left"
#define PANES_BOTTOM "right"
#else
#define PANES_TOP "top"
#define PANES_BOTTOM "bottom"
#endif
/* {{{ Structs */
typedef struct _Split Split;
typedef struct _Tabbar Tabbar;
struct _Tabbar
{
struct {
Evas_Object *box;
Eina_List *tabs;
} l, r;
};
struct _Term
{
Win *wn;
Config *config;
Term_Container *container;
Evas_Object *bg;
Evas_Object *base;
Evas_Object *termio;
Evas_Object *media;
Evas_Object *popmedia;
Evas_Object *miniview;
Evas_Object *sel;
Evas_Object *tabcount_spacer;
Evas_Object *tab_spacer;
Evas_Object *tab_region_base;
Evas_Object *tab_region_bg;
Eina_List *popmedia_queue;
Media_Type poptype, mediatype;
Tabbar tabbar;
int step_x, step_y, min_w, min_h, req_w, req_h;
struct {
int x, y;
} down;
int refcnt;
unsigned char hold : 1;
unsigned char unswallowed : 1;
unsigned char missed_bell : 1;
unsigned char miniview_shown : 1;
unsigned char popmedia_deleted : 1;
};
typedef struct _Solo Solo;
typedef struct _Tabs Tabs;
struct _Solo {
Term_Container tc;
Term *term;
};
typedef struct _Tab_Item Tab_Item;
struct _Tab_Item {
Term_Container *tc;
Evas_Object *obj;
void *selector_entry;
};
struct _Tabs {
Term_Container tc;
Evas_Object *selector;
Evas_Object *selector_bg;
Eina_List *tabs; // Tab_Item
Tab_Item *current;
};
struct _Split
{
Term_Container tc;
Term_Container *tc1, *tc2; // left/right or top/bottom child splits, null if leaf
Evas_Object *panes;
Term_Container *last_focus;
unsigned char is_horizontal : 1;
};
struct _Win
{
Term_Container tc;
Term_Container *child;
Evas_Object *win;
Evas_Object *conform;
Evas_Object *backbg;
Evas_Object *base;
Config *config;
Eina_List *terms;
Split *split;
Ecore_Job *size_job;
Evas_Object *cmdbox;
Ecore_Timer *cmdbox_del_timer;
Ecore_Timer *cmdbox_focus_timer;
unsigned char focused : 1;
unsigned char cmdbox_up : 1;
};
/* }}} */
static Eina_List *wins = NULL;
static Eina_Bool _win_is_focused(Win *wn);
static Eina_Bool _term_is_focused(Term *term);
static Term_Container *_solo_new(Term *term, Win *wn);
static Term_Container *_split_new(Term_Container *tc1, Term_Container *tc2, Eina_Bool is_horizontal);
static Term_Container *_tabs_new(Term_Container *child, Term_Container *parent);
static void _term_focus(Term *term);
static void _term_free(Term *term);
static void _term_media_update(Term *term, const Config *config);
static void _term_miniview_check(Term *term);
static void _popmedia_queue_process(Term *term);
static void _cb_size_hint(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event EINA_UNUSED);
static void _tab_new_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);
static Tab_Item* tab_item_new(Tabs *tabs, Term_Container *child);
static void _tabs_refresh(Tabs *tabs);
/* {{{ Solo */
static Evas_Object *
_solo_get_evas_object(Term_Container *tc)
{
Solo *solo;
assert (tc->type == TERM_CONTAINER_TYPE_SOLO);
solo = (Solo*) tc;
return solo->term->bg;
}
static Term *
_solo_focused_term_get(Term_Container *container)
{
Solo *solo;
assert (container->type == TERM_CONTAINER_TYPE_SOLO);
solo = (Solo*)container;
return container->is_focused ? solo->term : NULL;
}
static Term *
_solo_find_term_at_coords(Term_Container *tc,
Evas_Coord mx EINA_UNUSED,
Evas_Coord my EINA_UNUSED)
{
Solo *solo;
assert (tc->type == TERM_CONTAINER_TYPE_SOLO);
solo = (Solo*) tc;
return solo->term;
}
static void
_solo_size_eval(Term_Container *container, Sizeinfo *info)
{
Term *term;
int mw = 0, mh = 0;
Solo *solo;
assert (container->type == TERM_CONTAINER_TYPE_SOLO);
solo = (Solo*)container;
term = solo->term;
info->min_w = term->min_w;
info->min_h = term->min_h;
info->step_x = term->step_x;
info->step_y = term->step_y;
info->req_w = term->req_w;
info->req_h = term->req_h;
if (!evas_object_data_get(term->termio, "sizedone"))
{
evas_object_data_set(term->termio, "sizedone", term->termio);
info->req = 1;
}
evas_object_size_hint_min_get(term->bg, &mw, &mh);
info->bg_min_w = mw;
info->bg_min_h = mh;
}
static void
_solo_close(Term_Container *tc, Term_Container *child EINA_UNUSED)
{
tc->parent->close(tc->parent, tc);
eina_stringshare_del(tc->title);
free(tc);
}
static void
_solo_tabs_new(Term_Container *tc)
{
if (tc->parent->type != TERM_CONTAINER_TYPE_TABS)
_tabs_new(tc, tc->parent);
_tab_new_cb(tc->parent, NULL, NULL);
}
static void
_solo_split(Term_Container *tc, Term_Container *child EINA_UNUSED,
Term *from,
const char *cmd, Eina_Bool is_horizontal)
{
tc->parent->split(tc->parent, tc, from, cmd, is_horizontal);
}
static Term *
_solo_term_next(Term_Container *tc, Term_Container *child EINA_UNUSED)
{
return tc->parent->term_next(tc->parent, tc);
}
static Term *
_solo_term_prev(Term_Container *tc, Term_Container *child EINA_UNUSED)
{
return tc->parent->term_prev(tc->parent, tc);
}
static Term *
_solo_term_first(Term_Container *tc)
{
Solo *solo;
assert (tc->type == TERM_CONTAINER_TYPE_SOLO);
solo = (Solo*) tc;
return solo->term;
}
static Term *
_solo_term_last(Term_Container *tc)
{
Solo *solo;
assert (tc->type == TERM_CONTAINER_TYPE_SOLO);
solo = (Solo*) tc;
return solo->term;
}
static void
_solo_set_title(Term_Container *tc, Term_Container *child EINA_UNUSED,
const char *title)
{
eina_stringshare_del(tc->title);
tc->title = eina_stringshare_add(title);
tc->parent->set_title(tc->parent, tc, title);
}
static void
_solo_bell(Term_Container *tc, Term_Container *child EINA_UNUSED)
{
Solo *solo;
Term *term;
assert (tc->type == TERM_CONTAINER_TYPE_SOLO);
solo = (Solo*) tc;
term = solo->term;
term->missed_bell = EINA_TRUE;
if (!tc->wn->config->disable_visual_bell)
{
edje_object_signal_emit(term->bg, "bell", "terminology");
edje_object_signal_emit(term->base, "bell", "terminology");
if (tc->wn->config->bell_rings)
{
edje_object_signal_emit(term->bg, "bell,ring", "terminology");
edje_object_signal_emit(term->base, "bell,ring", "terminology");
}
}
tc->parent->bell(tc->parent, tc);
}
static void
_solo_unfocus(Term_Container *tc, Term_Container *relative)
{
Solo *solo;
Term *term;
assert (tc->type == TERM_CONTAINER_TYPE_SOLO);
solo = (Solo*) tc;
term = solo->term;
DBG("tc:%p tc->is_focused:%d from_parent:%d",
tc, tc->is_focused, tc->parent == relative);
if (!tc->is_focused)
return;
tc->is_focused = EINA_FALSE;
if (tc->parent != relative)
tc->parent->unfocus(tc->parent, tc);
edje_object_signal_emit(term->bg, "focus,out", "terminology");
edje_object_signal_emit(term->base, "focus,out", "terminology");
if (!tc->wn->cmdbox_up)
elm_object_focus_set(term->termio, EINA_FALSE);
}
static void
_solo_focus(Term_Container *tc, Term_Container *relative)
{
Solo *solo;
Term *term;
const char *title;
assert (tc->type == TERM_CONTAINER_TYPE_SOLO);
solo = (Solo*) tc;
term = solo->term;
if (!tc->parent)
return;
DBG("tc:%p tc->is_focused:%d from_parent:%d",
tc, tc->is_focused, tc->parent == relative);
if (tc->is_focused)
return;
term->missed_bell = EINA_FALSE;
if (tc->parent != relative)
tc->parent->focus(tc->parent, tc);
tc->is_focused = EINA_TRUE;
edje_object_signal_emit(term->bg, "focus,in", "terminology");
edje_object_signal_emit(term->base, "focus,in", "terminology");
if (term->wn->cmdbox)
elm_object_focus_set(term->wn->cmdbox, EINA_FALSE);
elm_object_focus_set(term->termio, EINA_TRUE);
title = termio_title_get(term->termio);
if (title)
tc->set_title(tc, tc, title);
if (term->missed_bell)
term->missed_bell = EINA_FALSE;
}
static void
_solo_update(Term_Container *tc)
{
assert (tc->type == TERM_CONTAINER_TYPE_SOLO);
}
static Term_Container *
_solo_new(Term *term, Win *wn)
{
Term_Container *tc = NULL;
Solo *solo = NULL;
solo = calloc(1, sizeof(Solo));
if (!solo)
{
free(solo);
return NULL;
}
tc = (Term_Container*)solo;
tc->term_next = _solo_term_next;
tc->term_prev = _solo_term_prev;
tc->term_first = _solo_term_first;
tc->term_last = _solo_term_last;
tc->focused_term_get = _solo_focused_term_get;
tc->get_evas_object = _solo_get_evas_object;
tc->split = _solo_split;
tc->find_term_at_coords = _solo_find_term_at_coords;
tc->size_eval = _solo_size_eval;
tc->swallow = NULL;
tc->focus = _solo_focus;
tc->unfocus = _solo_unfocus;
tc->set_title = _solo_set_title;
tc->bell = _solo_bell;
tc->close = _solo_close;
tc->update = _solo_update;
tc->title = eina_stringshare_add("Terminology");
tc->type = TERM_CONTAINER_TYPE_SOLO;
tc->parent = NULL;
tc->wn = wn;
solo->term = term;
term->container = tc;
return tc;
}
/* }}} */
/* {{{ Win */
static void
_cb_win_focus_in(void *data,
Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
Win *wn = data;
Term_Container *tc = (Term_Container*) wn;
Term *term;
DBG("tc:%p tc->is_focused:%d",
tc, tc->is_focused);
if (!tc->is_focused)
elm_win_urgent_set(wn->win, EINA_FALSE);
tc->is_focused = EINA_TRUE;
if ((wn->cmdbox_up) && (wn->cmdbox))
elm_object_focus_set(wn->cmdbox, EINA_TRUE);
term = tc->focused_term_get(tc);
if ( wn->config->mouse_over_focus )
{
Term *term_mouse;
Evas_Coord mx, my;
evas_pointer_canvas_xy_get(evas_object_evas_get(wn->win), &mx, &my);
term_mouse = tc->find_term_at_coords(tc, mx, my);
if ((term_mouse) && (term_mouse != term))
{
if (term)
{
edje_object_signal_emit(term->bg, "focus,out", "terminology");
edje_object_signal_emit(term->base, "focus,out", "terminology");
if (!wn->cmdbox_up)
elm_object_focus_set(term->termio, EINA_FALSE);
}
term = term_mouse;
}
}
if (term)
_term_focus(term);
else
tc->focus(tc, tc);
}
static void
_cb_win_focus_out(void *data, Evas_Object *obj EINA_UNUSED,
void *event EINA_UNUSED)
{
Win *wn = data;
Term_Container *tc = (Term_Container*) wn;
DBG("tc:%p tc->is_focused:%d",
tc, tc->is_focused);
tc->unfocus(tc, NULL);
}
static Eina_Bool
_win_is_focused(Win *wn)
{
Term_Container *tc;
if (!wn)
return EINA_FALSE;
tc = (Term_Container*) wn;
DBG("tc:%p tc->is_focused:%d",
tc, tc->is_focused);
return tc->is_focused;
}
int win_term_set(Win *wn, Term *term)
{
Term_Container *tc_win = NULL, *tc_child = NULL;
Evas_Object *base = win_base_get(wn);
Evas *evas = evas_object_evas_get(base);
tc_child = _solo_new(term, wn);
if (!tc_child)
goto bad;
tc_win = (Term_Container*) wn;
tc_win->swallow(tc_win, NULL, tc_child);
_cb_size_hint(term, evas, term->termio, NULL);
return 0;
bad:
free(tc_child);
return -1;
}
Evas_Object *
win_base_get(Win *wn)
{
return wn->base;
}
Config *win_config_get(Win *wn)
{
return wn->config;
}
Eina_List * win_terms_get(Win *wn)
{
return wn->terms;
}
Evas_Object *
win_evas_object_get(Win *wn)
{
return wn->win;
}
static void
_win_trans(Win *wn, Term *term, Eina_Bool trans)
{
Edje_Message_Int msg;
if (term->config->translucent)
msg.val = term->config->opacity;
else
msg.val = 100;
edje_object_message_send(term->bg, EDJE_MESSAGE_INT, 1, &msg);
edje_object_message_send(term->base, EDJE_MESSAGE_INT, 1, &msg);
if (trans)
{
elm_win_alpha_set(wn->win, EINA_TRUE);
evas_object_hide(wn->backbg);
}
else
{
elm_win_alpha_set(wn->win, EINA_FALSE);
evas_object_show(wn->backbg);
}
}
void
main_trans_update(const Config *config)
{
Win *wn;
Term *term, *term2;
Eina_List *l, *ll;
EINA_LIST_FOREACH(wins, l, wn)
{
EINA_LIST_FOREACH(wn->terms, ll, term)
{
if (term->config == config)
{
if (config->translucent)
_win_trans(wn, term, EINA_TRUE);
else
{
Eina_Bool trans_exists = EINA_FALSE;
EINA_LIST_FOREACH(wn->terms, ll, term2)
{
if (term2->config->translucent)
{
trans_exists = EINA_TRUE;
break;
}
}
_win_trans(wn, term, trans_exists);
}
return;
}
}
}
}
static void
_cb_del(void *data, Evas *e EINA_UNUSED,
Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
Win *wn = data;
// already obj here is deleted - dont do it again
wn->win = NULL;
win_free(wn);
}
void
win_free(Win *wn)
{
Term *term;
wins = eina_list_remove(wins, wn);
EINA_LIST_FREE(wn->terms, term)
{
term_unref(term);
}
if (wn->cmdbox_del_timer)
{
ecore_timer_del(wn->cmdbox_del_timer);
wn->cmdbox_del_timer = NULL;
}
if (wn->cmdbox_focus_timer)
{
ecore_timer_del(wn->cmdbox_focus_timer);
wn->cmdbox_focus_timer = NULL;
}
if (wn->cmdbox)
{
evas_object_del(wn->cmdbox);
wn->cmdbox = NULL;
}
if (wn->win)
{
evas_object_smart_callback_del_full(wn->win, "focus,in", _cb_win_focus_in, wn);
evas_object_smart_callback_del_full(wn->win, "focus,out", _cb_win_focus_out, wn);
evas_object_event_callback_del_full(wn->win, EVAS_CALLBACK_DEL, _cb_del, wn);
evas_object_del(wn->win);
}
if (wn->size_job) ecore_job_del(wn->size_job);
if (wn->config) config_del(wn->config);
free(wn);
}
static Win *
_win_find(Evas_Object *win)
{
Win *wn;
Eina_List *l;
EINA_LIST_FOREACH(wins, l, wn)
{
if (wn->win == win) return wn;
}
return NULL;
}
Eina_List *
terms_from_win_object(Evas_Object *win)
{
Win *wn;
wn = _win_find(win);
if (!wn) return NULL;
return wn->terms;
}
static Evas_Object *
tg_win_add(const char *name, const char *role, const char *title, const char *icon_name)
{
Evas_Object *win, *o;
char buf[4096];
if (!name) name = "main";
if (!title) title = "Terminology";
if (!icon_name) icon_name = "Terminology";
win = elm_win_add(NULL, name, ELM_WIN_BASIC);
elm_win_title_set(win, title);
elm_win_icon_name_set(win, icon_name);
if (role) elm_win_role_set(win, role);
elm_win_autodel_set(win, EINA_TRUE);
o = evas_object_image_add(evas_object_evas_get(win));
snprintf(buf, sizeof(buf), "%s/images/terminology.png",
elm_app_data_dir_get());
evas_object_image_file_set(o, buf, NULL);
elm_win_icon_object_set(win, o);
return win;
}
static Evas_Object *
_win_get_evas_object(Term_Container *tc)
{
Win *wn;
assert (tc->type == TERM_CONTAINER_TYPE_WIN);
wn = (Win*) tc;
return wn->win;
}
static Term *
_win_term_next(Term_Container *tc EINA_UNUSED, Term_Container *child)
{
return child->term_first(child);
}
static Term *
_win_term_prev(Term_Container *tc EINA_UNUSED, Term_Container *child)
{
return child->term_last(child);
}
static Term *
_win_term_first(Term_Container *tc)
{
Win *wn;
assert (tc->type == TERM_CONTAINER_TYPE_WIN);
wn = (Win*) tc;
return wn->child->term_first(wn->child);
}
static Term *
_win_term_last(Term_Container *tc)
{
Win *wn;
assert (tc->type == TERM_CONTAINER_TYPE_WIN);
wn = (Win*) tc;
return wn->child->term_last(wn->child);
}
static Term *
_win_focused_term_get(Term_Container *tc)
{
Win *wn;
assert (tc->type == TERM_CONTAINER_TYPE_WIN);
wn = (Win*) tc;
return tc->is_focused ? wn->child->focused_term_get(wn->child) : NULL;
}
static Term *
_win_find_term_at_coords(Term_Container *tc,
Evas_Coord mx, Evas_Coord my)
{
Win *wn;
assert (tc->type == TERM_CONTAINER_TYPE_WIN);
wn = (Win*) tc;
return wn->child->find_term_at_coords(wn->child, mx, my);
}
static void
_win_size_eval(Term_Container *tc, Sizeinfo *info)
{
Win *wn;
assert (tc->type == TERM_CONTAINER_TYPE_WIN);
wn = (Win*) tc;
wn->child->size_eval(wn->child, info);
}
static void
_win_swallow(Term_Container *tc, Term_Container *orig,
Term_Container *new_child)
{
Win *wn;
Evas_Object *base;
Evas_Object *o;
assert (tc->type == TERM_CONTAINER_TYPE_WIN);
wn = (Win*) tc;
base = win_base_get(wn);
if (orig)
{
o = orig->get_evas_object(orig);
edje_object_part_unswallow(base, o);
}
o = new_child->get_evas_object(new_child);
edje_object_part_swallow(base, "terminology.content", o);