1 /* Copyright © 2007-2016 Evgeny Ratnikov
2  *
3  * This program is free software: you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation, either version 3 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include <string.h>
18 #include <X11/Xlib.h> // XParseGeometry
19 #include <glib.h>
20 
21 #include <lua.h>
22 #include <lualib.h>
23 #include <lauxlib.h>
24 
25 #include "termit.h"
26 #include "termit_core_api.h"
27 #include "configs.h"
28 #include "keybindings.h"
29 #include "lua_api.h"
30 
31 extern struct TermitData termit;
32 extern struct Configs configs;
33 
34 lua_State* L = NULL;
35 
termit_lua_close()36 void termit_lua_close()
37 {
38     lua_close(L);
39 }
40 
trace_menus(GArray * menus)41 static void trace_menus(GArray* menus)
42 {
43 #ifdef DEBUG
44     guint i = 0;
45     for (; i<menus->len; ++i) {
46         struct UserMenu* um = &g_array_index(menus, struct UserMenu, i);
47         TRACE("%s items %d", um->name, um->items->len);
48         guint j = 0;
49         for (; j<um->items->len; ++j) {
50             struct UserMenuItem* umi = &g_array_index(um->items, struct UserMenuItem, j);
51             TRACE("  %s: (%d) [%s]", umi->name, umi->lua_callback, umi->accel);
52         }
53     }
54 #endif
55 }
56 
termit_config_get_string(gchar ** opt,lua_State * ls,int index)57 void termit_config_get_string(gchar** opt, lua_State* ls, int index)
58 {
59     if (!lua_isnil(ls, index) && lua_isstring(ls, index)) {
60         if (*opt) {
61             g_free(*opt);
62         }
63         *opt = g_strdup(lua_tostring(ls, index));
64     }
65 }
66 
termit_config_get_double(double * opt,lua_State * ls,int index)67 void termit_config_get_double(double* opt, lua_State* ls, int index)
68 {
69     if (!lua_isnil(ls, index) && lua_isnumber(ls, index)) {
70         *opt = lua_tonumber(ls, index);
71     }
72 }
73 
termit_config_getuint(guint * opt,lua_State * ls,int index)74 void termit_config_getuint(guint* opt, lua_State* ls, int index)
75 {
76     if (!lua_isnil(ls, index) && lua_isnumber(ls, index)) {
77         *opt = lua_tointeger(ls, index);
78     }
79 }
80 
termit_config_get_boolean(gboolean * opt,lua_State * ls,int index)81 void termit_config_get_boolean(gboolean* opt, lua_State* ls, int index)
82 {
83     if (!lua_isnil(ls, index) && lua_isboolean(ls, index)) {
84         *opt = lua_toboolean(ls, index);
85     }
86 }
87 
termit_config_get_function(int * opt,lua_State * ls,int index)88 void termit_config_get_function(int* opt, lua_State* ls, int index)
89 {
90     if (!lua_isnil(ls, index) && lua_isfunction(ls, index)) {
91         *opt = luaL_ref(ls, LUA_REGISTRYINDEX); // luaL_ref pops value so we restore stack size
92         lua_pushnil(ls);
93     }
94 }
95 
termit_config_get_color(GdkRGBA ** opt,lua_State * ls,int index)96 void termit_config_get_color(GdkRGBA** opt, lua_State* ls, int index)
97 {
98     gchar* color_str = NULL;
99     termit_config_get_string(&color_str, ls, index);
100     if (color_str) {
101         GdkRGBA color = {};
102         if (gdk_rgba_parse(&color, color_str) == TRUE) {
103             *opt = gdk_rgba_copy(&color);
104             TRACE("color_str=%s", color_str);
105         }
106     }
107     g_free(color_str);
108 }
109 
termit_config_get_erase_binding(VteEraseBinding * opt,lua_State * ls,int index)110 void termit_config_get_erase_binding(VteEraseBinding* opt, lua_State* ls, int index)
111 {
112     gchar* str = NULL;
113     termit_config_get_string(&str, ls, index);
114     *opt = termit_erase_binding_from_string(str);
115     g_free(str);
116 }
117 
termit_config_get_cursor_blink_mode(VteCursorBlinkMode * opt,lua_State * ls,int index)118 void termit_config_get_cursor_blink_mode(VteCursorBlinkMode* opt, lua_State* ls, int index)
119 {
120     gchar* str = NULL;
121     termit_config_get_string(&str, ls, index);
122     *opt = termit_cursor_blink_mode_from_string(str);
123     g_free(str);
124 }
125 
termit_config_get_cursor_shape(VteCursorShape * opt,lua_State * ls,int index)126 void termit_config_get_cursor_shape(VteCursorShape* opt, lua_State* ls, int index)
127 {
128     gchar* str = NULL;
129     termit_config_get_string(&str, ls, index);
130     *opt = termit_cursor_shape_from_string(str);
131     g_free(str);
132 }
133 
matchesLoader(const gchar * pattern,struct lua_State * ls,int index,void * data)134 static void matchesLoader(const gchar* pattern, struct lua_State* ls, int index, void* data)
135 {
136     TRACE("pattern=%s index=%d data=%p", pattern, index, data);
137     if (!lua_isfunction(ls, index)) {
138         ERROR("match [%s] without function: skipping", pattern);
139         return;
140     }
141     GArray* matches = (GArray*)data;
142     struct Match match = {};
143     GError* err = NULL;
144     match.regex = vte_regex_new_for_match(pattern, -1, VTE_REGEX_FLAGS_DEFAULT, &err);
145     if (err) {
146         TRACE("failed to compile regex [%s]: skipping", pattern);
147         return;
148     }
149     match.flags = 0;
150     match.pattern = g_strdup(pattern);
151     termit_config_get_function(&match.lua_callback, ls, index);
152     g_array_append_val(matches, match);
153 }
154 
155 struct ColormapHelper
156 {
157     GdkRGBA* colors;
158     int idx;
159 };
160 
colormapLoader(const gchar * name,lua_State * ls,int index,void * data)161 static void colormapLoader(const gchar* name, lua_State* ls, int index, void* data)
162 {
163     struct ColormapHelper* ch = (struct ColormapHelper*)data;
164     if (!lua_isnil(ls, index) && lua_isstring(ls, index)) {
165         const gchar* colorStr = lua_tostring(ls, index);
166         if (!gdk_rgba_parse(&(ch->colors[ch->idx]), colorStr)) {
167             ERROR("failed to parse color: %s %d - %s", name, ch->idx, colorStr);
168         }
169     } else {
170         ERROR("invalid type in colormap: skipping");
171     }
172     ++ch->idx;
173 }
174 
tabsLoader(const gchar * name,lua_State * ls,int index,void * data)175 static void tabsLoader(const gchar* name, lua_State* ls, int index, void* data)
176 {
177     if (lua_istable(ls, index)) {
178         GArray* tabs = (GArray*)data;
179         struct TabInfo ti = {};
180         if (termit_lua_load_table(ls, termit_lua_tab_loader, index, &ti)
181                 != TERMIT_LUA_TABLE_LOADER_OK) {
182             ERROR("failed to load tab: %s %s", name, lua_tostring(ls, 3));
183         } else {
184             g_array_append_val(tabs, ti);
185         }
186     } else {
187         ERROR("unknown type instead if tab table: skipping");
188         lua_pop(ls, 1);
189     }
190 }
191 
termit_lua_load_colormap(lua_State * ls,int index,GdkRGBA ** colors,glong * sz)192 void termit_lua_load_colormap(lua_State* ls, int index, GdkRGBA** colors, glong* sz)
193 {
194     if (lua_isnil(ls, index) || !lua_istable(ls, index)) {
195         ERROR("invalid colormap type");
196         return;
197     }
198     const int size = lua_rawlen(ls, index);
199     if ((size != 8) && (size != 16) && (size != 24)) {
200         ERROR("bad colormap length: %d", size);
201         return;
202     }
203     struct ColormapHelper ch = {};
204     ch.colors = g_malloc0(size * sizeof(GdkRGBA));
205     if (termit_lua_load_table(ls, colormapLoader, index, &ch)
206             == TERMIT_LUA_TABLE_LOADER_OK) {
207         if (*colors) {
208             g_free(*colors);
209         }
210         *colors = ch.colors;
211         *sz = size;
212     } else {
213         ERROR("failed to load colormap");
214         return;
215     }
216     TRACE("colormap loaded: size=%ld", *sz);
217 }
218 
termit_config_get_position(GtkPositionType * pos,lua_State * ls,int index)219 static void termit_config_get_position(GtkPositionType* pos, lua_State* ls, int index)
220 {
221     if (!lua_isnil(ls, index) && lua_isstring(ls, index)) {
222         const char* str = lua_tostring(ls, index);
223         if (strcmp(str, "Top") == 0) {
224             *pos = GTK_POS_TOP;
225         } else if (strcmp(str, "Bottom") == 0) {
226             *pos = GTK_POS_BOTTOM;
227         } else if (strcmp(str, "Left") == 0) {
228             *pos = GTK_POS_LEFT;
229         } else if (strcmp(str, "Right") == 0) {
230             *pos = GTK_POS_RIGHT;
231         } else {
232             ERROR("unknown pos: [%s]", str);
233         }
234     }
235 }
236 
termit_lua_options_loader(const gchar * name,lua_State * ls,int index,void * data)237 void termit_lua_options_loader(const gchar* name, lua_State* ls, int index, void* data)
238 {
239     struct Configs* p_cfg = (struct Configs*)data;
240     if (!strcmp(name, "tabName")) {
241         termit_config_get_string(&(p_cfg->default_tab_name), ls, index);
242     } else if (!strcmp(name, "windowTitle")) {
243         termit_config_get_string(&(p_cfg->default_window_title), ls, index);
244     } else if (!strcmp(name, "encoding")) {
245         termit_config_get_string(&(p_cfg->default_encoding), ls, index);
246     } else if (!strcmp(name, "wordCharExceptions")) {
247         termit_config_get_string(&(p_cfg->default_word_char_exceptions), ls, index);
248     } else if (!strcmp(name, "font")) {
249         termit_config_get_string(&(p_cfg->style.font_name), ls, index);
250     } else if (!strcmp(name, "foregroundColor")) {
251         termit_config_get_color(&p_cfg->style.foreground_color, ls, index);
252     } else if (!strcmp(name, "backgroundColor")) {
253         termit_config_get_color(&p_cfg->style.background_color, ls, index);
254     } else if (!strcmp(name, "showScrollbar")) {
255         termit_config_get_boolean(&(p_cfg->show_scrollbar), ls, index);
256     } else if (!strcmp(name, "fillTabbar")) {
257         termit_config_get_boolean(&(p_cfg->fill_tabbar), ls, index);
258     } else if (!strcmp(name, "hideSingleTab")) {
259         termit_config_get_boolean(&(p_cfg->hide_single_tab), ls, index);
260     } else if (!strcmp(name, "hideMenubar")) {
261         termit_config_get_boolean(&(p_cfg->hide_menubar), ls, index);
262     } else if (!strcmp(name, "hideTabbar")) {
263         termit_config_get_boolean(&(p_cfg->hide_tabbar), ls, index);
264     } else if (!strcmp(name, "showBorder")) {
265         termit_config_get_boolean(&(p_cfg->show_border), ls, index);
266     } else if (!strcmp(name, "startMaximized")) {
267         termit_config_get_boolean(&(p_cfg->start_maximized), ls, index);
268     } else if (!strcmp(name, "hideTitlebarWhenMaximized")) {
269         termit_config_get_boolean(&(p_cfg->hide_titlebar_when_maximized), ls, index);
270     } else if (!strcmp(name, "scrollbackLines")) {
271         termit_config_getuint(&(p_cfg->scrollback_lines), ls, index);
272     } else if (!strcmp(name, "allowChangingTitle")) {
273         termit_config_get_boolean(&(p_cfg->allow_changing_title), ls, index);
274     } else if (!strcmp(name, "audibleBell")) {
275         termit_config_get_boolean(&(p_cfg->audible_bell), ls, index);
276     } else if (!strcmp(name, "scrollOnOutput")) {
277         termit_config_get_boolean(&(p_cfg->scroll_on_output), ls, index);
278     } else if (!strcmp(name, "scrollOnKeystroke")) {
279         termit_config_get_boolean(&(p_cfg->scroll_on_keystroke), ls, index);
280     } else if (!strcmp(name, "urgencyOnBell")) {
281         termit_config_get_boolean(&(p_cfg->urgency_on_bell), ls, index);
282     } else if (!strcmp(name, "getWindowTitle")) {
283         termit_config_get_function(&(p_cfg->get_window_title_callback), ls, index);
284     } else if (!strcmp(name, "tabPos")) {
285         termit_config_get_position(&(p_cfg->tab_pos), ls, index);
286     } else if (!strcmp(name, "getTabTitle")) {
287         termit_config_get_function(&(p_cfg->get_tab_title_callback), ls, index);
288     } else if (!strcmp(name, "setStatusbar")) {
289         termit_config_get_function(&(p_cfg->get_statusbar_callback), ls, index);
290     } else if (!strcmp(name, "backspaceBinding")) {
291         termit_config_get_erase_binding(&(p_cfg->default_bksp), ls, index);
292     } else if (!strcmp(name, "deleteBinding")) {
293         termit_config_get_erase_binding(&(p_cfg->default_delete), ls, index);
294     } else if (!strcmp(name, "cursorBlinkMode")) {
295         termit_config_get_cursor_blink_mode(&(p_cfg->default_blink), ls, index);
296     } else if (!strcmp(name, "cursorShape")) {
297         termit_config_get_cursor_shape(&(p_cfg->default_shape), ls, index);
298     } else if (!strcmp(name, "colormap")) {
299         termit_lua_load_colormap(ls, index, &configs.style.colors, &configs.style.colors_size);
300     } else if (!strcmp(name, "matches")) {
301         if (termit_lua_load_table(ls, matchesLoader, index, configs.matches)
302                 != TERMIT_LUA_TABLE_LOADER_OK) {
303             ERROR("failed to load matches");
304         }
305     } else if (!strcmp(name, "geometry")) {
306         gchar* geometry_str = NULL;
307         termit_config_get_string(&geometry_str, ls, index);
308         if (geometry_str) {
309             unsigned int cols = 0, rows = 0;
310             int tmp1 = 0, tmp2 = 0;
311             XParseGeometry(geometry_str, &tmp1, &tmp2, &cols, &rows);
312             if ((cols != 0) && (rows != 0)) {
313                 p_cfg->cols = cols;
314                 p_cfg->rows = rows;
315             }
316         }
317         g_free(geometry_str);
318     } else if (!strcmp(name, "tabs")) {
319         if (lua_istable(ls, index)) {
320             if (!configs.default_tabs) {
321                 configs.default_tabs = g_array_new(FALSE, TRUE, sizeof(struct TabInfo));
322             }
323             TRACE("tabs at index: %d tabs.size=%d", index, configs.default_tabs->len);
324             if (termit_lua_load_table(ls, tabsLoader, index, configs.default_tabs)
325                     != TERMIT_LUA_TABLE_LOADER_OK) {
326                 ERROR("openTab failed");
327             }
328         } else {
329             ERROR("expecting table");
330         }
331     }
332 }
333 
termit_lua_add_package_path(const gchar * path)334 static void termit_lua_add_package_path(const gchar* path)
335 {
336     gchar* luaCmd = g_strdup_printf("package.path = package.path .. \";%s/?.lua\"", path);
337     int s = luaL_dostring(L, luaCmd);
338     termit_lua_report_error(__FILE__, __LINE__, s);
339     g_free(luaCmd);
340 }
341 
termit_system_path()342 static gchar** termit_system_path()
343 {
344     const gchar *configSystem = g_getenv("XDG_CONFIG_DIRS");
345     gchar* xdgConfigDirs = NULL;
346     if (configSystem) {
347         xdgConfigDirs = g_strdup_printf("%s:/usr/local/etc/xdg", configSystem);
348     } else {
349         xdgConfigDirs = g_strdup("/usr/local/etc/xdg");
350     }
351     gchar** systemPaths = g_strsplit(xdgConfigDirs, ":", 0);
352     g_free(xdgConfigDirs);
353     return systemPaths;
354 }
355 
termit_user_path()356 static gchar* termit_user_path()
357 {
358     const gchar *configHome = g_getenv("XDG_CONFIG_HOME");
359     if (configHome) {
360         return g_strdup(configHome);
361     } else {
362         return g_strdup_printf("%s/.config", g_getenv("HOME"));
363     }
364 }
365 
load_init(const gchar * initFile)366 static void load_init(const gchar* initFile)
367 {
368     const gchar *configFile = "rc.lua";
369     gchar** systemPaths = termit_system_path();
370     guint i = 0;
371     gchar* systemPath = systemPaths[i];
372     while (systemPath) {
373         if (g_file_test(systemPath, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) == TRUE) {
374             termit_lua_add_package_path(systemPath);
375         }
376         systemPath = systemPaths[++i];
377     }
378     gchar* userPath = termit_user_path();
379     termit_lua_add_package_path(userPath);
380 
381     gchar* fullPath = NULL;
382     if (initFile) {
383         fullPath = g_strdup(initFile);
384     } else {
385         fullPath = g_strdup_printf("%s/termit/%s", userPath, configFile);
386         if (g_file_test(fullPath, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR) == FALSE) {
387             TRACE("%s not found", fullPath);
388             g_free(fullPath);
389 
390             i = 0;
391             gchar* systemPath = systemPaths[i];
392             while (systemPath) {
393                 fullPath = g_strdup_printf("%s/termit/%s", systemPath, configFile);
394                 if (g_file_test(fullPath, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR) == FALSE) {
395                     TRACE("%s not found", fullPath);
396                     g_free(fullPath);
397                     fullPath = NULL;
398                 } else {
399                     break;
400                 }
401                 systemPath = systemPaths[++i];
402             }
403         }
404     }
405     g_strfreev(systemPaths);
406     g_free(userPath);
407     if (fullPath) {
408         TRACE("using config: %s", fullPath);
409         int s = luaL_loadfile(L, fullPath);
410         termit_lua_report_error(__FILE__, __LINE__, s);
411         g_free(fullPath);
412 
413         s = lua_pcall(L, 0, LUA_MULTRET, 0);
414         termit_lua_report_error(__FILE__, __LINE__, s);
415     } else {
416         ERROR("config file %s not found", configFile);
417     }
418 }
419 
termit_lua_fill_tab(int tab_index,lua_State * ls)420 int termit_lua_fill_tab(int tab_index, lua_State* ls)
421 {
422     TERMIT_GET_TAB_BY_INDEX(pTab, tab_index, return 0);
423     lua_newtable(ls);
424     TERMIT_TAB_ADD_STRING("title", pTab->title);
425     TERMIT_TAB_ADD_STRING("command", pTab->argv[0]);
426     TERMIT_TAB_ADD_STRING("argv", "");
427     // FIXME: add argv
428     TERMIT_TAB_ADD_STRING("encoding", pTab->encoding);
429     gchar* working_dir = termit_get_pid_dir(pTab->pid);
430     TERMIT_TAB_ADD_STRING("workingDir", working_dir);
431     g_free(working_dir);
432     TERMIT_TAB_ADD_NUMBER("pid", pTab->pid);
433     TERMIT_TAB_ADD_STRING("font", pTab->style.font_name);
434     TERMIT_TAB_ADD_NUMBER("fontSize", pango_font_description_get_size(pTab->style.font)/PANGO_SCALE);
435     TERMIT_TAB_ADD_STRING("backspaceBinding", termit_erase_binding_to_string(pTab->bksp_binding));
436     TERMIT_TAB_ADD_STRING("deleteBinding", termit_erase_binding_to_string(pTab->delete_binding));
437     TERMIT_TAB_ADD_STRING("cursorBlinkMode", termit_cursor_blink_mode_to_string(pTab->cursor_blink_mode));
438     TERMIT_TAB_ADD_STRING("cursorShape", termit_cursor_shape_to_string(pTab->cursor_shape));
439     return 1;
440 }
441 
termit_lua_tabs_index(lua_State * ls)442 static int termit_lua_tabs_index(lua_State* ls)
443 {
444     if (lua_isnumber(ls, 1)) {
445         TRACE_MSG("index is not number: skipping");
446         return 0;
447     }
448     int tab_index =  lua_tointeger(ls, -1);
449     TRACE("tab_index:%d", tab_index);
450     return termit_lua_fill_tab(tab_index - 1, ls);
451 }
452 
termit_lua_tabs_newindex(lua_State * ls)453 static int termit_lua_tabs_newindex(lua_State* ls)
454 {
455     ERROR("'tabs' is read-only variable");
456     return 0;
457 }
458 
termit_lua_init_tabs()459 static void termit_lua_init_tabs()
460 {
461     lua_newtable(L);
462     luaL_newmetatable(L, "tabs_meta");
463     lua_pushcfunction(L, termit_lua_tabs_index);
464     lua_setfield(L, -2, "__index");
465     lua_pushcfunction(L, termit_lua_tabs_newindex);
466     lua_setfield(L, -2, "__newindex");
467     lua_setmetatable(L, -2);
468     lua_setglobal(L, "tabs");
469 }
470 
471 static const gchar* termit_init_file = NULL;
472 
termit_lua_load_config()473 void termit_lua_load_config()
474 {
475     load_init(termit_init_file);
476     termit_config_trace();
477 
478     trace_menus(configs.user_menus);
479     trace_menus(configs.user_popup_menus);
480 }
481 
termit_lua_init(const gchar * initFile)482 void termit_lua_init(const gchar* initFile)
483 {
484     L = luaL_newstate();
485     luaL_openlibs(L);
486 
487     if (!termit_init_file) {
488         termit_init_file = g_strdup(initFile);
489     }
490     termit_lua_init_tabs();
491     termit_lua_init_api();
492     termit_keys_set_defaults();
493     termit_lua_load_config();
494 }
495