dcerpc-nt: add UNION_ALIGN_TO... helpers
[wireshark-sm.git] / epan / wslua / wslua_gui.c
blobfcf290a43161566fb6854fb47cbd5cd100fd3103
1 /*
2 * wslua_gui.c
4 * (c) 2006, Luis E. Garcia Ontanon <luis@ontanon.org>
6 * Wireshark - Network traffic analyzer
7 * By Gerald Combs <gerald@wireshark.org>
8 * Copyright 1998 Gerald Combs
10 * SPDX-License-Identifier: GPL-2.0-or-later
13 #include "config.h"
14 #define WS_LOG_DOMAIN LOG_DOMAIN_WSLUA
16 #include <epan/wmem_scopes.h>
18 #include "wslua.h"
20 /* WSLUA_MODULE Gui GUI Support */
22 static const funnel_ops_t* ops;
24 struct _lua_menu_data {
25 lua_State* L;
26 int cb_ref;
29 static int menu_cb_error_handler(lua_State* L) {
30 const char* error = lua_tostring(L,1);
31 report_failure("Lua: Error during execution of Menu callback:\n %s",error);
32 return 0;
35 WSLUA_FUNCTION wslua_gui_enabled(lua_State* L) { /* Checks if we're running inside a GUI (i.e. Wireshark) or not. */
36 lua_pushboolean(L,GPOINTER_TO_INT(ops && ops->add_button));
37 WSLUA_RETURN(1); /* Boolean `true` if a GUI is available, `false` if it isn't. */
40 static void lua_menu_callback(void *data) {
41 struct _lua_menu_data* md = (struct _lua_menu_data *)data;
42 lua_State* L = md->L;
44 lua_settop(L,0);
45 lua_pushcfunction(L,menu_cb_error_handler);
46 lua_rawgeti(L, LUA_REGISTRYINDEX, md->cb_ref);
48 switch ( lua_pcall(L,0,0,1) ) {
49 case 0:
50 break;
51 case LUA_ERRRUN:
52 ws_warning("Runtime error while calling menu callback");
53 break;
54 case LUA_ERRMEM:
55 ws_warning("Memory alloc error while calling menu callback");
56 break;
57 case LUA_ERRERR:
58 ws_warning("Error while running the error handler function for menu callback");
59 break;
60 default:
61 ws_assert_not_reached();
62 break;
65 return;
68 WSLUA_FUNCTION wslua_register_menu(lua_State* L) { /* Register a menu item in one of the main menus. Requires a GUI. */
69 #define WSLUA_ARG_register_menu_NAME 1 /* The name of the menu item. Use slashes to separate submenus. (e.g. menu:Lua Scripts[My Fancy Statistics]). (string) */
70 #define WSLUA_ARG_register_menu_ACTION 2 /* The function to be called when the menu item is invoked. The function must take no arguments and return nothing. */
71 #define WSLUA_OPTARG_register_menu_GROUP 3 /*
72 Where to place the item in the menu hierarchy.
73 If omitted, defaults to MENU_STAT_GENERIC.
74 Valid packet (Wireshark) items are:
75 * MENU_PACKET_ANALYZE_UNSORTED: menu:Analyze[]
76 * MENU_PACKET_STAT_UNSORTED: menu:Statistics[]
77 * MENU_STAT_GENERIC: menu:Statistics[], first section
78 * MENU_STAT_RESPONSE_TIME: menu:Statistics[Service Response Time]
79 * MENU_STAT_RSERPOOL: menu:Statistics[Reliable Server Pooling (RSerPool)]
80 * MENU_TELEPHONY_UNSORTED: menu:Telephony[]
81 * MENU_TELEPHONY_ANSI: menu:Telephony[ANSI]
82 * MENU_TELEPHONY_GSM: menu:Telephony[GSM]
83 * MENU_TELEPHONY_3GPP_UU: menu:Telephony[3GPP Uu]
84 * MENU_TELEPHONY_MTP3: menu:Telephony[MTP3]
85 * MENU_TELEPHONY_SCTP: menu:Telephony[SCTP]
86 * MENU_TOOLS_UNSORTED: menu:Tools[]
88 Valid log (Stratoshark) items are:
89 * MENU_LOG_ANALYZE_UNSORTED: menu:Analyze[]
90 * MENU_LOG_STAT_UNSORTED: menu:Statistics[]
92 The following are deprecated and shouldn't be used in new code:
93 * MENU_ANALYZE_CONVERSATION_FILTER, menu:Analyze[Conversation Filter] registration is not yet supported in Lua
94 * MENU_STAT_CONVERSATION_LIST, menu:Statistics[Conversations] registration is not yet supported in Lua
95 * MENU_STAT_ENDPOINT_LIST, menu:Statistics[Endpoints] registration is not yet supported in Lua
96 * MENU_ANALYZE_UNSORTED, superseded by MENU_PACKET_ANALYZE_UNSORTED
97 * MENU_ANALYZE_CONVERSATION, superseded by MENU_ANALYZE_CONVERSATION_FILTER
98 * MENU_STAT_CONVERSATION, superseded by MENU_STAT_CONVERSATION_LIST
99 * MENU_STAT_ENDPOINT, superseded by MENU_STAT_ENDPOINT_LIST
100 * MENU_STAT_RESPONSE, superseded by MENU_STAT_RESPONSE_TIME
101 * MENU_STAT_UNSORTED, superseded by MENU_PACKET_STAT_UNSORTED
102 * MENU_STAT_TELEPHONY, superseded by MENU_TELEPHONY_UNSORTED
103 * MENU_STAT_TELEPHONY_ANSI, superseded by MENU_TELEPHONY_ANSI
104 * MENU_STAT_TELEPHONY_GSM, superseded by MENU_TELEPHONY_GSM
105 * MENU_STAT_TELEPHONY_3GPP_UU, superseded by MENU_TELEPHONY_3GPP_UU
106 * MENU_STAT_TELEPHONY_MTP3, superseded by MENU_TELEPHONY_MTP3
107 * MENU_STAT_TELEPHONY_SCTP, superseded by MENU_TELEPHONY_SCTP
110 const char* name = luaL_checkstring(L,WSLUA_ARG_register_menu_NAME);
111 struct _lua_menu_data* md;
112 bool retap = false;
113 register_stat_group_t group = (register_stat_group_t)wslua_optuint(L,WSLUA_OPTARG_register_menu_GROUP,REGISTER_STAT_GROUP_GENERIC);
115 if ( group > REGISTER_TOOLS_GROUP_UNSORTED) {
116 WSLUA_OPTARG_ERROR(register_menu,GROUP,"Must be a defined MENU_*");
117 return 0;
120 if (!lua_isfunction(L,WSLUA_ARG_register_menu_ACTION)) {
121 WSLUA_ARG_ERROR(register_menu,ACTION,"Must be a function");
122 return 0;
125 md = g_new(struct _lua_menu_data, 1);
126 md->L = L;
128 lua_pushvalue(L, 2);
129 md->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
130 lua_remove(L,2);
132 funnel_register_menu(name,
133 group,
134 lua_menu_callback,
136 g_free,
137 retap);
139 WSLUA_RETURN(0);
142 void wslua_deregister_menus(void) {
143 funnel_deregister_menus(lua_menu_callback);
147 * Error handler used by lua_custom_packet_menu_callback when calling the user-supplied callback
149 * @param L State of the Lua interpreter
150 * @return Always returns 0
152 static int packet_menu_cb_error_handler(lua_State* L) {
153 const char* error = lua_tostring(L,1);
154 report_failure("Lua: Error During execution of Packet Menu Callback:\n %s",error);
155 return 0;
159 * Wrapper used to call the user-supplied Lua callback when a custom packet
160 * context menu is clicked.
162 * @param data Lua menu data
163 * @param finfo_array packet data
165 static void lua_custom_packet_menu_callback(void *data, GPtrArray *finfo_array) {
166 // _lua_menu_data is State + the integer index of a callback.
167 struct _lua_menu_data* md = (struct _lua_menu_data *)data;
168 lua_State* L = md->L;
170 lua_settop(L,0);
171 lua_pushcfunction(L,packet_menu_cb_error_handler);
172 lua_rawgeti(L, LUA_REGISTRYINDEX, md->cb_ref);
174 // Push the packet data as arguments to the Lua callback:
175 int items_found = 0;
176 for (unsigned i = 0; i < finfo_array->len; i ++) {
177 field_info *fi = (field_info *)g_ptr_array_index (finfo_array, i);
178 push_FieldInfo(L, fi);
179 items_found++;
182 switch ( lua_pcall(L,items_found,0,1) ) {
183 case 0:
184 break;
185 case LUA_ERRRUN:
186 g_warning("Runtime error while calling custom_packet_menu callback");
187 break;
188 case LUA_ERRMEM:
189 g_warning("Memory alloc error while calling custom_packet_menu callback");
190 break;
191 default:
192 g_assert_not_reached();
193 break;
196 return;
200 * Lua function exposed to users: register_packet_menu
202 WSLUA_FUNCTION wslua_register_packet_menu(lua_State* L) { /* Register a menu item in the packet list. */
203 #define WSLUA_ARG_register_packet_menu_NAME 1 /* The name of the menu item. Use slashes to separate submenus. (e.g. level1/level2/name). (string) */
204 #define WSLUA_ARG_register_packet_menu_ACTION 2 /* The function to be called when the menu item is invoked. The function must take a variable number of arguments and return nothing. The arguments will be FieldInfo objects, one for each field present in the selected packet. */
205 #define WSLUA_OPTARG_register_packet_menu_REQUIRED_FIELDS 3 /* A comma-separated list of packet fields (e.g., http.host,dns.qry.name) which all must be present for the menu to be displayed. If omitted, the packet menu will be displayed for all packets. */
207 const char* name = luaL_checkstring(L,WSLUA_ARG_register_packet_menu_NAME);
208 const char* required_fields = luaL_optstring(L,WSLUA_OPTARG_register_packet_menu_REQUIRED_FIELDS,"");
210 struct _lua_menu_data* md;
211 bool retap = false;
213 if (!lua_isfunction(L,WSLUA_ARG_register_packet_menu_ACTION)) {
214 WSLUA_ARG_ERROR(register_packet_menu,ACTION,"Must be a function");
215 return 0;
218 md = g_new0(struct _lua_menu_data, 1);
219 md->L = L;
221 lua_pushvalue(L, 2);
222 md->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
223 lua_remove(L,2);
225 funnel_register_packet_menu(name,
226 required_fields,
227 lua_custom_packet_menu_callback,
229 retap);
230 WSLUA_RETURN(0);
233 struct _dlg_cb_data {
234 lua_State* L;
235 int func_ref;
238 static int dlg_cb_error_handler(lua_State* L) {
239 const char* error = lua_tostring(L,1);
240 report_failure("Lua: Error during execution of Dialog callback:\n %s",error);
241 return 0;
244 static void lua_dialog_cb(char** user_input, void* data) {
245 struct _dlg_cb_data* dcbd = (struct _dlg_cb_data *)data;
246 int i = 0;
247 char* input;
248 lua_State* L = dcbd->L;
250 lua_settop(L,0);
251 lua_pushcfunction(L,dlg_cb_error_handler);
252 lua_rawgeti(L, LUA_REGISTRYINDEX, dcbd->func_ref);
254 for (i = 0; (input = user_input[i]) ; i++) {
255 lua_pushstring(L,input);
256 g_free(input);
259 g_free(user_input);
261 switch ( lua_pcall(L,i,0,1) ) {
262 case 0:
263 break;
264 case LUA_ERRRUN:
265 ws_warning("Runtime error while calling dialog callback");
266 break;
267 case LUA_ERRMEM:
268 ws_warning("Memory alloc error while calling dialog callback");
269 break;
270 case LUA_ERRERR:
271 ws_warning("Error while running the error handler function for dialog callback");
272 break;
273 default:
274 ws_assert_not_reached();
275 break;
280 struct _close_cb_data {
281 lua_State* L;
282 int func_ref;
283 TextWindow wslua_tw;
287 static int text_win_close_cb_error_handler(lua_State* L) {
288 const char* error = lua_tostring(L,1);
289 report_failure("Lua: Error during execution of TextWindow close callback:\n %s",error);
290 return 0;
293 static void text_win_close_cb(void* data) {
294 struct _close_cb_data* cbd = (struct _close_cb_data *)data;
295 lua_State* L = cbd->L;
297 if (cbd->L) { /* close function is set */
299 lua_settop(L,0);
300 lua_pushcfunction(L,text_win_close_cb_error_handler);
301 lua_rawgeti(L, LUA_REGISTRYINDEX, cbd->func_ref);
303 switch ( lua_pcall(L,0,0,1) ) {
304 case 0:
305 break;
306 case LUA_ERRRUN:
307 ws_warning("Runtime error during execution of TextWindow close callback");
308 break;
309 case LUA_ERRMEM:
310 ws_warning("Memory alloc error during execution of TextWindow close callback");
311 break;
312 case LUA_ERRERR:
313 ws_warning("Error while running the error handler function for TextWindow close callback");
314 break;
315 default:
316 break;
320 if (cbd->wslua_tw->expired) {
321 g_free(cbd->wslua_tw);
322 g_free(cbd);
323 } else {
324 cbd->wslua_tw->expired = true;
329 WSLUA_FUNCTION wslua_new_dialog(lua_State* L) { /*
330 Displays a dialog, prompting for input. The dialog includes an btn:[OK] button and btn:[Cancel] button. Requires a GUI.
332 .An input dialog in action
333 image::images/wslua-new-dialog.png[{small-screenshot-attrs}]
335 ===== Example
337 [source,lua]
338 ----
339 if not gui_enabled() then return end
341 -- Prompt for IP and port and then print them to stdout
342 local label_ip = "IP address"
343 local label_port = "Port"
344 local function print_ip(ip, port)
345 print(label_ip, ip)
346 print(label_port, port)
348 new_dialog("Enter IP address", print_ip, label_ip, label_port)
350 -- Prompt for 4 numbers and then print their product to stdout
351 new_dialog(
352 "Enter 4 numbers",
353 function (a, b, c, d) print(a * b * c * d) end,
354 "a", "b", "c", "d"
356 ----
358 #define WSLUA_ARG_new_dialog_TITLE 1 /* The title of the dialog. */
359 #define WSLUA_ARG_new_dialog_ACTION 2 /* Action to be performed when the user presses btn:[OK]. */
360 /* WSLUA_MOREARGS new_dialog Strings to be used as labels of the dialog's fields. Each string creates a new labeled field. The first field is required.
361 Instead of a string it is possible to provide tables with fields 'name' and 'value' of type string. Then the created dialog's field will be labeled with the content of name and prefilled with the content of value.*/
363 const char* title;
364 int top = lua_gettop(L);
365 int i;
366 GPtrArray* field_names;
367 GPtrArray* field_values;
368 struct _dlg_cb_data* dcbd;
370 if (! ops) {
371 luaL_error(L,"the GUI facility has to be enabled");
372 return 0;
375 if (!ops->new_dialog) {
376 WSLUA_ERROR(new_dialog,"GUI not available");
377 return 0;
380 title = luaL_checkstring(L,WSLUA_ARG_new_dialog_TITLE);
382 if (! lua_isfunction(L,WSLUA_ARG_new_dialog_ACTION)) {
383 WSLUA_ARG_ERROR(new_dialog,ACTION,"Must be a function");
384 return 0;
387 if (top < 3) {
388 WSLUA_ERROR(new_dialog,"At least one field required");
389 return 0;
393 dcbd = g_new(struct _dlg_cb_data, 1);
394 dcbd->L = L;
396 lua_remove(L,1);
398 lua_pushvalue(L, 1);
399 dcbd->func_ref = luaL_ref(L, LUA_REGISTRYINDEX);
400 lua_remove(L,1);
402 field_names = g_ptr_array_new_with_free_func(g_free);
403 field_values = g_ptr_array_new_with_free_func(g_free);
405 top -= 2;
407 for (i = 1; i <= top; i++)
409 if (lua_isstring(L, i))
411 char* field_name = g_strdup(luaL_checkstring(L, i));
412 char* field_value = g_strdup("");
413 g_ptr_array_add(field_names, (void *)field_name);
414 g_ptr_array_add(field_values, (void *)field_value);
416 else if (lua_istable(L, i))
418 lua_getfield(L, i, "name");
419 lua_getfield(L, i, "value");
421 if (!lua_isstring(L, -2))
423 lua_pop(L, 2);
425 g_ptr_array_free(field_names, true);
426 g_ptr_array_free(field_values, true);
427 g_free(dcbd);
428 WSLUA_ERROR(new_dialog, "All fields must be strings or a table with a string field 'name'.");
429 return 0;
432 char* field_name = g_strdup(luaL_checkstring(L, -2));
433 char* field_value = lua_isstring(L, -1) ?
434 g_strdup(luaL_checkstring(L, -1)) :
435 g_strdup("");
437 g_ptr_array_add(field_names, (void *)field_name);
438 g_ptr_array_add(field_values, (void *)field_value);
440 lua_pop(L, 2);
442 else
444 g_ptr_array_free(field_names, true);
445 g_ptr_array_free(field_values, true);
446 g_free(dcbd);
447 WSLUA_ERROR(new_dialog, "All fields must be strings or a table with a string field 'name'.");
448 return 0;
452 g_ptr_array_add(field_names, NULL);
453 g_ptr_array_add(field_values, NULL);
455 ops->new_dialog(ops->ops_id, title, (const char**)(field_names->pdata), (const char**)(field_values->pdata), lua_dialog_cb, dcbd, g_free);
457 g_ptr_array_free(field_names, true);
458 g_ptr_array_free(field_values, true);
460 WSLUA_RETURN(0);
463 WSLUA_CLASS_DEFINE(ProgDlg,FAIL_ON_NULL("ProgDlg"));
465 Creates and manages a modal progress bar.
466 This is intended to be used with
467 http://lua-users.org/wiki/CoroutinesTutorial[coroutines],
468 where a main UI thread controls the progress bar dialog while a background coroutine (worker thread) yields to the main thread between steps.
469 The main thread checks the status of the btn:[Cancel] button and if it's not set, returns control to the coroutine.
471 .A progress bar in action
472 image::images/wslua-progdlg.png[{medium-screenshot-attrs}]
474 The legacy (GTK+) user interface displayed this as a separate dialog, hence the “Dlg” suffix.
475 The Qt user interface shows a progress bar inside the main status bar.
478 WSLUA_CONSTRUCTOR ProgDlg_new(lua_State* L) { /*
479 Creates and displays a new `ProgDlg` progress bar with a btn:[Cancel] button and optional title.
480 It is highly recommended that you wrap code that uses a `ProgDlg` instance because it does not automatically close itself upon encountering an error.
481 Requires a GUI.
483 ===== Example
485 [source,lua]
486 ----
487 if not gui_enabled() then return end
489 local p = ProgDlg.new("Constructing", "tacos")
491 -- We have to wrap the ProgDlg code in a pcall in case some unexpected
492 -- error occurs.
493 local ok, errmsg = pcall(function()
494 local co = coroutine.create(
495 function()
496 local limit = 100000
497 for i=1,limit do
498 print("co", i)
499 coroutine.yield(i/limit, "step "..i.." of "..limit)
504 -- Whenever coroutine yields, check the status of the cancel button to determine
505 -- when to break. Wait up to 20 sec for coroutine to finish.
506 local start_time = os.time()
507 while coroutine.status(co) ~= 'dead' do
508 local elapsed = os.time() - start_time
510 -- Quit if cancel button pressed or 20 seconds elapsed
511 if p:stopped() or elapsed > 20 then
512 break
515 local res, val, val2 = coroutine.resume(co)
516 if not res or res == false then
517 if val then
518 debug(val)
520 print('coroutine error')
521 break
524 -- show progress in progress dialog
525 p:update(val, val2)
527 end)
529 p:close()
531 if not ok and errmsg then
532 report_failure(errmsg)
534 ----
536 #define WSLUA_OPTARG_ProgDlg_new_TITLE 1 /* Title of the progress bar. Defaults to "Progress". */
537 #define WSLUA_OPTARG_ProgDlg_new_TASK 2 /* Optional task name, which will be appended to the title. Defaults to the empty string (""). */
538 ProgDlg pd = (ProgDlg)g_malloc(sizeof(struct _wslua_progdlg));
539 pd->title = g_strdup(luaL_optstring(L,WSLUA_OPTARG_ProgDlg_new_TITLE,"Progress"));
540 pd->task = g_strdup(luaL_optstring(L,WSLUA_OPTARG_ProgDlg_new_TASK,""));
541 pd->stopped = false;
543 if (ops->new_progress_window) {
544 pd->pw = ops->new_progress_window(ops->ops_id, pd->title, pd->task, true, &(pd->stopped));
545 } else {
546 g_free (pd);
547 WSLUA_ERROR(ProgDlg_new, "GUI not available");
548 return 0;
551 pushProgDlg(L,pd);
553 WSLUA_RETURN(1); /* The newly created `ProgDlg` object. */
556 WSLUA_METHOD ProgDlg_update(lua_State* L) { /* Sets the progress dialog's progress bar position based on percentage done. */
557 #define WSLUA_ARG_ProgDlg_update_PROGRESS 2 /* Progress value, e.g. 0.75. Value must be between 0.0 and 1.0 inclusive. */
558 #define WSLUA_OPTARG_ProgDlg_update_TASK 3 /* Task name. Currently ignored. Defaults to empty string (""). */
559 ProgDlg pd = checkProgDlg(L,1);
560 double pr = lua_tonumber(L,WSLUA_ARG_ProgDlg_update_PROGRESS);
561 const char* task = luaL_optstring(L,WSLUA_OPTARG_ProgDlg_update_TASK,"");
563 if (!ops->update_progress) {
564 WSLUA_ERROR(ProgDlg_update,"GUI not available");
565 return 0;
568 g_free(pd->task);
569 pd->task = g_strdup(task);
571 /* XXX, dead code: pd already dereferenced. should it be: !pd->task?
572 if (!pd) {
573 WSLUA_ERROR(ProgDlg_update,"Cannot be called for something not a ProgDlg");
574 } */
576 if (pr >= 0.0 && pr <= 1.0) {
577 ops->update_progress(pd->pw, (float) pr, task);
578 } else {
579 WSLUA_ERROR(ProgDlg_update,"Progress value out of range (must be between 0.0 and 1.0)");
580 return 0;
583 return 0;
586 WSLUA_METHOD ProgDlg_stopped(lua_State* L) { /* Checks whether the user has pressed the btn:[Cancel] button. */
587 ProgDlg pd = checkProgDlg(L,1);
589 lua_pushboolean(L,pd->stopped);
591 WSLUA_RETURN(1); /* Boolean `true` if the user has asked to stop the operation, `false` otherwise. */
596 WSLUA_METHOD ProgDlg_close(lua_State* L) { /* Hides the progress bar. */
597 ProgDlg pd = checkProgDlg(L,1);
599 if (!ops->destroy_progress_window) {
600 WSLUA_ERROR(ProgDlg_close,"GUI not available");
601 return 0;
604 if (pd->pw) {
605 ops->destroy_progress_window(pd->pw);
606 pd->pw = NULL;
608 return 0;
612 static int ProgDlg__tostring(lua_State* L) {
613 ProgDlg pd = checkProgDlg(L,1);
615 lua_pushfstring(L, "%sstopped",pd->stopped?"":"not ");
617 WSLUA_RETURN(1); /* A string specifying whether the Progress Dialog has stopped or not. */
620 /* Gets registered as metamethod automatically by WSLUA_REGISTER_CLASS/META */
621 static int ProgDlg__gc(lua_State* L) {
622 ProgDlg pd = toProgDlg(L,1);
624 if (pd) {
625 if (pd->pw && ops->destroy_progress_window) {
626 ops->destroy_progress_window(pd->pw);
629 g_free(pd);
630 } else {
631 luaL_error(L, "ProgDlg__gc has being passed something else!");
634 return 0;
638 WSLUA_METHODS ProgDlg_methods[] = {
639 WSLUA_CLASS_FNREG(ProgDlg,new),
640 WSLUA_CLASS_FNREG(ProgDlg,update),
641 WSLUA_CLASS_FNREG(ProgDlg,stopped),
642 WSLUA_CLASS_FNREG(ProgDlg,close),
643 { NULL, NULL }
646 WSLUA_META ProgDlg_meta[] = {
647 WSLUA_CLASS_MTREG(ProgDlg,tostring),
648 { NULL, NULL }
651 int ProgDlg_register(lua_State* L) {
653 ops = funnel_get_funnel_ops();
655 WSLUA_REGISTER_CLASS(ProgDlg);
657 return 0;
662 WSLUA_CLASS_DEFINE(TextWindow,FAIL_ON_NULL_OR_EXPIRED("TextWindow")); /*
664 Creates and manages a text window.
665 The text can be read-only or editable, and buttons can be added below the text.
667 .A text window in action
668 image::images/wslua-textwindow.png[{medium-screenshot-attrs}]
671 /* XXX: button and close callback data is being leaked */
672 /* XXX: lua callback function and TextWindow are not garbage collected because
673 they stay in LUA_REGISTRYINDEX forever */
675 WSLUA_CONSTRUCTOR TextWindow_new(lua_State* L) { /*
676 Creates a new `TextWindow` text window and displays it.
677 Requires a GUI.
679 ===== Example
681 [source,lua]
682 ----
683 if not gui_enabled() then return end
685 -- create new text window and initialize its text
686 local win = TextWindow.new("Log")
687 win:set("Hello world!")
689 -- add buttons to clear text window and to enable editing
690 win:add_button("Clear", function() win:clear() end)
691 win:add_button("Enable edit", function() win:set_editable(true) end)
693 -- add button to change text to uppercase
694 win:add_button("Uppercase", function()
695 local text = win:get_text()
696 if text ~= "" then
697 win:set(string.upper(text))
699 end)
701 -- print "closing" to stdout when the user closes the text window
702 win:set_atclose(function() print("closing") end)
703 ----
706 #define WSLUA_OPTARG_TextWindow_new_TITLE 1 /* Title of the new window. Optional. Defaults to "Untitled Window". */
708 const char* title;
709 TextWindow tw = NULL;
710 struct _close_cb_data* default_cbd;
712 if (!ops->new_text_window || !ops->set_close_cb) {
713 WSLUA_ERROR(TextWindow_new,"GUI not available");
714 return 0;
717 title = luaL_optstring(L,WSLUA_OPTARG_TextWindow_new_TITLE, "Untitled Window");
718 tw = g_new(struct _wslua_tw, 1);
719 tw->expired = false;
720 tw->ws_tw = ops->new_text_window(ops->ops_id, title);
722 default_cbd = g_new(struct _close_cb_data, 1);
724 default_cbd->L = NULL;
725 default_cbd->func_ref = 0;
726 default_cbd->wslua_tw = tw;
728 tw->close_cb_data = (void *)default_cbd;
730 ops->set_close_cb(tw->ws_tw,text_win_close_cb,default_cbd);
732 pushTextWindow(L,tw);
734 WSLUA_RETURN(1); /* The newly created `TextWindow` object. */
737 WSLUA_METHOD TextWindow_set_atclose(lua_State* L) { /* Set the function that will be called when the text window closes. */
738 #define WSLUA_ARG_TextWindow_set_atclose_ACTION 2 /* A Lua function to be executed when the user closes the text window. */
740 TextWindow tw = checkTextWindow(L,1);
741 struct _close_cb_data* cbd;
743 if (!ops->set_close_cb) {
744 WSLUA_ERROR(TextWindow_set_atclose,"GUI not available");
745 return 0;
748 lua_settop(L,2);
750 if (! lua_isfunction(L,2)) {
751 WSLUA_ARG_ERROR(TextWindow_set_atclose,ACTION,"Must be a function");
752 return 0;
755 cbd = g_new(struct _close_cb_data, 1);
757 cbd->L = L;
758 cbd->func_ref = luaL_ref(L, LUA_REGISTRYINDEX);
759 cbd->wslua_tw = tw;
761 g_free(tw->close_cb_data);
762 tw->close_cb_data = (void *)cbd;
764 ops->set_close_cb(tw->ws_tw,text_win_close_cb,cbd);
766 /* XXX: this is a bad way to do this - should copy the object on to the stack first */
767 WSLUA_RETURN(1); /* The `TextWindow` object. */
770 WSLUA_METHOD TextWindow_set(lua_State* L) { /* Sets the text to be displayed. */
771 #define WSLUA_ARG_TextWindow_set_TEXT 2 /* The text to be displayed. */
773 TextWindow tw = checkTextWindow(L,1);
774 const char* text = luaL_checkstring(L,WSLUA_ARG_TextWindow_set_TEXT);
776 if (!ops->set_text) {
777 WSLUA_ERROR(TextWindow_set,"GUI not available");
778 return 0;
781 ops->set_text(tw->ws_tw,text);
783 /* XXX: this is a bad way to do this - should copy the object on to the stack first */
784 WSLUA_RETURN(1); /* The `TextWindow` object. */
787 WSLUA_METHOD TextWindow_append(lua_State* L) { /* Appends text to the current window contents. */
788 #define WSLUA_ARG_TextWindow_append_TEXT 2 /* The text to be appended. */
789 TextWindow tw = checkTextWindow(L,1);
790 const char* text = luaL_checkstring(L,WSLUA_ARG_TextWindow_append_TEXT);
792 if (!ops->append_text) {
793 WSLUA_ERROR(TextWindow_append,"GUI not available");
794 return 0;
797 ops->append_text(tw->ws_tw,text);
799 /* XXX: this is a bad way to do this - should copy the object on to the stack first */
800 WSLUA_RETURN(1); /* The `TextWindow` object. */
803 WSLUA_METHOD TextWindow_prepend(lua_State* L) { /* Prepends text to the current window contents. */
804 #define WSLUA_ARG_TextWindow_prepend_TEXT 2 /* The text to be prepended. */
805 TextWindow tw = checkTextWindow(L,1);
806 const char* text = luaL_checkstring(L,WSLUA_ARG_TextWindow_prepend_TEXT);
808 if (!ops->prepend_text) {
809 WSLUA_ERROR(TextWindow_prepend,"GUI not available");
810 return 0;
813 ops->prepend_text(tw->ws_tw,text);
815 /* XXX: this is a bad way to do this - should copy the object on to the stack first */
816 WSLUA_RETURN(1); /* The `TextWindow` object. */
819 WSLUA_METHOD TextWindow_clear(lua_State* L) { /* Erases all of the text in the window. */
820 TextWindow tw = checkTextWindow(L,1);
822 if (!ops->clear_text) {
823 WSLUA_ERROR(TextWindow_clear,"GUI not available");
824 return 0;
827 ops->clear_text(tw->ws_tw);
829 /* XXX: this is a bad way to do this - should copy the object on to the stack first */
830 WSLUA_RETURN(1); /* The `TextWindow` object. */
833 WSLUA_METHOD TextWindow_get_text(lua_State* L) { /* Get the text of the window. */
834 TextWindow tw = checkTextWindow(L,1);
835 const char* text;
837 if (!ops->get_text) {
838 WSLUA_ERROR(TextWindow_get_text,"GUI not available");
839 return 0;
842 text = ops->get_text(tw->ws_tw);
844 lua_pushstring(L,text);
845 WSLUA_RETURN(1); /* The `TextWindow`++'++s text. */
848 WSLUA_METHOD TextWindow_close(lua_State* L) { /* Close the window. */
849 TextWindow tw = checkTextWindow(L,1);
851 if (!ops->destroy_text_window) {
852 WSLUA_ERROR(TextWindow_get_text,"GUI not available");
853 return 0;
856 ops->destroy_text_window(tw->ws_tw);
857 tw->ws_tw = NULL;
859 return 0;
862 /* Gets registered as metamethod automatically by WSLUA_REGISTER_CLASS/META */
863 static int TextWindow__gc(lua_State* L) {
864 TextWindow tw = toTextWindow(L,1);
866 if (!tw)
867 return 0;
869 if (!tw->expired) {
870 tw->expired = true;
871 if (ops->destroy_text_window) {
872 ops->destroy_text_window(tw->ws_tw);
874 } else {
875 g_free(tw->close_cb_data);
876 g_free(tw);
879 return 0;
882 WSLUA_METHOD TextWindow_set_editable(lua_State* L) { /* Make this text window editable. */
883 #define WSLUA_OPTARG_TextWindow_set_editable_EDITABLE 2 /* `true` to make the text editable, `false` otherwise. Defaults to `true`. */
885 TextWindow tw = checkTextWindow(L,1);
886 bool editable = wslua_optbool(L,WSLUA_OPTARG_TextWindow_set_editable_EDITABLE,true);
888 if (!ops->set_editable) {
889 WSLUA_ERROR(TextWindow_set_editable,"GUI not available");
890 return 0;
893 ops->set_editable(tw->ws_tw,editable);
895 WSLUA_RETURN(1); /* The `TextWindow` object. */
898 typedef struct _wslua_bt_cb_t {
899 lua_State* L;
900 int func_ref;
901 int wslua_tw_ref;
902 } wslua_bt_cb_t;
904 static bool wslua_button_callback(funnel_text_window_t* ws_tw, void* data) {
905 wslua_bt_cb_t* cbd = (wslua_bt_cb_t *)data;
906 lua_State* L = cbd->L;
907 (void) ws_tw; /* ws_tw is unused since we need wslua_tw_ref and it is stored in cbd */
909 lua_settop(L,0);
910 lua_pushcfunction(L,dlg_cb_error_handler);
911 lua_rawgeti(L, LUA_REGISTRYINDEX, cbd->func_ref);
912 lua_rawgeti(L, LUA_REGISTRYINDEX, cbd->wslua_tw_ref);
914 switch ( lua_pcall(L,1,0,1) ) {
915 case 0:
916 break;
917 case LUA_ERRRUN:
918 ws_warning("Runtime error while calling button callback");
919 break;
920 case LUA_ERRMEM:
921 ws_warning("Memory alloc error while calling button callback");
922 break;
923 case LUA_ERRERR:
924 ws_warning("Error while running the error handler function for button callback");
925 break;
926 default:
927 ws_assert_not_reached();
928 break;
931 return true;
934 WSLUA_METHOD TextWindow_add_button(lua_State* L) {
935 /* Adds a button with an action handler to the text window. */
936 #define WSLUA_ARG_TextWindow_add_button_LABEL 2 /* The button label. */
937 #define WSLUA_ARG_TextWindow_add_button_FUNCTION 3 /* The Lua function to be called when the button is pressed. */
938 TextWindow tw = checkTextWindow(L,1);
939 const char* label = luaL_checkstring(L,WSLUA_ARG_TextWindow_add_button_LABEL);
941 funnel_bt_t* fbt;
942 wslua_bt_cb_t* cbd;
944 if (!ops->add_button) {
945 WSLUA_ERROR(TextWindow_add_button,"GUI not available");
946 return 0;
949 if (! lua_isfunction(L,WSLUA_ARG_TextWindow_add_button_FUNCTION) ) {
950 WSLUA_ARG_ERROR(TextWindow_add_button,FUNCTION,"must be a function");
951 return 0;
954 lua_settop(L,3);
956 if (ops->add_button) {
957 fbt = g_new(funnel_bt_t, 1);
958 cbd = g_new(wslua_bt_cb_t, 1);
960 fbt->tw = tw->ws_tw;
961 fbt->func = wslua_button_callback;
962 fbt->data = cbd;
963 fbt->free_fcn = g_free;
964 fbt->free_data_fcn = g_free;
966 cbd->L = L;
967 cbd->func_ref = luaL_ref(L, LUA_REGISTRYINDEX);
968 cbd->wslua_tw_ref = luaL_ref(L, LUA_REGISTRYINDEX);
970 ops->add_button(tw->ws_tw,fbt,label);
973 WSLUA_RETURN(1); /* The `TextWindow` object. */
976 WSLUA_METHODS TextWindow_methods[] = {
977 WSLUA_CLASS_FNREG(TextWindow,new),
978 WSLUA_CLASS_FNREG(TextWindow,set),
979 WSLUA_CLASS_FNREG(TextWindow,append),
980 WSLUA_CLASS_FNREG(TextWindow,prepend),
981 WSLUA_CLASS_FNREG(TextWindow,clear),
982 WSLUA_CLASS_FNREG(TextWindow,set_atclose),
983 WSLUA_CLASS_FNREG(TextWindow,set_editable),
984 WSLUA_CLASS_FNREG(TextWindow,get_text),
985 WSLUA_CLASS_FNREG(TextWindow,add_button),
986 WSLUA_CLASS_FNREG(TextWindow,close),
987 { NULL, NULL }
990 WSLUA_META TextWindow_meta[] = {
991 {"__tostring", TextWindow_get_text},
992 { NULL, NULL }
995 int TextWindow_register(lua_State* L) {
997 ops = funnel_get_funnel_ops();
999 WSLUA_REGISTER_CLASS(TextWindow);
1001 return 0;
1005 WSLUA_FUNCTION wslua_retap_packets(lua_State* L) {
1007 Rescans all packets and runs each <<lua_class_Listener, tap listener>> without reconstructing the display.
1009 if ( ops->retap_packets ) {
1010 ops->retap_packets(ops->ops_id);
1011 } else {
1012 WSLUA_ERROR(wslua_retap_packets, "GUI not available");
1015 return 0;
1019 WSLUA_FUNCTION wslua_copy_to_clipboard(lua_State* L) { /* Copy a string into the clipboard. Requires a GUI. */
1020 #define WSLUA_ARG_copy_to_clipboard_TEXT 1 /* The string to be copied into the clipboard. */
1021 const char* copied_str = luaL_checkstring(L,WSLUA_ARG_copy_to_clipboard_TEXT);
1022 GString* gstr;
1023 if (!ops->copy_to_clipboard) {
1024 WSLUA_ERROR(copy_to_clipboard, "GUI not available");
1025 return 0;
1028 gstr = g_string_new(copied_str);
1030 ops->copy_to_clipboard(gstr);
1032 g_string_free(gstr,TRUE);
1034 return 0;
1037 WSLUA_FUNCTION wslua_open_capture_file(lua_State* L) { /* Open and display a capture file. Requires a GUI. */
1038 #define WSLUA_ARG_open_capture_file_FILENAME 1 /* The name of the file to be opened. */
1039 #define WSLUA_ARG_open_capture_file_FILTER 2 /* The https://gitlab.com/wireshark/wireshark/-/wikis/DisplayFilters[display filter] to be applied once the file is opened. */
1041 const char* fname = luaL_checkstring(L,WSLUA_ARG_open_capture_file_FILENAME);
1042 const char* filter = luaL_optstring(L,WSLUA_ARG_open_capture_file_FILTER,NULL);
1043 char* error = NULL;
1045 if (!ops->open_file) {
1046 WSLUA_ERROR(open_capture_file, "GUI not available");
1047 return 0;
1050 if (! ops->open_file(ops->ops_id, fname, filter, &error) ) {
1051 lua_pushboolean(L,false);
1053 if (error) {
1054 lua_pushstring(L,error);
1055 g_free(error);
1056 } else
1057 lua_pushnil(L);
1059 return 2;
1060 } else {
1061 lua_pushboolean(L,true);
1062 return 1;
1066 WSLUA_FUNCTION wslua_get_filter(lua_State* L) { /* Get the main filter text. */
1067 const char *filter_str = NULL;
1069 if (!ops->get_filter) {
1070 WSLUA_ERROR(get_filter, "GUI not available");
1071 return 0;
1074 filter_str = ops->get_filter(ops->ops_id);
1075 lua_pushstring(L,filter_str);
1077 return 1;
1080 WSLUA_FUNCTION wslua_set_filter(lua_State* L) { /* Set the main filter text. */
1081 #define WSLUA_ARG_set_filter_TEXT 1 /* The filter's text. */
1082 const char* filter_str = luaL_checkstring(L,WSLUA_ARG_set_filter_TEXT);
1084 if (!ops->set_filter) {
1085 WSLUA_ERROR(set_filter, "GUI not available");
1086 return 0;
1089 ops->set_filter(ops->ops_id, filter_str);
1091 return 0;
1094 WSLUA_FUNCTION wslua_get_color_filter_slot(lua_State* L) { /*
1095 Gets the current https://gitlab.com/wireshark/wireshark/-/wikis/ColoringRules[packet coloring rule] (by index) for the
1096 current session. Wireshark reserves 10 slots for these coloring rules. Requires a GUI.
1098 #define WSLUA_ARG_get_color_filter_slot_ROW 1 /*
1099 The index (1-10) of the desired color filter value in the temporary coloring rules list.
1101 .Default background colors
1102 [cols="3",options="header"]
1103 |===
1104 |Index |RGB (hex) |Color
1105 |1 |ffc0c0 |{set:cellbgcolor:#ffc0c0} pink 1
1106 |2 |ffc0ff |{set:cellbgcolor:#ffc0ff} pink 2
1107 |3 |e0c0e0 |{set:cellbgcolor:#e0c0e0} purple 1
1108 |4 |c0c0ff |{set:cellbgcolor:#c0c0ff} purple 2
1109 |5 |c0e0e0 |{set:cellbgcolor:#c0e0e0} green 1
1110 |6 |c0ffff |{set:cellbgcolor:#c0ffff} green 2
1111 |7 |c0ffc0 |{set:cellbgcolor:#c0ffc0} green 3
1112 |8 |ffffc0 |{set:cellbgcolor:#ffffc0} yellow 1
1113 |9 |e0e0c0 |{set:cellbgcolor:#e0e0c0} yellow 2
1114 |10 |e0e0e0 |{set:cellbgcolor:#e0e0e0} gray
1115 |===
1117 uint8_t row = (uint8_t)luaL_checkinteger(L, WSLUA_ARG_get_color_filter_slot_ROW);
1118 char* filter_str = NULL;
1120 if (!ops->get_color_filter_slot) {
1121 WSLUA_ERROR(get_color_filter_slot, "GUI not available");
1122 return 0;
1125 filter_str = ops->get_color_filter_slot(row);
1126 if (filter_str == NULL) {
1127 lua_pushnil(L);
1128 } else {
1129 lua_pushstring(L, filter_str);
1130 g_free(filter_str);
1133 return 1;
1136 WSLUA_FUNCTION wslua_set_color_filter_slot(lua_State* L) { /*
1137 Sets a https://gitlab.com/wireshark/wireshark/-/wikis/ColoringRules[packet coloring rule] (by index) for the current session.
1138 Wireshark reserves 10 slots for these coloring rules.
1139 Requires a GUI.
1141 #define WSLUA_ARG_set_color_filter_slot_ROW 1 /*
1142 The index (1-10) of the desired color in the temporary coloring rules list.
1143 The default foreground is black and the default backgrounds are listed below.
1145 // XXX We need get the colors working, e.g. by adding them to a stylesheet.
1146 .Default background colors
1147 [cols="3",options="header"]
1148 |===
1149 |Index |RGB (hex) |Color
1150 |1 |ffc0c0 |{set:cellbgcolor:#ffc0c0} pink 1
1151 |2 |ffc0ff |{set:cellbgcolor:#ffc0ff} pink 2
1152 |3 |e0c0e0 |{set:cellbgcolor:#e0c0e0} purple 1
1153 |4 |c0c0ff |{set:cellbgcolor:#c0c0ff} purple 2
1154 |5 |c0e0e0 |{set:cellbgcolor:#c0e0e0} green 1
1155 |6 |c0ffff |{set:cellbgcolor:#c0ffff} green 2
1156 |7 |c0ffc0 |{set:cellbgcolor:#c0ffc0} green 3
1157 |8 |ffffc0 |{set:cellbgcolor:#ffffc0} yellow 1
1158 |9 |e0e0c0 |{set:cellbgcolor:#e0e0c0} yellow 2
1159 |10 |e0e0e0 |{set:cellbgcolor:#e0e0e0} gray
1160 |===
1162 The color list can be set from the command line using two unofficial preferences: `gui.colorized_frame.bg` and `gui.colorized_frame.fg`, which require 10 hex RGB codes (6 hex digits each), e.g.
1163 ----
1164 wireshark -o gui.colorized_frame.bg:${RGB0},${RGB1},${RGB2},${RGB3},${RGB4},${RGB5},${RGB6},${RGB7},${RGB8},${RGB9}
1165 ----
1167 For example, this command yields the same results as the table above (and with all foregrounds set to black):
1168 ----
1169 wireshark -o gui.colorized_frame.bg:ffc0c0,ffc0ff,e0c0e0,c0c0ff,c0e0e0,c0ffff,c0ffc0,ffffc0,e0e0c0,e0e0e0 -o gui.colorized_frame.fg:000000,000000,000000,000000,000000,000000,000000,000000,000000,000000
1170 ----
1172 #define WSLUA_ARG_set_color_filter_slot_TEXT 2 /* The https://gitlab.com/wireshark/wireshark/-/wikis/DisplayFilters[display filter] for selecting packets to be colorized
1173 . */
1174 uint8_t row = (uint8_t)luaL_checkinteger(L,WSLUA_ARG_set_color_filter_slot_ROW);
1175 const char* filter_str = luaL_checkstring(L,WSLUA_ARG_set_color_filter_slot_TEXT);
1177 if (!ops->set_color_filter_slot) {
1178 WSLUA_ERROR(set_color_filter_slot, "GUI not available");
1179 return 0;
1182 ops->set_color_filter_slot(row, filter_str);
1184 return 0;
1187 WSLUA_FUNCTION wslua_apply_filter(lua_State* L) { /*
1188 Apply the filter in the main filter box.
1189 Requires a GUI.
1191 [WARNING]
1192 ====
1193 Avoid calling this from within a dissector function or else an infinite loop can occur if it causes the dissector to be called again.
1194 This function is best used in a button callback (from a dialog or text window) or menu callback.
1195 ====
1197 if (!ops->apply_filter) {
1198 WSLUA_ERROR(apply_filter, "GUI not available");
1199 return 0;
1202 ops->apply_filter(ops->ops_id);
1204 return 0;
1208 WSLUA_FUNCTION wslua_reload(lua_State* L) { /* Reload the current capture file. Deprecated. Use reload_packets() instead. */
1210 if (!ops->reload_packets) {
1211 WSLUA_ERROR(reload, "GUI not available");
1212 return 0;
1215 ops->reload_packets(ops->ops_id);
1217 return 0;
1221 WSLUA_FUNCTION wslua_reload_packets(lua_State* L) { /*
1222 Reload the current capture file.
1223 Requires a GUI.
1225 [WARNING]
1226 ====
1227 Avoid calling this from within a dissector function or else an infinite loop can occur if it causes the dissector to be called again.
1228 This function is best used in a button callback (from a dialog or text window) or menu callback.
1229 ====
1232 if (!ops->reload_packets) {
1233 WSLUA_ERROR(reload, "GUI not available");
1234 return 0;
1237 ops->reload_packets(ops->ops_id);
1239 return 0;
1243 WSLUA_FUNCTION wslua_redissect_packets(lua_State* L) { /*
1244 Redissect all packets in the current capture file.
1245 Requires a GUI.
1247 [WARNING]
1248 ====
1249 Avoid calling this from within a dissector function or else an infinite loop can occur if it causes the dissector to be called again.
1250 This function is best used in a button callback (from a dialog or text window) or menu callback.
1251 ====
1254 if (!ops->redissect_packets) {
1255 WSLUA_ERROR(reload, "GUI not available");
1256 return 0;
1259 ops->redissect_packets(ops->ops_id);
1261 return 0;
1265 WSLUA_FUNCTION wslua_reload_lua_plugins(lua_State* L) { /* Reload all Lua plugins. */
1267 if (!ops->reload_lua_plugins) {
1268 WSLUA_ERROR(reload_lua_plugins, "GUI not available");
1269 return 0;
1272 ops->reload_lua_plugins(ops->ops_id);
1274 return 0;
1278 WSLUA_FUNCTION wslua_browser_open_url(lua_State* L) { /*
1279 Opens an URL in a web browser. Requires a GUI.
1281 [WARNING]
1282 ====
1283 Do not pass an untrusted URL to this function.
1285 It will be passed to the system's URL handler, which might execute malicious code, switch on your Bluetooth-connected foghorn, or any of a number of unexpected or harmful things.
1286 ====
1288 #define WSLUA_ARG_browser_open_url_URL 1 /* The url. */
1289 const char* url = luaL_checkstring(L,WSLUA_ARG_browser_open_url_URL);
1291 if (!ops->browser_open_url) {
1292 WSLUA_ERROR(browser_open_url, "GUI not available");
1293 return 0;
1296 ops->browser_open_url(url);
1298 return 0;
1301 WSLUA_FUNCTION wslua_browser_open_data_file(lua_State* L) { /*
1302 Open a file located in the data directory (specified in the Wireshark preferences) in the web browser.
1303 If the file does not exist, the function silently ignores the request.
1304 Requires a GUI.
1306 [WARNING]
1307 ====
1308 Do not pass an untrusted URL to this function.
1310 It will be passed to the system's URL handler, which might execute malicious code, switch on your Bluetooth-connected foghorn, or any of a number of unexpected or harmful things.
1311 ====
1313 #define WSLUA_ARG_browser_open_data_file_FILENAME 1 /* The file name. */
1314 const char* file = luaL_checkstring(L,WSLUA_ARG_browser_open_data_file_FILENAME);
1316 if (!ops->browser_open_data_file) {
1317 WSLUA_ERROR(browser_open_data_file, "GUI not available");
1318 return 0;
1321 ops->browser_open_data_file(file);
1323 return 0;
1327 * Editor modelines - https://www.wireshark.org/tools/modelines.html
1329 * Local variables:
1330 * c-basic-offset: 4
1331 * tab-width: 8
1332 * indent-tabs-mode: nil
1333 * End:
1335 * vi: set shiftwidth=4 tabstop=8 expandtab:
1336 * :indentSize=4:tabSize=8:noTabs=true: