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.

7582 lines
197 KiB

#include "private.h"
#include <assert.h>
#include <Elementary.h>
#include <Elementary_Cursor.h>
#include <Ecore_Input.h>
#include <Ecore_IMF.h>
#include <Ecore_IMF_Evas.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 "theme.h"
#include "sel.h"
#include "controls.h"
#include "keyin.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.
*/
6 years ago
/* specific log domain to help debug code in that file */
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__)
#define PANES_TOP "top"
#define PANES_BOTTOM "bottom"
#define DRAG_TIMEOUT 0.4
/* {{{ Structs */
typedef struct tag_Split Split;
typedef struct tag_Tabbar Tabbar;
typedef struct tag_Solo Solo;
typedef struct tag_Tabs Tabs;
typedef struct tag_Tab_Item Tab_Item;
typedef struct tag_Tab_Drag Tab_Drag;
struct tag_Tab_Drag
{
Evas_Coord mdx; /* Mouse-down x */
Evas_Coord mdy; /* Mouse-down y */
Split_Direction split_direction;
Term *term_over;
Term *term;
Evas_Object *icon;
Evas_Object *img;
Evas *e;
Ecore_Timer *timer;
/* To be able to restore */
Term_Container_Type parent_type;
union {
struct {
int previous_position;
Term_Container *tabs_child;
};
struct {
Term_Container *other;
double left_size;
Eina_Bool is_horizontal;
Eina_Bool is_first_child;
}; };
};
struct tag_Tabbar
{
struct {
Evas_Object *box;
} l, r;
};
struct tag_Term
{
Win *wn;
Config *config;
Term_Container *container;
Evas_Object *bg;
Evas_Object *bg_edj;
Evas_Object *core;
Evas_Object *termio;
Evas_Object *media;
Evas_Object *popmedia;
Evas_Object *miniview;
Evas_Object *sel;
Evas_Object *sendfile_request;
Evas_Object *sendfile_progress;
Evas_Object *sendfile_progress_bar;
Evas_Object *tab_spacer;
Evas_Object *tab_region_base;
Evas_Object *tab_region_bg;
Evas_Object *tab_inactive;
Tab_Item *tab_item;
Eina_List *popmedia_queue;
Ecore_Timer *sendfile_request_hide_timer;
Ecore_Timer *sendfile_progress_hide_timer;
const char *sendfile_dir;
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;
unsigned char has_bg_cursor : 1;
unsigned char core_cursor_set: 1;
Eina_Bool sendfile_request_enabled : 1;
Eina_Bool sendfile_progress_enabled : 1;
};
struct tag_Solo {
Term_Container tc;
Term *term;
};
struct tag_Tab_Item {
Term_Container *tc;
Evas_Object *obj;
void *selector_entry;
};
struct tag_Tabs {
Term_Container tc;
Evas_Object *selector;
Evas_Object *selector_bg;
Eina_List *tabs; // Tab_Item
Tab_Item *current;
double v1_orig;
double v2_orig;
};
struct tag_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 tag_Win
{
Term_Container tc; /* has to be first field */
Keys_Handler khdl;
const char *preedit_str;
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 *hide_cursor_timer;
Ecore_Event_Handler *config_elm_handler;
unsigned char focused : 1;
unsigned char cmdbox_up : 1;
unsigned char group_input : 1;
unsigned char group_only_visible : 1;
unsigned char group_once_handled : 1;
unsigned char translucent : 1;
unsigned int on_popover;
};
/* }}} */
/* {{{ static */
static Eina_List *wins = NULL;
static Tab_Drag *_tab_drag = NULL;
static Eina_Bool _win_is_focused(Win *wn);
static Term_Container *_solo_new(Term *term, Win *wn);
static Term_Container *_split_new(Term_Container *tc1, Term_Container *tc2,
double left_size, Eina_Bool is_horizontal);
static Term_Container *_tabs_new(Term_Container *child, Term_Container *parent);
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_recreate(Tabs *tabs);
static void _tab_drag_free(void);
static void _term_tabregion_free(Term *term);
static void _imf_event_commit_cb(void *data, Ecore_IMF_Context *_ctx EINA_UNUSED, void *event);
/* }}} */
/* {{{ Utils */
#ifndef NDEBUG
static void
_focus_validator(void)
{
Win *wn;
Term *term;
Eina_List *l, *ll;
EINA_LIST_FOREACH(wins, l, wn)
{
Term_Container *focused_found = NULL;
if (wn->group_input)
continue;
EINA_LIST_FOREACH(wn->terms, ll, term)
{
Term_Container *tc = term->container;
if (focused_found)
{
assert(!tc->is_focused);
}
else
{
if (tc->is_focused)
{
Term *term_focused;
Term_Container *tc_parent = tc;
focused_found = tc;
do
{
assert (tc_parent->is_focused);
tc_parent = tc_parent->parent;
if (tc_parent->type == TERM_CONTAINER_TYPE_TABS)
{
Tabs *tabs = (Tabs*) tc_parent;
if (tabs->selector_bg)
return;
}
}
while (tc_parent->type != TERM_CONTAINER_TYPE_WIN);
assert (tc_parent->is_focused);
term_focused = tc_parent->focused_term_get(tc_parent);
assert(term_focused == term);
}
}
}
}
}
#else
static void
_focus_validator(void)
{}
#endif
#define GROUPED_INPUT_TERM_FOREACH(_wn, _list, _term) \
EINA_LIST_FOREACH(_wn->terms, _list, _term) \
if (!_wn->group_only_visible || term_is_visible(_term))
/* }}} */
/* {{{ Scale */
static void
_scale_round(void *data EINA_UNUSED,
Evas_Object *obj,
void *event_info EINA_UNUSED)
{
double val = elm_slider_value_get(obj);
double v;
v = ((double)((int)(val * 10.0))) / 10.0;
if (v != val) elm_slider_value_set(obj, v);
}
static void
_scale_change(void *data EINA_UNUSED,
Evas_Object *obj,
void *event_info EINA_UNUSED)
{
double scale = elm_config_scale_get();
double val = elm_slider_value_get(obj);
if (scale == val)
return;
elm_config_scale_set(val);
elm_config_all_flush();
}
typedef struct tag_Scale_Ctx
{
Evas_Object *hv;
Term *term;
} Scale_Ctx;
static void
_scale_done(void *data,
Evas_Object *obj EINA_UNUSED,
void *event_info EINA_UNUSED)
{
Scale_Ctx *ctx = data;
evas_object_smart_callback_del_full(ctx->hv, "dismissed",
_scale_done, ctx);
evas_object_del(ctx->hv);
ctx->term->wn->on_popover--;
term_unref(ctx->term);
elm_config_save();
config_save(ctx->term->config);
free(ctx);
}
void
win_scale_wizard(Evas_Object *win, Term *term)
{
Evas_Object *bx, *lbl, *sl, *fr, *bt;
const char *txt;
Scale_Ctx *ctx;
EINA_SAFETY_ON_NULL_RETURN(term);
ctx = calloc(1, sizeof(*ctx));
if (!ctx)
return;
ctx->term = term;
term->wn->on_popover++;
term_ref(term);
ctx->hv = elm_hover_add(win);
evas_object_size_hint_weight_set(ctx->hv, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(ctx->hv, EVAS_HINT_FILL, 0.5);
elm_hover_parent_set(ctx->hv, win);
elm_hover_target_set(ctx->hv, win);
evas_object_smart_callback_add(ctx->hv, "dismissed", _scale_done, ctx);
fr = elm_frame_add(win);
evas_object_size_hint_weight_set(fr, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(fr, EVAS_HINT_FILL, 0.5);
elm_object_text_set(fr, _("Scale"));
elm_object_part_content_set(ctx->hv, "middle", fr);
evas_object_show(fr);
bx = elm_box_add(win);
evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.5);
elm_object_content_set(fr, bx);
evas_object_show(bx);
fr = elm_frame_add(win);
evas_object_size_hint_weight_set(fr, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(fr, EVAS_HINT_FILL, 0.5);
elm_object_style_set(fr, "pad_medium");
elm_box_pack_end(bx, fr);
evas_object_show(fr);
lbl = elm_label_add(win);
evas_object_size_hint_weight_set(lbl, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(lbl, EVAS_HINT_FILL, 0.5);
txt = eina_stringshare_printf("<hilight>%s</>",_("Scale"));
elm_object_text_set(lbl, txt);
eina_stringshare_del(txt);
elm_object_content_set(fr, lbl);
elm_box_pack_end(bx, lbl);
evas_object_show(lbl);
sl = elm_slider_add(win);
evas_object_size_hint_weight_set(sl, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(sl, EVAS_HINT_FILL, 0.5);
elm_slider_span_size_set(sl, 120);
elm_slider_unit_format_set(sl, "%1.2f");
elm_slider_indicator_format_set(sl, "%1.2f");
elm_slider_min_max_set(sl, 0.25, 5.0);
elm_slider_value_set(sl, elm_config_scale_get());
elm_box_pack_end(bx, sl);
evas_object_show(sl);
evas_object_smart_callback_add(sl, "changed", _scale_round, NULL);
evas_object_smart_callback_add(sl, "delay,changed", _scale_change, NULL);
bt = elm_button_add(win);
elm_object_text_set(bt, _("Done"));
elm_box_pack_end(bx, bt);
evas_object_smart_callback_add(bt, "clicked", _scale_done, ctx);
evas_object_show(bt);
lbl = elm_label_add(win);
evas_object_size_hint_weight_set(lbl, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(lbl, EVAS_HINT_FILL, 0.5);
elm_object_text_set(lbl, _("Select preferred size so that this text is readable"));
elm_label_line_wrap_set(lbl, ELM_WRAP_WORD);
elm_box_pack_end(bx, lbl);
evas_object_show(lbl);
lbl = elm_label_add(win);
evas_object_size_hint_weight_set(lbl, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(lbl, EVAS_HINT_FILL, 0.5);
elm_object_text_set(lbl, _("The scale configuration can be changed in the Settings (right click on the terminal) → Toolkit, or by starting the command <keyword>elementary_config</keyword>"));
elm_label_line_wrap_set(lbl, ELM_WRAP_WORD);
elm_box_pack_end(bx, lbl);
evas_object_show(lbl);
evas_object_show(ctx->hv);
elm_object_focus_set(ctx->hv, EINA_TRUE);
elm_object_focus_set(bt, EINA_TRUE);
}
/* }}} */
/* {{{ Solo */
static Evas_Object *
5 years ago
_solo_get_evas_object(const 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(const Term_Container *tc)
{
Solo *solo;
Term *term = NULL;
assert (tc->type == TERM_CONTAINER_TYPE_SOLO);
solo = (Solo*)tc;
if (tc->is_focused)
term = solo->term;
return term;
}
static Term *
5 years ago
_solo_find_term_at_coords(const 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)
{
Solo *solo;
Term *term;
DBG("close");
assert (tc->type == TERM_CONTAINER_TYPE_SOLO);
solo = (Solo*) tc;
tc->parent->close(tc->parent, tc);
eina_stringshare_del(tc->title);
term = solo->term;
term->container = NULL;
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 int
_solo_split_direction(Term_Container *tc,
Term_Container *child_orig EINA_UNUSED,
Term_Container *child_new,
Split_Direction direction)
{
return tc->parent->split_direction(tc->parent, tc, child_new, direction);
}
static Term *
5 years ago
_solo_term_next(const Term_Container *tc,
const Term_Container *_child EINA_UNUSED)
{
return tc->parent->term_next(tc->parent, tc);
}
static Term *
5 years ago
_solo_term_prev(const Term_Container *tc,
const Term_Container *_child EINA_UNUSED)
{
return tc->parent->term_prev(tc->parent, tc);
}
static Term *
_solo_term_up(const Term_Container *tc,
const Term_Container *_child EINA_UNUSED)
{
return tc->parent->term_up(tc->parent, tc);
}
static Term *
_solo_term_down(const Term_Container *tc,
const Term_Container *_child EINA_UNUSED)
{
return tc->parent->term_down(tc->parent, tc);
}
static Term *
_solo_term_left(const Term_Container *tc,
const Term_Container *_child EINA_UNUSED)
{
return tc->parent->term_left(tc->parent, tc);
}
static Term *
_solo_term_right(const Term_Container *tc,
const Term_Container *_child EINA_UNUSED)
{
return tc->parent->term_right(tc->parent, tc);
}
static Term *
5 years ago
_solo_term_first(const Term_Container *tc)
{
Solo *solo;
assert (tc->type == TERM_CONTAINER_TYPE_SOLO);
solo = (Solo*) tc;
return solo->term;
}
static Term *
5 years ago
_solo_term_last(const 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)
{
Solo *solo;
Term *term;
assert (tc->type == TERM_CONTAINER_TYPE_SOLO);
solo = (Solo*) tc;
term = solo->term;
eina_stringshare_del(tc->title);
tc->title = eina_stringshare_add(title);
if (term->config->show_tabs)
{
elm_layout_text_set(term->bg, "terminology.tab.title", title);
}
if (_tab_drag && _tab_drag->term == term && _tab_drag->icon)
{
elm_layout_text_set(_tab_drag->icon,
"terminology.title", 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;
if (!tc->is_focused)
term->missed_bell = EINA_TRUE;
if (!tc->wn->config->disable_visual_bell)
{
elm_layout_signal_emit(term->bg, "bell", "terminology");
elm_layout_signal_emit(term->core, "bell", "terminology");
if (tc->wn->config->bell_rings)
{
elm_layout_signal_emit(term->bg, "bell,ring", "terminology");
elm_layout_signal_emit(term->core, "bell,ring", "terminology");
}
if ((_tab_drag != NULL) && (_tab_drag->term == term))
{
elm_layout_signal_emit(_tab_drag->icon, "bell", "terminology");
}
}
if ((term->missed_bell) && (term->config->show_tabs)
&& (tc->parent->type == TERM_CONTAINER_TYPE_SPLIT))
{
elm_layout_signal_emit(term->bg, "tab,bell,on", "terminology");
}
edje_object_message_signal_process(term->bg_edj);
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 term:%p",
tc, tc->is_focused, tc->parent == relative, term);
if (!tc->is_focused)
return;
tc->is_focused = EINA_FALSE;
termio_focus_out(term->termio);
if (tc->parent != relative)
tc->parent->unfocus(tc->parent, tc);
if (!term->config->disable_focus_visuals)
{
elm_layout_signal_emit(term->bg, "focus,out", "terminology");
elm_layout_signal_emit(term->core, "focus,out", "terminology");
}
}
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 term:%p",
tc, tc->is_focused, tc->parent == relative, term);
if (tc->is_focused)
return;
term->missed_bell = EINA_FALSE;
if ((term->config->show_tabs)
&& (tc->parent->type == TERM_CONTAINER_TYPE_SPLIT))
{
elm_layout_signal_emit(term->bg, "tab,bell,off", "terminology");
}
if (term->config->disable_focus_visuals)
{
elm_layout_signal_emit(term->bg, "focused,set", "terminology");
elm_layout_signal_emit(term->core, "focused,set", "terminology");
}
else