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