diff --git a/capture_opts.c b/capture_opts.c index 69a24579c2..4a7c8f2ac2 100644 --- a/capture_opts.c +++ b/capture_opts.c @@ -68,6 +68,8 @@ capture_opts_init(capture_options *capture_opts) capture_opts->default_options.extcap_args = NULL; capture_opts->default_options.extcap_userdata = NULL; capture_opts->default_options.extcap_pid = INVALID_EXTCAP_PID; + capture_opts->default_options.extcap_control_in = NULL; + capture_opts->default_options.extcap_control_out = NULL; #endif #ifdef CAN_SET_CAPTURE_BUFFER_SIZE capture_opts->default_options.buffer_size = DEFAULT_CAPTURE_BUFFER_SIZE; @@ -702,6 +704,8 @@ capture_opts_add_iface_opt(capture_options *capture_opts, const char *optarg_str interface_opts.extcap_args = NULL; interface_opts.extcap_pid = INVALID_EXTCAP_PID; interface_opts.extcap_userdata = NULL; + interface_opts.extcap_control_in = g_strdup(capture_opts->default_options.extcap_control_in); + interface_opts.extcap_control_out = g_strdup(capture_opts->default_options.extcap_control_out); #endif #ifdef CAN_SET_CAPTURE_BUFFER_SIZE interface_opts.buffer_size = capture_opts->default_options.buffer_size; @@ -1129,6 +1133,8 @@ capture_opts_del_iface(capture_options *capture_opts, guint if_index) if (interface_opts.extcap_pid != INVALID_EXTCAP_PID) g_spawn_close_pid(interface_opts.extcap_pid); g_free(interface_opts.extcap_userdata); + g_free(interface_opts.extcap_control_in); + g_free(interface_opts.extcap_control_out); #endif #ifdef HAVE_PCAP_REMOTE if (interface_opts.src_type == CAPTURE_IFREMOTE) { @@ -1180,6 +1186,8 @@ collect_ifaces(capture_options *capture_opts) if (interface_opts.extcap_args) g_hash_table_ref(interface_opts.extcap_args); interface_opts.extcap_userdata = NULL; + interface_opts.extcap_control_in = NULL; + interface_opts.extcap_control_out = NULL; #endif #ifdef CAN_SET_CAPTURE_BUFFER_SIZE interface_opts.buffer_size = device.buffer; diff --git a/capture_opts.h b/capture_opts.h index 780bbca526..d9ab3d86ab 100644 --- a/capture_opts.h +++ b/capture_opts.h @@ -226,6 +226,8 @@ typedef struct interface_options_tag { GPid extcap_pid; /* pid of running process or INVALID_EXTCAP_PID */ gpointer extcap_userdata; guint extcap_child_watch; + gchar *extcap_control_in; + gchar *extcap_control_out; #endif #ifdef CAN_SET_CAPTURE_BUFFER_SIZE int buffer_size; diff --git a/doc/README.extcap b/doc/README.extcap index 94ca73584b..50db8f67a3 100644 --- a/doc/README.extcap +++ b/doc/README.extcap @@ -196,6 +196,195 @@ such a check is the same as for Qt RegExp classes. This feature is only active i Qt version of Wireshark. +TOOLBAR CONTROLS +================ +An extcap utility can provide configuration for controls to use in an interface toolbar. +This controls are bidirectional and can be used to control the extcap utility while +capturing. + +This is useful in scenarios where configuration can be done based on findings in the +capture process, setting temporary values or give other inputs without restarting the +current capture. + +Example: + +$ extcapbin --extcap-interfaces +extcap {version=1.0}{display=Example extcap interface} +interface {value=example1}{display=Example interface 1 for extcap} +interface {value=example2}{display=Example interface 2 for extcap} +control {number=0}{type=string}{display=Message}{tooltip=Package message content. Must start with a capital letter.}{validation=[A-Z]+}{required=true} +control {number=1}{type=selector}{display=Time delay}{tooltip=Time delay between packages} +control {number=2}{type=boolean}{display=Verify}{default=true}{tooltip=Verify package content} +control {number=3}{type=button}{display=Turn on}{tooltip=Turn on or off} +control {number=4}{type=button}{role=logger}{display=Log}{tooltip=Show capture log} +value {control=1}{value=1}{display=1 sec} +value {control=1}{value=2}{display=2 sec}{default=true} + +All controls will be presented as GUI elements in a toolbar specific to the extcap +utility. The extcap must not rely on using those controls (they are optional) because +of other capturing tools not using GUI (e.g. tshark, tfshark). + + +CONTROLS +======== +The controls are similar to the ARGUMENTS, but without the CALL element. All controls +may be given a default value at startup and most can be changed during capture, both +by the extcap and the user (depending on the type of control). + +All controls must provide a NUMBER, by which they are identified. No NUMBER may be +provided twice. Also all options must present the elements TYPE and DISPLAY, where +TYPE provides the type of control to add to the toolbar and DISPLAY the name in the GUI. + +Additionally TOOLTIP and PLACEHOLDER may be provided, which will give the user an +explanation within the GUI. + +All controls, except from the logger, help and reset buttons, may be disabled +(and enabled) in GUI by the extcap during capture. This can be because of set-once +operations, or operations which takes some time to complete. + +All control values which are changed by the user (not equal to the default value) will +be sent to the extcap utility when starting a capture. The extcap utility may choose +to discard initial values and set new values, depending on implementation. + +This TYPEs are defined as controls: + + * BOOLEAN - This provides a checkbox with the possibility to set a true/false value. + + The extcap utility can set a default value at startup, and can change (set) and receive + value changes while capturing. When starting a capture the GUI will send the value if + different from the default value. + + The payload is one byte with binary value 0 or 1. + + Valid Commands: Set value, Enable, Disable. + + * BUTTON - This provides a button with different ROLEs: + + ** CONTROL - This button will send a signal. + This is the default role if nothing is configured. + + The extcap utility can set the button text at startup, and can change (set) the + button text and receive button press signals while capturing. The button is + disabled and the button text is restored to the default text when not capturing. + + The payload is either the button text or empty (signal). + + Valid Commands: Set value, Enable, Disable. + + ** LOGGER - This provides a logger mechanism where the extcap utility can send log + entries to be presented in a log window. This communication is unidirectional. + + The payload is the log entry, and should be ended with a newline. + Maximum length is 65535 bytes. + + Valid Commands: Set log entry, Add log entry. + + The Set command will clear the log before adding the entry. + + ** HELP - This button opens the help page, if configured. + This type has no controls and will not be used in communication. + + Valid Commands: NONE. + + ** RESET - This button will restore all control values to default. + This type has no controls and will not be used in communication. + + Valid Commands: NONE. + + * SELECTOR - This provides a combo box with fixed values which can be selected. + + The extcap utility can set default values at startup, and add and remove values and + receive change in value selection while capturing. When starting a capture the GUI + will send the value if different from the default value. + + The payload is a string with the value, and optionally a string with a display + value if this is different from the value. This two string values are separated + by a null character. + + Valid Commands: Set selected value, Add value, Remove value, Enable, Disable. + + If value is empty the Remove command will remove all entries. + + * STRING - This provides a text edit line with the possibility to set a string or any + value which can be represented in a string (integer, float, date, etc.). + + The extcap utility can set a default string value at startup, and can change (set) and + receive value changes while capturing. When starting a capture the GUI will send the + value if different from the default value. + + The payload is a string with the value. Maximum length is 32767 bytes. + + Valid Commands: Set value, Enable, Disable. + + The element VALIDATION allows to provide a regular expression string, which is used + to check the user input for validity beyond normal data type or range checks. + Back-slashes must be escaped (as in \\b for \b). + + +MESSAGES +======== +In addition to the controls it's possible to send a single message from the extcap +utility to the user. This message can be put in the status bar or displayed in a +information, warning or error dialog which must be accepted by the user. This message +does not use the NUMBER argument so this can have any value. + + +CONTROL PROTOCOL +================ +The protocol used to communicate over the control pipes has a fixed size header of +6 bytes and a payload with 0 - 65535 bytes. + +Control packet: + + +----+----+----+----+----+----+----+----+ + | Sync Pipe Indication (1 byte) | + +----+----+----+----+----+----+----+----+ + | Message Length | + | (3 bytes network order) | + +----+----+----+----+----+----+----+----+ + | Control Number (1 byte) | + +----+----+----+----+----+----+----+----+ + | Command (1 byte) | + +----+----+----+----+----+----+----+----+ + | Payload | + | (0 - 65535 bytes) | + +----+----+----+----+----+----+----+----+ + + Sync Pipe Indication: + The common sync pipe indication. This protocol uses the value 'T'. + + Message Length: + Payload length + 2 bytes for argument number and command. + + Control Number: + Unique number to identify the control. This number also gives the order of + the controls in the interface toolbar. + + Command: Control type: + 0 = Initialized none + 1 = Set boolean / button / logger / selector / string + 2 = Add logger / selector + 3 = Remove selector + 4 = Enable boolean / button / selector / string + 5 = Disable boolean / button / selector / string + 6 = Statusbar message none + 7 = Information message none + 8 = Warning message none + 9 = Error message none + + Payload Length: + The length of the following payload. Maximum length is 65535 bytes. + +The Initialized command will be sent from the GUI to the extcap utility when all +initial control values are sent after starting a capture. This is an indication +that the GUI is ready to receive control values. + +The GUI will only send Initialized and Set commands. The extcap utility shall not +send the Initialized command. + +Messages with unknown control number or command will be silently ignored. + + DEVELOPMENT =========== To have extcap support, extcap must be enabled. Moreover the specific extcap must diff --git a/doc/extcap_example.py b/doc/extcap_example.py index 6ab5699afb..e7ea7b2d6b 100755 --- a/doc/extcap_example.py +++ b/doc/extcap_example.py @@ -51,13 +51,38 @@ import struct import binascii from threading import Thread -ERROR_USAGE = 0 -ERROR_ARG = 1 -ERROR_INTERFACE = 2 -ERROR_FIFO = 3 -ERROR_DELAY = 4 +ERROR_USAGE = 0 +ERROR_ARG = 1 +ERROR_INTERFACE = 2 +ERROR_FIFO = 3 +ERROR_DELAY = 4 -globalinterface = 0 +CTRL_CMD_INITIALIZED = 0 +CTRL_CMD_SET = 1 +CTRL_CMD_ADD = 2 +CTRL_CMD_REMOVE = 3 +CTRL_CMD_ENABLE = 4 +CTRL_CMD_DISABLE = 5 +CTRL_CMD_STATUSBAR = 6 +CTRL_CMD_INFORMATION = 7 +CTRL_CMD_WARNING = 8 +CTRL_CMD_ERROR = 9 + +CTRL_ARG_MESSAGE = 0 +CTRL_ARG_DELAY = 1 +CTRL_ARG_VERIFY = 2 +CTRL_ARG_BUTTON = 3 +CTRL_ARG_HELP = 4 +CTRL_ARG_RESET = 5 +CTRL_ARG_LOGGER = 6 +CTRL_ARG_NONE = 255 + +initialized = False +message = '' +delay = 0.0 +verify = False +button = False +button_disabled = False """ This code has been taken from http://stackoverflow.com/questions/5943249/python-argparse-and-controlling-overriding-the-exit-status-code - originally developed by Rob Cowie http://stackoverflow.com/users/46690/rob-cowie @@ -128,12 +153,29 @@ def extcap_config(interface): def extcap_interfaces(): - print ("extcap {version=1.0}{help=http://www.wireshark.org}") - print ("interface {value=example1}{display=Example interface usage for extcap}") + print ("extcap {version=1.0}{help=http://www.wireshark.org}{display=Example extcap interface}") + print ("interface {value=example1}{display=Example interface 1 for extcap}") + print ("interface {value=example2}{display=Example interface 2 for extcap}") + print ("control {number=%d}{type=string}{display=Message}{tooltip=Package message content. Must start with a capital letter.}{placeholder=Enter package message content here ...}{validation=^[A-Z]+}" % CTRL_ARG_MESSAGE) + print ("control {number=%d}{type=selector}{display=Time delay}{tooltip=Time delay between packages}" % CTRL_ARG_DELAY) + print ("control {number=%d}{type=boolean}{display=Verify}{default=true}{tooltip=Verify package content}" % CTRL_ARG_VERIFY) + print ("control {number=%d}{type=button}{display=Turn on}{tooltip=Turn on or off}" % CTRL_ARG_BUTTON) + print ("control {number=%d}{type=button}{role=help}{display=Help}{tooltip=Show help}" % CTRL_ARG_HELP) + print ("control {number=%d}{type=button}{role=reset}{display=Reset}{tooltip=Restore default values}" % CTRL_ARG_RESET) + print ("control {number=%d}{type=button}{role=logger}{display=Log}{tooltip=Show capture log}" % CTRL_ARG_LOGGER) + print ("value {control=%d}{value=1}{display=1}" % CTRL_ARG_DELAY) + print ("value {control=%d}{value=2}{display=2}" % CTRL_ARG_DELAY) + print ("value {control=%d}{value=3}{display=3}" % CTRL_ARG_DELAY) + print ("value {control=%d}{value=4}{display=4}" % CTRL_ARG_DELAY) + print ("value {control=%d}{value=5}{display=5}{default=true}" % CTRL_ARG_DELAY) + print ("value {control=%d}{value=60}{display=60}" % CTRL_ARG_DELAY) + def extcap_dlts(interface): if ( interface == '1' ): print ("dlt {number=147}{name=USER0}{display=Demo Implementation for Extcap}") + elif ( interface == '2' ): + print ("dlt {number=148}{name=USER1}{display=Demo Implementation for Extcap}") """ @@ -216,20 +258,125 @@ def pcap_fake_package ( message, fake_ip ): pcap += message return pcap -def extcap_capture(interface, fifo, delay, verify, message, remote, fake_ip): - tdelay = delay if delay != 0 else 5 +def control_read(fn): + try: + header = fn.read(6) + sp, _, length, arg, typ = struct.unpack('>sBHBB', header) + if length > 2: + payload = fn.read(length - 2) + else: + payload = '' + return arg, typ, payload + except: + return None, None, None + +def control_read_thread(control_in, fn_out): + global initialized, message, delay, verify, button, button_disabled + with open(control_in, 'rb', 0 ) as fn: + arg = 0 + while arg != None: + arg, typ, payload = control_read(fn) + log = '' + if typ == CTRL_CMD_INITIALIZED: + initialized = True + elif arg == CTRL_ARG_MESSAGE: + message = payload + log = "Message = " + payload + elif arg == CTRL_ARG_DELAY: + delay = float(payload) + log = "Time delay = " + payload + elif arg == CTRL_ARG_VERIFY: + # Only read this after initialized + if initialized: + verify = (payload[0] != '\0') + log = "Verify = " + str(verify) + control_write(fn_out, CTRL_ARG_NONE, CTRL_CMD_STATUSBAR, "Verify changed") + elif arg == CTRL_ARG_BUTTON: + control_write(fn_out, CTRL_ARG_BUTTON, CTRL_CMD_DISABLE, "") + button_disabled = True + if button == True: + control_write(fn_out, CTRL_ARG_BUTTON, CTRL_CMD_SET, "Turn on") + button = False + log = "Button turned off" + else: + control_write(fn_out, CTRL_ARG_BUTTON, CTRL_CMD_SET, "Turn off") + button = True + log = "Button turned on" + + if len(log) > 0: + control_write(fn_out, CTRL_ARG_LOGGER, CTRL_CMD_ADD, log + "\n") + +def control_write(fn, arg, typ, payload): + packet = bytearray() + packet += struct.pack('>sBHBB', 'T', 0, len(payload) + 2, arg, typ) + packet += payload + fn.write(packet) + +def control_write_defaults(fn_out): + global initialized, message, delay, verify + + while not initialized: + time.sleep(.1) # Wait for initial control values + + # Write startup configuration to Toolbar controls + control_write(fn_out, CTRL_ARG_MESSAGE, CTRL_CMD_SET, message) + control_write(fn_out, CTRL_ARG_DELAY, CTRL_CMD_SET, str(delay)) + control_write(fn_out, CTRL_ARG_VERIFY, CTRL_CMD_SET, struct.pack('B', verify)) + + for i in range(1,16): + item = bytearray() + item += str(i) + struct.pack('B', 0) + str(i) + " sec" + control_write(fn_out, CTRL_ARG_DELAY, CTRL_CMD_ADD, item) + + control_write(fn_out, CTRL_ARG_DELAY, CTRL_CMD_REMOVE, str(60)) + +def extcap_capture(interface, fifo, control_in, control_out, in_delay, in_verify, in_message, remote, fake_ip): + global message, delay, verify, button_disabled + delay = in_delay if in_delay != 0 else 5 + message = in_message + verify = in_verify + counter = 1 if not os.path.exists(fifo): print ( "Fifo does not exist, exiting!", file=sys.stderr ) sys.exit(1) + fn_out = None + if control_out != None: + fn_out = open(control_out, 'wb', 0) + control_write(fn_out, CTRL_ARG_LOGGER, CTRL_CMD_SET, "Log started at " + time.strftime("%c") + "\n") + + + if control_in != None: + # Start reading thread + thread = Thread(target = control_read_thread, args = (control_in, fn_out)) + thread.start() + + + if fn_out != None: + control_write_defaults(fn_out) + with open(fifo, 'wb', 0 ) as fh: fh.write (pcap_fake_header()) while True: + if fn_out != None: + log = "Received packet #" + str(counter) + "\n" + control_write(fn_out, CTRL_ARG_LOGGER, CTRL_CMD_ADD, log) + counter = counter + 1 + + if button_disabled == True: + control_write(fn_out, CTRL_ARG_BUTTON, CTRL_CMD_ENABLE, "") + control_write(fn_out, CTRL_ARG_NONE, CTRL_CMD_INFORMATION, "Turn action finished.") + button_disabled = False + out = ("%s|%04X%s|%s" % ( remote.strip(), len(message), message, verify )).encode("utf8") fh.write (pcap_fake_package(out, fake_ip)) - time.sleep(tdelay) + time.sleep(delay) + + thread.join() + if fn_out != None: + fn_out.close() def extcap_close_fifo(fifo): if not os.path.exists(fifo): @@ -268,6 +415,8 @@ if __name__ == '__main__': parser.add_argument("--extcap-config", help="Provide a list of configurations for the given interface", action="store_true") parser.add_argument("--extcap-capture-filter", help="Used together with capture to provide a capture filter") parser.add_argument("--fifo", help="Use together with capture to provide the fifo to dump data to") + parser.add_argument("--extcap-control-in", help="Use together with capture to provide the fifo to dump data to") + parser.add_argument("--extcap-control-out", help="Use together with capture to provide the fifo to dump data to") # Interface Arguments parser.add_argument("--verify", help="Demonstrates a verification bool flag", action="store_true" ) @@ -334,7 +483,7 @@ if __name__ == '__main__': sys.exit(ERROR_DELAY) try: - extcap_capture(interface, args.fifo, args.delay, args.verify, message, args.remote, fake_ip) + extcap_capture(interface, args.fifo, args.extcap_control_in, args.extcap_control_out, args.delay, args.verify, message, args.remote, fake_ip) except KeyboardInterrupt: pass else: diff --git a/docbook/release-notes.asciidoc b/docbook/release-notes.asciidoc index a76cb578d3..d95bc6c41b 100644 --- a/docbook/release-notes.asciidoc +++ b/docbook/release-notes.asciidoc @@ -48,6 +48,8 @@ since version 2.2.0: * You can move back and forth in the selection history in the Qt UI. * IEEE 802.15.4 dissector now uses an UAT for decryption keys. The original decryption key preference has been obsoleted. +* Extcap utilities can now provide configuration for a GUI interface toolbar to + control the extcap utility while capturing. //=== Removed Dissectors diff --git a/extcap.c b/extcap.c index 87ec4f5d00..c9e357bf19 100644 --- a/extcap.c +++ b/extcap.c @@ -49,6 +49,8 @@ #include +#include "ui/iface_toolbar.h" + #include #include #include @@ -77,6 +79,11 @@ static GHashTable * _loaded_interfaces = NULL; */ static GHashTable * _tool_for_ifname = NULL; +/* internal container, for all the extcap executables that have been found + * and that provides a toolbar with controls to be added to a Interface Toolbar + */ +static GHashTable *_toolbars = NULL; + /* internal container, to map preference names to pointers that hold preference * values. These ensure that preferences can survive extcap if garbage * collection, and does not lead to dangling pointers in the prefs subsystem. @@ -198,6 +205,56 @@ extcap_find_interface_for_ifname(const gchar *ifname) return result; } +static void +extcap_free_toolbar_value(iface_toolbar_value *value) +{ + if (!value) + { + return; + } + + g_free(value->value); + g_free(value->display); + g_free(value); +} + +static void +extcap_free_toolbar_control(iface_toolbar_control *control) +{ + if (!control) + { + return; + } + + g_free(control->display); + g_free(control->validation); + g_free(control->tooltip); + if (control->ctrl_type == INTERFACE_TYPE_STRING) { + g_free(control->default_value.string); + } + g_list_foreach(control->values, (GFunc)extcap_free_toolbar_value, NULL); + g_list_free(control->values); + g_free(control); +} + +static void +extcap_free_toolbar(gpointer data) +{ + if (!data) + { + return; + } + + iface_toolbar *toolbar = (iface_toolbar *)data; + + g_free(toolbar->menu_title); + g_free(toolbar->help); + g_list_free_full(toolbar->ifnames, g_free); + g_list_foreach(toolbar->controls, (GFunc)extcap_free_toolbar_control, NULL); + g_list_free(toolbar->controls); + g_free(toolbar); +} + static gboolean extcap_if_exists_for_extcap(const gchar *ifname, const gchar *extcap) { @@ -218,6 +275,26 @@ extcap_if_executable(const gchar *ifname) return interface != NULL ? interface->extcap_path : NULL; } +static void +extcap_iface_toolbar_add(const gchar *extcap, iface_toolbar *toolbar_entry) +{ + char *toolname; + + if (!extcap || !toolbar_entry) + { + return; + } + + toolname = g_path_get_basename(extcap); + + if (!g_hash_table_lookup(_toolbars, toolname)) + { + g_hash_table_insert(_toolbars, g_strdup(toolname), toolbar_entry); + } + + g_free(toolname); +} + /* Note: args does not need to be NULL-terminated. */ static gboolean extcap_foreach(gint argc, gchar **args, extcap_cb_t cb, extcap_callback_info_t cb_info) @@ -815,6 +892,27 @@ extcap_has_configuration(const char *ifname, gboolean is_required) return found; } +gboolean +extcap_has_toolbar(const char *ifname) +{ + if (!iface_toolbar_use()) + { + return FALSE; + } + + GList *toolbar_list = g_hash_table_get_values (_toolbars); + for (GList *walker = toolbar_list; walker; walker = walker->next) + { + iface_toolbar *toolbar = (iface_toolbar *) walker->data; + if (g_list_find_custom(toolbar->ifnames, ifname, (GCompareFunc) strcmp)) + { + return TRUE; + } + } + + return FALSE; +} + /* taken from capchild/capture_sync.c */ static gboolean pipe_data_available(int pipe_fd) { @@ -896,6 +994,16 @@ void extcap_if_cleanup(capture_options *capture_opts, gchar **errormsg) ws_unlink(interface_opts.extcap_fifo); interface_opts.extcap_fifo = NULL; } + if (interface_opts.extcap_control_in && file_exists(interface_opts.extcap_control_in)) + { + ws_unlink(interface_opts.extcap_control_in); + interface_opts.extcap_control_in = NULL; + } + if (interface_opts.extcap_control_out && file_exists(interface_opts.extcap_control_out)) + { + ws_unlink(interface_opts.extcap_control_out); + interface_opts.extcap_control_out = NULL; + } #endif /* Maybe the client closed and removed fifo, but ws should check if * pid should be closed */ @@ -1088,6 +1196,16 @@ GPtrArray *extcap_prepare_arguments(interface_options interface_opts) } add_arg(EXTCAP_ARGUMENT_RUN_PIPE); add_arg(interface_opts.extcap_fifo); + if (interface_opts.extcap_control_in) + { + add_arg(EXTCAP_ARGUMENT_CONTROL_OUT); + add_arg(interface_opts.extcap_control_in); + } + if (interface_opts.extcap_control_out) + { + add_arg(EXTCAP_ARGUMENT_CONTROL_IN); + add_arg(interface_opts.extcap_control_out); + } if (interface_opts.extcap_args == NULL || g_hash_table_size(interface_opts.extcap_args) == 0) { /* User did not perform interface configuration. @@ -1189,6 +1307,13 @@ extcap_init_interfaces(capture_options *capture_opts) continue; } + /* create control pipes if having toolbar */ + if (extcap_has_toolbar(interface_opts.name)) + { + extcap_create_pipe(&interface_opts.extcap_control_in); + extcap_create_pipe(&interface_opts.extcap_control_out); + } + /* create pipe for fifo */ if (!extcap_create_pipe(&interface_opts.extcap_fifo)) { @@ -1393,15 +1518,22 @@ static void remove_extcap_entry(gpointer entry, gpointer data _U_) static gboolean cb_load_interfaces(extcap_callback_info_t cb_info) { - GList * interfaces = NULL, * walker = NULL; + GList * interfaces = NULL, * control_items = NULL, * walker = NULL; extcap_interface * int_iter = NULL; extcap_info * element = NULL; + iface_toolbar * toolbar_entry = NULL; gchar * toolname = g_path_get_basename(cb_info.extcap); GList * interface_keys = g_hash_table_get_keys(_loaded_interfaces); /* Load interfaces from utility */ - interfaces = extcap_parse_interfaces(cb_info.output); + interfaces = extcap_parse_interfaces(cb_info.output, &control_items); + + if (control_items) + { + toolbar_entry = g_new0(iface_toolbar, 1); + toolbar_entry->controls = control_items; + } g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Loading interface list for %s ", cb_info.extcap); @@ -1447,6 +1579,11 @@ static gboolean cb_load_interfaces(extcap_callback_info_t cb_info) } help = int_iter->help; + if (toolbar_entry) + { + toolbar_entry->menu_title = g_strdup(int_iter->display); + toolbar_entry->help = g_strdup(int_iter->help); + } walker = g_list_next(walker); continue; @@ -1476,11 +1613,26 @@ static gboolean cb_load_interfaces(extcap_callback_info_t cb_info) element->interfaces = g_list_append(element->interfaces, int_iter); g_hash_table_insert(_tool_for_ifname, g_strdup(int_iter->call), g_strdup(toolname)); + + if (toolbar_entry) + { + if (!toolbar_entry->menu_title) + { + toolbar_entry->menu_title = g_strdup(int_iter->display); + } + toolbar_entry->ifnames = g_list_append(toolbar_entry->ifnames, g_strdup(int_iter->call)); + } } walker = g_list_next(walker); } + if (toolbar_entry && toolbar_entry->menu_title) + { + iface_toolbar_add(toolbar_entry); + extcap_iface_toolbar_add(cb_info.extcap, toolbar_entry); + } + g_list_foreach(interfaces, remove_extcap_entry, NULL); g_list_free(interfaces); g_list_free(interface_keys); @@ -1499,6 +1651,21 @@ extcap_load_interface_list(void) gchar *argv; gchar *error; + if (_toolbars) + { + // Remove existing interface toolbars here instead of in extcap_clear_interfaces() + // to avoid flicker in shown toolbars when refreshing interfaces. + GList *toolbar_list = g_hash_table_get_values (_toolbars); + for (GList *walker = toolbar_list; walker; walker = walker->next) + { + iface_toolbar *toolbar = (iface_toolbar *) walker->data; + iface_toolbar_remove(toolbar->menu_title); + } + g_hash_table_remove_all(_toolbars); + } else { + _toolbars = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, extcap_free_toolbar); + } + if (_loaded_interfaces == NULL) { _loaded_interfaces = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, extcap_free_interface_info); diff --git a/extcap.h b/extcap.h index 7c7a82baaf..297745ee63 100644 --- a/extcap.h +++ b/extcap.h @@ -43,13 +43,15 @@ #define EXTCAP_PIPE_PREFIX "wireshark_extcap" #define EXTCAP_ARGUMENT_CONFIG "--extcap-config" -#define EXTCAP_ARGUMENT_LIST_INTERFACES "--extcap-interfaces" +#define EXTCAP_ARGUMENT_LIST_INTERFACES "--extcap-interfaces" #define EXTCAP_ARGUMENT_INTERFACE "--extcap-interface" #define EXTCAP_ARGUMENT_LIST_DLTS "--extcap-dlts" #define EXTCAP_ARGUMENT_RUN_CAPTURE "--capture" #define EXTCAP_ARGUMENT_CAPTURE_FILTER "--extcap-capture-filter" #define EXTCAP_ARGUMENT_RUN_PIPE "--fifo" +#define EXTCAP_ARGUMENT_CONTROL_IN "--extcap-control-in" +#define EXTCAP_ARGUMENT_CONTROL_OUT "--extcap-control-out" typedef struct _extcap_info { gchar * basename; @@ -117,6 +119,9 @@ extcap_free_if_configuration(GList *list, gboolean free_args); gboolean extcap_has_configuration(const char * ifname, gboolean is_required); +gboolean +extcap_has_toolbar(const char *ifname); + #ifdef WIN32 HANDLE extcap_get_win32_handle(); diff --git a/extcap_parser.c b/extcap_parser.c index 78c48dd94f..7f7b6ceb0c 100644 --- a/extcap_parser.c +++ b/extcap_parser.c @@ -28,6 +28,9 @@ #include #include +#include "ui/iface_toolbar.h" +#include "wsutil/strtoi.h" + #include "extcap.h" #include "extcap_parser.h" @@ -120,7 +123,7 @@ static extcap_token_sentence *extcap_tokenize_sentence(const gchar *s) { rs->sentence = NULL; /* Regex for catching just the allowed values for sentences */ - if ((regex = g_regex_new("^[\\t| ]*(arg|value|interface|extcap|dlt)(?=[\\t| ]+\\{)", + if ((regex = g_regex_new("^[\\t| ]*(arg|value|interface|extcap|dlt|control)(?=[\\t| ]+\\{)", (GRegexCompileFlags) G_REGEX_CASELESS, (GRegexMatchFlags) 0, NULL)) != NULL) { g_regex_match(regex, s, (GRegexMatchFlags) 0, &match_info); @@ -193,6 +196,10 @@ static extcap_token_sentence *extcap_tokenize_sentence(const gchar *s) { param_type = EXTCAP_PARAM_VERSION; } else if (g_ascii_strcasecmp(arg, "help") == 0) { param_type = EXTCAP_PARAM_HELP; + } else if (g_ascii_strcasecmp(arg, "control") == 0) { + param_type = EXTCAP_PARAM_CONTROL; + } else if (g_ascii_strcasecmp(arg, "role") == 0) { + param_type = EXTCAP_PARAM_ROLE; } else { param_type = EXTCAP_PARAM_UNKNOWN; } @@ -269,6 +276,24 @@ void extcap_free_arg(extcap_arg *a) { g_free(a); } +void extcap_free_toolbar_value(iface_toolbar_value *v) { + if (v == NULL) + return; + + g_free(v->value); + g_free(v->display); +} + +void extcap_free_toolbar_control(iface_toolbar_control *c) { + if (c == NULL) + return; + + g_free(c->display); + g_free(c->validation); + g_free(c->tooltip); + g_free(c->placeholder); +} + void extcap_free_arg_list(GList *a) { g_list_foreach(a, (GFunc)extcap_free_arg, NULL); g_list_free(a); @@ -280,6 +305,12 @@ static gint glist_find_numbered_arg(gconstpointer listelem, gconstpointer needle return 1; } +static gint glist_find_numbered_control(gconstpointer listelem, gconstpointer needle) { + if (((const iface_toolbar_control *) listelem)->num == *((const int *) needle)) + return 0; + return 1; +} + static void extcap_free_tokenized_sentence(gpointer s, gpointer user_data _U_) { extcap_token_sentence *t = (extcap_token_sentence *)s; @@ -604,7 +635,179 @@ static extcap_interface *extcap_parse_interface_sentence(extcap_token_sentence * return ri; } -GList *extcap_parse_interfaces(gchar *output) { +static iface_toolbar_control *extcap_parse_control_sentence(GList *control_items, extcap_token_sentence *s) +{ + extcap_sentence_type sent = EXTCAP_SENTENCE_UNKNOWN; + gchar *param_value = NULL; + iface_toolbar_control *control = NULL; + iface_toolbar_value *value = NULL; + GList *entry = NULL; + guint32 num = 0; + + if (s == NULL) + return NULL; + + if (g_ascii_strcasecmp(s->sentence, "control") == 0) { + sent = EXTCAP_SENTENCE_CONTROL; + } else if (g_ascii_strcasecmp(s->sentence, "value") == 0) { + sent = EXTCAP_SENTENCE_VALUE; + } + + if (sent == EXTCAP_SENTENCE_UNKNOWN) + return NULL; + + if (sent == EXTCAP_SENTENCE_CONTROL) { + control = g_new0(iface_toolbar_control, 1); + control->ctrl_type = INTERFACE_TYPE_UNKNOWN; + + param_value = (gchar *)g_hash_table_lookup(s->param_list, ENUM_KEY(EXTCAP_PARAM_ARGNUM)); + if (param_value == NULL) { + extcap_free_toolbar_control(control); + return NULL; + } + + if (!ws_strtou32(param_value, NULL, &num)) { + extcap_free_toolbar_control(control); + return NULL; + } + control->num = (int)num; + + param_value = (gchar *)g_hash_table_lookup(s->param_list, ENUM_KEY(EXTCAP_PARAM_DISPLAY)); + if (param_value == NULL) { + extcap_free_toolbar_control(control); + return NULL; + } + control->display = g_strdup(param_value); + + if ((param_value = (gchar *)g_hash_table_lookup(s->param_list, ENUM_KEY(EXTCAP_PARAM_VALIDATION))) + != NULL) { + control->validation = g_strdup(param_value); + } + + if ((param_value = (gchar *)g_hash_table_lookup(s->param_list, ENUM_KEY(EXTCAP_PARAM_REQUIRED))) + != NULL) { + control->is_required = g_regex_match_simple(EXTCAP_BOOLEAN_REGEX, param_value, G_REGEX_CASELESS, (GRegexMatchFlags)0); + } + + param_value = (gchar *)g_hash_table_lookup(s->param_list, ENUM_KEY(EXTCAP_PARAM_TOOLTIP)); + if (param_value != NULL) { + control->tooltip = g_strdup(param_value); + } + + param_value = (gchar *)g_hash_table_lookup(s->param_list, ENUM_KEY(EXTCAP_PARAM_PLACEHOLDER)); + if (param_value != NULL) { + control->placeholder = g_strdup(param_value); + } + + param_value = (gchar *)g_hash_table_lookup(s->param_list, ENUM_KEY(EXTCAP_PARAM_TYPE)); + if (param_value == NULL) { + extcap_free_toolbar_control(control); + return NULL; + } + + extcap_arg_type arg_type = EXTCAP_ARG_UNKNOWN; + if (g_ascii_strcasecmp(param_value, "boolean") == 0) { + control->ctrl_type = INTERFACE_TYPE_BOOLEAN; + arg_type = EXTCAP_ARG_BOOLEAN; + } else if (g_ascii_strcasecmp(param_value, "button") == 0) { + control->ctrl_type = INTERFACE_TYPE_BUTTON; + } else if (g_ascii_strcasecmp(param_value, "selector") == 0) { + control->ctrl_type = INTERFACE_TYPE_SELECTOR; + } else if (g_ascii_strcasecmp(param_value, "string") == 0) { + control->ctrl_type = INTERFACE_TYPE_STRING; + arg_type = EXTCAP_ARG_STRING; + } else { + printf("invalid type %s in CONTROL sentence\n", param_value); + extcap_free_toolbar_control(control); + return NULL; + } + + param_value = (gchar *)g_hash_table_lookup(s->param_list, ENUM_KEY(EXTCAP_PARAM_ROLE)); + if (param_value != NULL) { + if (g_ascii_strcasecmp(param_value, "control") == 0) { + control->ctrl_role = INTERFACE_ROLE_CONTROL; + } else if (g_ascii_strcasecmp(param_value, "help") == 0) { + control->ctrl_role = INTERFACE_ROLE_HELP; + } else if (g_ascii_strcasecmp(param_value, "logger") == 0) { + control->ctrl_role = INTERFACE_ROLE_LOGGER; + } else if (g_ascii_strcasecmp(param_value, "reset") == 0) { + control->ctrl_role = INTERFACE_ROLE_RESET; + } else { + printf("invalid role %s in CONTROL sentence\n", param_value); + control->ctrl_role = INTERFACE_ROLE_UNKNOWN; + } + } else { + /* Default role */ + control->ctrl_role = INTERFACE_ROLE_CONTROL; + } + + param_value = (gchar *)g_hash_table_lookup(s->param_list, ENUM_KEY(EXTCAP_PARAM_DEFAULT)); + if (param_value != NULL) { + if (arg_type != EXTCAP_ARG_UNKNOWN) { + extcap_complex *complex = extcap_parse_complex(arg_type, param_value); + if (complex != NULL) { + if (arg_type == EXTCAP_ARG_BOOLEAN) { + control->default_value.boolean = extcap_complex_get_bool(complex); + } else if (arg_type == EXTCAP_ARG_STRING) { + control->default_value.string = g_strdup(complex->_val); + } + extcap_free_complex(complex); + } else { + printf("invalid default, couldn't parse %s\n", param_value); + } + } + } + + } else if (sent == EXTCAP_SENTENCE_VALUE) { + param_value = (gchar *)g_hash_table_lookup(s->param_list, ENUM_KEY(EXTCAP_PARAM_CONTROL)); + if (param_value == NULL) { + printf("no control in VALUE sentence\n"); + return NULL; + } + + if (!ws_strtou32(param_value, NULL, &num)) { + extcap_free_toolbar_control(control); + return NULL; + } + + entry = g_list_find_custom(control_items, &num, glist_find_numbered_control); + if (entry == NULL) { + printf("couldn't find control %u in list for VALUE sentence\n", num); + return NULL; + } + + value = g_new0(iface_toolbar_value, 1); + value->num = (int)num; + + param_value = (gchar *)g_hash_table_lookup(s->param_list, ENUM_KEY(EXTCAP_PARAM_VALUE)); + if (param_value == NULL) { + extcap_free_toolbar_value(value); + return NULL; + } + value->value = g_strdup(param_value); + + param_value = (gchar *)g_hash_table_lookup(s->param_list, ENUM_KEY(EXTCAP_PARAM_DISPLAY)); + if (param_value == NULL) { + extcap_free_toolbar_value(value); + return NULL; + } + value->display = g_strdup(param_value); + + param_value = (gchar *)g_hash_table_lookup(s->param_list, ENUM_KEY(EXTCAP_PARAM_DEFAULT)); + if (param_value != NULL) { + value->is_default = g_regex_match_simple(EXTCAP_BOOLEAN_REGEX, param_value, G_REGEX_CASELESS, (GRegexMatchFlags)0); + } + + control = (iface_toolbar_control *)entry->data; + control->values = g_list_append(control->values, value); + + return NULL; + } + + return control; +} + +GList *extcap_parse_interfaces(gchar *output, GList **control_items) { GList *result = NULL; GList *tokens = NULL; @@ -613,6 +816,7 @@ GList *extcap_parse_interfaces(gchar *output) { while (walker) { extcap_interface *ri = NULL; + iface_toolbar_control *ti = NULL; extcap_token_sentence *if_sentence = (extcap_token_sentence *) walker->data; if (if_sentence) { @@ -622,6 +826,13 @@ GList *extcap_parse_interfaces(gchar *output) { if ((ri = extcap_parse_interface_sentence(if_sentence))) { result = g_list_append(result, ri); } + } else if (control_items && + ((g_ascii_strcasecmp(if_sentence->sentence, "control") == 0) || + (g_ascii_strcasecmp(if_sentence->sentence, "value") == 0))) + { + if ((ti = extcap_parse_control_sentence(*control_items, if_sentence))) { + *control_items = g_list_append(*control_items, ti); + } } } diff --git a/extcap_parser.h b/extcap_parser.h index 1fd4d42071..3d4c8d0d7b 100644 --- a/extcap_parser.h +++ b/extcap_parser.h @@ -34,7 +34,8 @@ typedef enum { EXTCAP_SENTENCE_VALUE, EXTCAP_SENTENCE_EXTCAP, EXTCAP_SENTENCE_INTERFACE, - EXTCAP_SENTENCE_DLT + EXTCAP_SENTENCE_DLT, + EXTCAP_SENTENCE_CONTROL } extcap_sentence_type; typedef enum { @@ -78,7 +79,9 @@ typedef enum { EXTCAP_PARAM_SAVE, EXTCAP_PARAM_VALIDATION, EXTCAP_PARAM_VERSION, - EXTCAP_PARAM_HELP + EXTCAP_PARAM_HELP, + EXTCAP_PARAM_CONTROL, + EXTCAP_PARAM_ROLE } extcap_param_type; #define ENUM_KEY(s) GUINT_TO_POINTER((guint)s) @@ -197,7 +200,7 @@ void extcap_free_arg_list(GList *a); GList * extcap_parse_args(gchar *output); /* Parse all sentences for interfaces */ -GList * extcap_parse_interfaces(gchar *output); +GList * extcap_parse_interfaces(gchar *output, GList **control_items); /* Parse all sentences for DLTs */ GList * extcap_parse_dlts(gchar *output); diff --git a/sync_pipe.h b/sync_pipe.h index 455b2b4bd6..d713305297 100644 --- a/sync_pipe.h +++ b/sync_pipe.h @@ -58,6 +58,7 @@ #define SP_PACKET_COUNT 'P' /* count of packets captured since last message */ #define SP_DROPS 'D' /* count of packets dropped in capture */ #define SP_SUCCESS 'S' /* success indication, no extra data */ +#define SP_TOOLBAR_CTRL 'T' /* interface toolbar control packet */ /* * Win32 only: Indications sent out on the signal pipe (from parent to child) * (UNIX-like sends signals for this) diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 8a52ef9463..e02eab1c6e 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -34,6 +34,7 @@ set(COMMON_UI_SRC failure_message.c filter_files.c firewall_rules.c + iface_toolbar.c iface_lists.c io_graph_item.c language.c diff --git a/ui/Makefile.am b/ui/Makefile.am index 776ad4230b..49699c6615 100644 --- a/ui/Makefile.am +++ b/ui/Makefile.am @@ -60,6 +60,7 @@ WIRESHARK_UI_SRC = \ failure_message.c \ filter_files.c \ firewall_rules.c \ + iface_toolbar.c \ iface_lists.c \ io_graph_item.c \ language.c \ @@ -109,6 +110,7 @@ WIRESHARK_UI_INCLUDES = \ help_url.h \ packet_list_utils.h \ firewall_rules.h \ + iface_toolbar.h \ iface_lists.h \ io_graph_item.h \ language.h \ diff --git a/ui/iface_toolbar.c b/ui/iface_toolbar.c new file mode 100644 index 0000000000..0e16456ee4 --- /dev/null +++ b/ui/iface_toolbar.c @@ -0,0 +1,68 @@ +/* iface_toolbar.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include + +#include "iface_toolbar.h" + + +static iface_toolbar_add_cb_t iface_toolbar_add_cb; +static iface_toolbar_remove_cb_t iface_toolbar_remove_cb; + +void iface_toolbar_add(const iface_toolbar *toolbar) +{ + if (iface_toolbar_add_cb) { + iface_toolbar_add_cb(toolbar); + } +} + +void iface_toolbar_remove(const gchar *menu_title) +{ + if (iface_toolbar_remove_cb) { + iface_toolbar_remove_cb(menu_title); + } +} + +gboolean iface_toolbar_use(void) +{ + return iface_toolbar_add_cb ? TRUE : FALSE; +} + +void iface_toolbar_register_cb(iface_toolbar_add_cb_t add_cb, iface_toolbar_remove_cb_t remove_cb) +{ + iface_toolbar_add_cb = add_cb; + iface_toolbar_remove_cb = remove_cb; +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/iface_toolbar.h b/ui/iface_toolbar.h new file mode 100644 index 0000000000..888c817230 --- /dev/null +++ b/ui/iface_toolbar.h @@ -0,0 +1,105 @@ +/* iface_toolbar.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __IFACE_TOOLBAR_H__ +#define __IFACE_TOOLBAR_H__ + +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef enum { + INTERFACE_TYPE_UNKNOWN, + INTERFACE_TYPE_BOOLEAN, + INTERFACE_TYPE_BUTTON, + INTERFACE_TYPE_SELECTOR, + INTERFACE_TYPE_STRING +} iface_toolbar_ctrl_type; + +typedef enum { + INTERFACE_ROLE_UNKNOWN, + INTERFACE_ROLE_CONTROL, + INTERFACE_ROLE_HELP, + INTERFACE_ROLE_LOGGER, + INTERFACE_ROLE_RESET +} iface_toolbar_ctrl_role; + +typedef struct _iface_toolbar_value { + int num; + gchar *value; + gchar *display; + gboolean is_default; +} iface_toolbar_value; + +typedef struct _iface_toolbar_control { + int num; + iface_toolbar_ctrl_type ctrl_type; + iface_toolbar_ctrl_role ctrl_role; + gchar *display; + gchar *validation; + gboolean is_required; + gchar *tooltip; + gchar *placeholder; + union { + gboolean boolean; + gchar *string; + } default_value; + GList *values; +} iface_toolbar_control; + +typedef struct _iface_toolbar { + gchar *menu_title; + gchar *help; + GList *ifnames; + GList *controls; +} iface_toolbar; + +typedef void (*iface_toolbar_add_cb_t)(const iface_toolbar *); +typedef void (*iface_toolbar_remove_cb_t)(const gchar *); + +void iface_toolbar_add(const iface_toolbar *toolbar); + +void iface_toolbar_remove(const gchar *menu_title); + +gboolean iface_toolbar_use(void); + +void iface_toolbar_register_cb(iface_toolbar_add_cb_t, iface_toolbar_remove_cb_t); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __IFACE_TOOLBAR_H__ */ + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/CMakeLists.txt b/ui/qt/CMakeLists.txt index e8936e9119..ca3d67f530 100644 --- a/ui/qt/CMakeLists.txt +++ b/ui/qt/CMakeLists.txt @@ -87,6 +87,9 @@ set(WIRESHARK_QT_HEADERS gsm_map_summary_dialog.h iax2_analysis_dialog.h import_text_dialog.h + interface_toolbar.h + interface_toolbar_reader.h + interface_toolbar_lineedit.h interface_tree_model.h interface_tree_cache_model.h interface_sort_filter_model.h @@ -257,6 +260,9 @@ set(WIRESHARK_QT_SRC geometry_state_dialog.cpp iax2_analysis_dialog.cpp import_text_dialog.cpp + interface_toolbar.cpp + interface_toolbar_reader.cpp + interface_toolbar_lineedit.cpp interface_tree_model.cpp interface_tree_cache_model.cpp interface_sort_filter_model.cpp @@ -418,6 +424,7 @@ set(WIRESHARK_QT_UI iax2_analysis_dialog.ui import_text_dialog.ui interface_frame.ui + interface_toolbar.ui io_graph_dialog.ui layout_preferences_frame.ui lbm_lbtrm_transport_dialog.ui diff --git a/ui/qt/Makefile.am b/ui/qt/Makefile.am index 77d17ea449..7f52918fcf 100644 --- a/ui/qt/Makefile.am +++ b/ui/qt/Makefile.am @@ -70,6 +70,7 @@ NODIST_GENERATED_HEADER_FILES = \ ui_iax2_analysis_dialog.h \ ui_import_text_dialog.h \ ui_interface_frame.h \ + ui_interface_toolbar.h \ ui_io_graph_dialog.h \ ui_layout_preferences_frame.h \ ui_lbm_lbtrm_transport_dialog.h \ @@ -215,6 +216,9 @@ MOC_HDRS = \ iax2_analysis_dialog.h \ import_text_dialog.h \ interface_frame.h \ + interface_toolbar.h \ + interface_toolbar_lineedit.h \ + interface_toolbar_reader.h \ interface_tree_model.h \ interface_tree_cache_model.h \ interface_sort_filter_model.h \ @@ -340,6 +344,7 @@ UI_FILES = \ iax2_analysis_dialog.ui \ import_text_dialog.ui \ interface_frame.ui \ + interface_toolbar.ui \ io_graph_dialog.ui \ layout_preferences_frame.ui \ lbm_lbtrm_transport_dialog.ui \ @@ -499,6 +504,9 @@ WIRESHARK_QT_SRC = \ iax2_analysis_dialog.cpp \ import_text_dialog.cpp \ interface_frame.cpp \ + interface_toolbar.cpp \ + interface_toolbar_lineedit.cpp \ + interface_toolbar_reader.cpp \ interface_tree_model.cpp \ interface_tree_cache_model.cpp \ interface_sort_filter_model.cpp \ @@ -822,6 +830,8 @@ io_graph_dialog.$(OBJEXT): ui_io_graph_dialog.h interface_frame.$(OBJEXT): ui_interface_frame.h +interface_toolbar.$(OBJEXT): ui_interface_toolbar.h + layout_preferences_frame.$(OBJEXT): ui_layout_preferences_frame.h lbm_lbtrm_transport_dialog.$(OBJEXT): ui_lbm_lbtrm_transport_dialog.h diff --git a/ui/qt/funnel_text_dialog.cpp b/ui/qt/funnel_text_dialog.cpp index 6ba873f451..8be47b032c 100644 --- a/ui/qt/funnel_text_dialog.cpp +++ b/ui/qt/funnel_text_dialog.cpp @@ -47,6 +47,7 @@ FunnelTextDialog::FunnelTextDialog(const QString &title) : if (!title.isEmpty()) { loadGeometry(0, 0, QString("Funnel %1").arg(title)); } + setWindowTitle(wsApp->windowTitleString(title)); funnel_text_window_.funnel_text_dialog = this; @@ -75,7 +76,6 @@ void FunnelTextDialog::reject() struct _funnel_text_window_t *FunnelTextDialog::textWindowNew(const QString title) { FunnelTextDialog *ftd = new FunnelTextDialog(title); - ftd->setWindowTitle(wsApp->windowTitleString(title)); ftd->show(); return &ftd->funnel_text_window_; } diff --git a/ui/qt/interface_toolbar.cpp b/ui/qt/interface_toolbar.cpp new file mode 100644 index 0000000000..56a1a53b34 --- /dev/null +++ b/ui/qt/interface_toolbar.cpp @@ -0,0 +1,895 @@ +/* interface_toolbar.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include + +#include "interface_toolbar.h" +#include "interface_toolbar_lineedit.h" +#include "simple_dialog.h" +#include "ui/main_statusbar.h" +#include + +#include "sync_pipe.h" +#include "wsutil/file_util.h" + +#include +#include +#include +#include +#include +#include +#include + +static const char *interface_type_property = "control_type"; +static const char *interface_role_property = "control_role"; + +// From interface control protocol. +enum InterfaceControlCommand { + commandControlInitialized = 0, + commandControlSet = 1, + commandControlAdd = 2, + commandControlRemove = 3, + commandControlEnable = 4, + commandControlDisable = 5, + commandStatusMessage = 6, + commandInformationMessage = 7, + commandWarningMessage = 8, + commandErrorMessage = 9, +}; + +// To do: +// - Move control pipe handling to extcap + +InterfaceToolbar::InterfaceToolbar(QWidget *parent, const iface_toolbar *toolbar) : + QFrame(parent), + ui(new Ui::InterfaceToolbar), + help_link_(toolbar->help), + use_spacer_(true) +{ + ui->setupUi(this); + + // Fill inn interfaces list and initialize default interface values + ui->interfacesComboBox->blockSignals(true); + for (GList *walker = toolbar->ifnames; walker; walker = walker->next) + { + QString ifname((gchar *)walker->data); + ui->interfacesComboBox->addItem(ifname); + interface_[ifname].reader_thread = NULL; + interface_[ifname].out_fd = -1; + interface_[ifname].log_dialog = NULL; + } + ui->interfacesComboBox->blockSignals(false); + + initializeControls(toolbar); + +#ifdef Q_OS_MAC + foreach (QWidget *w, findChildren()) + { + w->setAttribute(Qt::WA_MacSmallSize, true); + } +#endif + + if (!use_spacer_) + { + ui->horizontalSpacer->changeSize(0,0, QSizePolicy::Fixed, QSizePolicy::Fixed); + } + + updateWidgets(); +} + +InterfaceToolbar::~InterfaceToolbar() +{ + foreach (QString ifname, interface_.keys()) + { + if (interface_[ifname].log_dialog) + { + interface_[ifname].log_dialog->close(); + } + } + + delete ui; +} + +void InterfaceToolbar::initializeControls(const iface_toolbar *toolbar) +{ + for (GList *walker = toolbar->controls; walker; walker = walker->next) + { + iface_toolbar_control *control = (iface_toolbar_control *)walker->data; + + if (control_widget_.contains(control->num)) + { + // Already have a widget with this number + continue; + } + + QWidget *widget = NULL; + switch (control->ctrl_type) + { + case INTERFACE_TYPE_BOOLEAN: + widget = createCheckbox(control); + break; + + case INTERFACE_TYPE_BUTTON: + widget = createButton(control); + break; + + case INTERFACE_TYPE_SELECTOR: + widget = createSelector(control); + break; + + case INTERFACE_TYPE_STRING: + widget = createString(control); + break; + + default: + // Not supported + break; + } + + if (widget) + { + widget->setProperty(interface_type_property, control->ctrl_type); + widget->setProperty(interface_role_property, control->ctrl_role); + control_widget_[control->num] = widget; + } + } +} + +void InterfaceToolbar::setDefaultValue(int num, const QByteArray &value) +{ + foreach (QString ifname, interface_.keys()) + { + // Adding default value to all interfaces + interface_[ifname].value[num] = value; + } + default_value_[num] = value; +} + +QWidget *InterfaceToolbar::createCheckbox(iface_toolbar_control *control) +{ + QCheckBox *checkbox = new QCheckBox(QString().fromUtf8(control->display)); + checkbox->setToolTip(QString().fromUtf8(control->tooltip)); + + if (control->default_value.boolean) + { + checkbox->setCheckState(Qt::Checked); + QByteArray default_value(1, 1); + setDefaultValue(control->num, default_value); + } + + connect(checkbox, SIGNAL(stateChanged(int)), this, SLOT(onCheckBoxChanged(int))); + + ui->leftLayout->addWidget(checkbox); + + return checkbox; +} + +QWidget *InterfaceToolbar::createButton(iface_toolbar_control *control) +{ + QPushButton *button = new QPushButton(QString().fromUtf8((gchar *)control->display)); + button->setMaximumHeight(27); + button->setToolTip(QString().fromUtf8(control->tooltip)); + + switch (control->ctrl_role) + { + case INTERFACE_ROLE_CONTROL: + setDefaultValue(control->num, (gchar *)control->display); + connect(button, SIGNAL(pressed()), this, SLOT(onButtonPressed())); + break; + + case INTERFACE_ROLE_HELP: + connect(button, SIGNAL(pressed()), this, SLOT(onHelpButtonPressed())); + if (help_link_.isEmpty()) + { + // No help URL provided + button->hide(); + } + break; + + case INTERFACE_ROLE_LOGGER: + connect(button, SIGNAL(pressed()), this, SLOT(onLogButtonPressed())); + break; + + case INTERFACE_ROLE_RESET: + button->setText("Reset"); + button->setToolTip("Restore default values"); + connect(button, SIGNAL(pressed()), this, SLOT(onResetButtonPressed())); + break; + + default: + break; + } + + ui->rightLayout->addWidget(button); + + return button; +} + +QWidget *InterfaceToolbar::createSelector(iface_toolbar_control *control) +{ + QLabel *label = new QLabel(QString().fromUtf8(control->display)); + label->setToolTip(QString().fromUtf8(control->tooltip)); + QComboBox *combobox = new QComboBox(); + combobox->setToolTip(QString().fromUtf8(control->tooltip)); + combobox->setSizeAdjustPolicy(QComboBox::AdjustToContents); + + for (GList *walker = control->values; walker; walker = walker->next) + { + iface_toolbar_value *val = (iface_toolbar_value *)walker->data; + QString value = QString().fromUtf8((gchar *)val->value); + if (value.length() == 0) + { + // Invalid value + continue; + } + QString display = QString().fromUtf8((gchar *)val->display); + QByteArray interface_value; + + interface_value.append(value); + if (display.length() == 0) + { + display = value; + } + else + { + interface_value.append('\0' + display); + } + combobox->addItem(display, value); + if (val->is_default) + { + combobox->setCurrentText(display); + setDefaultValue(control->num, value.toUtf8()); + } + foreach (QString ifname, interface_.keys()) + { + // Adding values to all interfaces + interface_[ifname].list[control->num].append(interface_value); + } + default_list_[control->num].append(interface_value); + } + + connect(combobox, SIGNAL(currentIndexChanged(int)), this, SLOT(onComboBoxChanged(int))); + + ui->leftLayout->addWidget(label); + ui->leftLayout->addWidget(combobox); + label_widget_[control->num] = label; + + return combobox; +} + +QWidget *InterfaceToolbar::createString(iface_toolbar_control *control) +{ + QLabel *label = new QLabel(QString().fromUtf8(control->display)); + label->setToolTip(QString().fromUtf8(control->tooltip)); + InterfaceToolbarLineEdit *lineedit = new InterfaceToolbarLineEdit(NULL, control->validation, control->is_required); + lineedit->setToolTip(QString().fromUtf8(control->tooltip)); + lineedit->setPlaceholderText(QString().fromUtf8(control->placeholder)); + + if (control->default_value.string) + { + lineedit->setText(QString().fromUtf8(control->default_value.string)); + setDefaultValue(control->num, control->default_value.string); + } + + connect(lineedit, SIGNAL(editedTextApplied()), this, SLOT(onLineEditChanged())); + + ui->leftLayout->addWidget(label); + ui->leftLayout->addWidget(lineedit); + label_widget_[control->num] = label; + use_spacer_ = false; + + return lineedit; +} + +void InterfaceToolbar::setWidgetValue(QWidget *widget, int command, QByteArray payload) +{ + if (QComboBox *combobox = dynamic_cast(widget)) + { + combobox->blockSignals(true); + switch (command) + { + case commandControlSet: + { + int idx = combobox->findData(payload); + if (idx != -1) + { + combobox->setCurrentIndex(idx); + } + break; + } + + case commandControlAdd: + { + QString value; + QString display; + if (payload.contains('\0')) + { + // The payload contains "value\0display" + QList values = payload.split('\0'); + value = values[0]; + display = values[1]; + } + else + { + value = display = payload; + } + + int idx = combobox->findData(value); + if (idx != -1) + { + // The value already exists, update item text + combobox->setItemText(idx, display); + } + else + { + combobox->addItem(display, value); + } + break; + } + + case commandControlRemove: + { + if (payload.size() == 0) + { + combobox->clear(); + } + else + { + int idx = combobox->findData(payload); + if (idx != -1) + { + combobox->removeItem(idx); + } + } + break; + } + + default: + break; + } + combobox->blockSignals(false); + } + else if (InterfaceToolbarLineEdit *lineedit = dynamic_cast(widget)) + { + // We don't block signals here because changes are applied with enter or apply button, + // and we want InterfaceToolbarLineEdit to always syntax check the text. + switch (command) + { + case commandControlSet: + lineedit->setText(payload); + lineedit->disableApplyButton(); + break; + + default: + break; + } + } + else if (QCheckBox *checkbox = dynamic_cast(widget)) + { + checkbox->blockSignals(true); + switch (command) + { + case commandControlSet: + { + Qt::CheckState state = Qt::Unchecked; + if (payload.size() > 0 && payload.at(0) != 0) + { + state = Qt::Checked; + } + checkbox->setCheckState(state); + break; + } + + default: + break; + } + checkbox->blockSignals(false); + } + else if (QPushButton *button = dynamic_cast(widget)) + { + if ((command == commandControlSet) && + widget->property(interface_role_property).toInt() == INTERFACE_ROLE_CONTROL) + { + button->setText(payload); + } + } +} + +void InterfaceToolbar::setInterfaceValue(QString ifname, QWidget *widget, int num, int command, QByteArray payload) +{ + if (dynamic_cast(widget)) + { + switch (command) + { + case commandControlSet: + foreach (QByteArray entry, interface_[ifname].list[num]) + { + if (entry == payload || entry.startsWith(payload + '\0')) + { + interface_[ifname].value[num] = payload; + } + } + break; + + case commandControlAdd: + interface_[ifname].list[num].append(payload); + break; + + case commandControlRemove: + if (payload.size() == 0) + { + interface_[ifname].value[num].clear(); + interface_[ifname].list[num].clear(); + } + else + { + foreach (QByteArray entry, interface_[ifname].list[num]) + { + if (entry == payload || entry.startsWith(payload + '\0')) + { + interface_[ifname].list[num].removeAll(entry); + } + } + } + break; + + default: + break; + } + } + else if (dynamic_cast(widget)) + { + switch (command) + { + case commandControlSet: + interface_[ifname].value[num] = payload; + break; + + default: + break; + } + } + else if ((widget->property(interface_type_property).toInt() == INTERFACE_TYPE_BUTTON) && + (widget->property(interface_role_property).toInt() == INTERFACE_ROLE_LOGGER)) + { + if (command == commandControlSet) + { + if (interface_[ifname].log_dialog) + { + interface_[ifname].log_dialog->clearText(); + } + interface_[ifname].log_text.clear(); + } + if (command == commandControlSet || command == commandControlAdd) + { + if (interface_[ifname].log_dialog) + { + interface_[ifname].log_dialog->appendText(payload); + } + interface_[ifname].log_text.append(payload); + } + } + else if (widget->property(interface_role_property).toInt() == INTERFACE_ROLE_CONTROL) + { + // QCheckBox or QPushButton + interface_[ifname].value[num] = payload; + } +} + + +void InterfaceToolbar::controlReceived(QString ifname, int num, int command, QByteArray payload) +{ + switch (command) + { + case commandControlSet: + case commandControlAdd: + case commandControlRemove: + if (QWidget *widget = control_widget_[num]) + { + setInterfaceValue(ifname, widget, num, command, payload); + + if (ifname.compare(ui->interfacesComboBox->currentText()) == 0) + { + setWidgetValue(widget, command, payload); + } + } + break; + + case commandControlEnable: + case commandControlDisable: + if (QWidget *widget = control_widget_[num]) + { + if (widget->property(interface_role_property).toInt() == INTERFACE_ROLE_CONTROL) + { + bool enable = (command == commandControlEnable ? true : false); + interface_[ifname].widget_disabled[num] = !enable; + + if (ifname.compare(ui->interfacesComboBox->currentText()) == 0) + { + widget->setEnabled(enable); + if (label_widget_.contains(num)) + { + label_widget_[num]->setEnabled(enable); + } + } + } + } + break; + + case commandStatusMessage: + statusbar_push_temporary_msg("%s", payload.data()); + break; + + case commandInformationMessage: + simple_dialog(ESD_TYPE_INFO, ESD_BTN_OK, "%s", payload.data()); + break; + + case commandWarningMessage: + simple_dialog(ESD_TYPE_WARN, ESD_BTN_OK, "%s", payload.data()); + break; + + case commandErrorMessage: + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", payload.data()); + break; + + default: + // Unknown commands are silently ignored + break; + } +} + +void InterfaceToolbar::controlSend(QString ifname, int num, int command, const QByteArray &payload = QByteArray()) +{ + if (payload.length() > 65535) + { + // Not supported + return; + } + + if (interface_[ifname].out_fd == -1) + { + // Does not have a control out channel + return; + } + + ssize_t payload_length = payload.length() + 2; + unsigned char high_nibble = (payload_length >> 16) & 0xFF; + unsigned char mid_nibble = (payload_length >> 8) & 0xFF; + unsigned char low_nibble = (payload_length >> 0) & 0xFF; + + QByteArray ba; + + ba.append(SP_TOOLBAR_CTRL); + ba.append(high_nibble); + ba.append(mid_nibble); + ba.append(low_nibble); + ba.append(num); + ba.append(command); + ba.append(payload); + + if (ws_write(interface_[ifname].out_fd, ba.data(), ba.length()) != ba.length()) + { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Unable to send control message:\n%s.", + g_strerror(errno)); + } +} + +void InterfaceToolbar::onButtonPressed() +{ + const QString &ifname = ui->interfacesComboBox->currentText(); + QPushButton *button = static_cast(sender()); + int num = control_widget_.key(button); + + controlSend(ifname, num, commandControlSet); +} + +void InterfaceToolbar::onCheckBoxChanged(int state) +{ + const QString &ifname = ui->interfacesComboBox->currentText(); + QCheckBox *checkbox = static_cast(sender()); + int num = control_widget_.key(checkbox); + + QByteArray payload(1, state == Qt::Unchecked ? 0 : 1); + controlSend(ifname, num, commandControlSet, payload); + interface_[ifname].value[num] = payload; +} + +void InterfaceToolbar::onComboBoxChanged(int idx) +{ + const QString &ifname = ui->interfacesComboBox->currentText(); + QComboBox *combobox = static_cast(sender()); + int num = control_widget_.key(combobox); + QString value = combobox->itemData(idx).toString(); + + QByteArray payload(value.toUtf8()); + controlSend(ifname, num, commandControlSet, payload); + interface_[ifname].value[num] = payload; +} + +void InterfaceToolbar::onLineEditChanged() +{ + const QString &ifname = ui->interfacesComboBox->currentText(); + InterfaceToolbarLineEdit *lineedit = static_cast(sender()); + int num = control_widget_.key(lineedit); + + QByteArray payload(lineedit->text().toUtf8()); + controlSend(ifname, num, commandControlSet, payload); + interface_[ifname].value[num] = payload; +} + +void InterfaceToolbar::onLogButtonPressed() +{ + const QString &ifname = ui->interfacesComboBox->currentText(); + + if (!interface_[ifname].log_dialog) + { + QPushButton *button = static_cast(sender()); + interface_[ifname].log_dialog = new FunnelTextDialog(ifname + " " + button->text()); + connect(interface_[ifname].log_dialog, SIGNAL(accepted()), this, SLOT(closeLog())); + connect(interface_[ifname].log_dialog, SIGNAL(rejected()), this, SLOT(closeLog())); + + interface_[ifname].log_dialog->setText(interface_[ifname].log_text); + } + + interface_[ifname].log_dialog->show(); + interface_[ifname].log_dialog->raise(); + interface_[ifname].log_dialog->activateWindow(); +} + +void InterfaceToolbar::onHelpButtonPressed() +{ + QUrl help_url(help_link_); + + if (help_url.scheme().compare("file") != 0) { + QDesktopServices::openUrl(help_url); + } +} + +void InterfaceToolbar::closeLog() +{ + FunnelTextDialog *log_dialog = static_cast(sender()); + + foreach (QString ifname, interface_.keys()) + { + if (interface_[ifname].log_dialog == log_dialog) + { + interface_[ifname].log_dialog = NULL; + } + } +} + +void InterfaceToolbar::startReaderThread(QString ifname, QString control_in) +{ + QThread *thread = new QThread; + InterfaceToolbarReader *reader = new InterfaceToolbarReader(ifname, control_in); + reader->moveToThread(thread); + + connect(thread, SIGNAL(started()), reader, SLOT(loop())); + connect(reader, SIGNAL(finished()), thread, SLOT(quit())); + connect(reader, SIGNAL(finished()), reader, SLOT(deleteLater())); + connect(thread, SIGNAL(finished()), reader, SLOT(deleteLater())); + connect(reader, SIGNAL(received(QString, int, int, QByteArray)), + this, SLOT(controlReceived(QString, int, int, QByteArray))); + + interface_[ifname].reader_thread = thread; + + thread->start(); +} + +void InterfaceToolbar::startCapture(QString ifname, QString control_in, QString control_out) +{ + if (!interface_.contains(ifname) || // This interface is not for us + interface_[ifname].out_fd != -1) // Already have control channels for this interface + { + return; + } + + // The reader thread will open control in channel + startReaderThread(ifname, control_in); + + // Open control out channel + interface_[ifname].out_fd = ws_open(control_out.toUtf8(), O_WRONLY | O_BINARY, 0); + + sendChangedValues(ifname); + controlSend(ifname, 0, commandControlInitialized); + + updateWidgets(); +} + +void InterfaceToolbar::stopCapture() +{ + foreach (QString ifname, interface_.keys()) + { + if (interface_[ifname].reader_thread) + { +#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) + interface_[ifname].reader_thread->requestInterruption(); +#endif + interface_[ifname].reader_thread = NULL; + } + + if (interface_[ifname].out_fd != -1) + { + ws_close (interface_[ifname].out_fd); + interface_[ifname].out_fd = -1; + } + + foreach (int num, control_widget_.keys()) + { + // Reset disabled property for all widgets + interface_[ifname].widget_disabled[num] = false; + + QWidget *widget = control_widget_[num]; + if ((widget->property(interface_type_property).toInt() == INTERFACE_TYPE_BUTTON) && + (widget->property(interface_role_property).toInt() == INTERFACE_ROLE_CONTROL)) + { + // Reset default value for control buttons + interface_[ifname].value[num] = default_value_[num]; + + if (ifname.compare(ui->interfacesComboBox->currentText()) == 0) + { + setWidgetValue(widget, commandControlSet, default_value_[num]); + } + } + } + } + + updateWidgets(); +} + +void InterfaceToolbar::sendChangedValues(QString ifname) +{ + // Send all values which has changed + foreach (int num, control_widget_.keys()) + { + QWidget *widget = control_widget_[num]; + if ((interface_[ifname].value[num] != default_value_[num]) && + (widget->property(interface_type_property).toInt() != INTERFACE_TYPE_BUTTON) && + (widget->property(interface_role_property).toInt() == INTERFACE_ROLE_CONTROL)) + { + controlSend(ifname, num, commandControlSet, interface_[ifname].value[num]); + } + } +} + +void InterfaceToolbar::onResetButtonPressed() +{ + const QString &ifname = ui->interfacesComboBox->currentText(); + + // Set default values to all widgets and interfaces + foreach (int num, control_widget_.keys()) + { + QWidget *widget = control_widget_[num]; + if (default_list_[num].size() > 0) + { + // This is a QComboBox. Clear list and add new entries. + setWidgetValue(widget, commandControlRemove, QByteArray()); + interface_[ifname].list[num].clear(); + + foreach (QByteArray value, default_list_[num]) + { + setWidgetValue(widget, commandControlAdd, value); + interface_[ifname].list[num].append(value); + } + } + + switch (widget->property(interface_role_property).toInt()) + { + case INTERFACE_ROLE_CONTROL: + setWidgetValue(widget, commandControlSet, default_value_[num]); + interface_[ifname].value[num] = default_value_[num]; + break; + + case INTERFACE_ROLE_LOGGER: + if (interface_[ifname].log_dialog) + { + interface_[ifname].log_dialog->clearText(); + } + interface_[ifname].log_text.clear(); + break; + + default: + break; + } + } +} + +bool InterfaceToolbar::hasInterface(QString ifname) +{ + return interface_.contains(ifname); +} + +void InterfaceToolbar::updateWidgets() +{ + const QString &ifname = ui->interfacesComboBox->currentText(); + bool is_capturing = (interface_[ifname].out_fd == -1 ? false : true); + + foreach (int num, control_widget_.keys()) + { + QWidget *widget = control_widget_[num]; + if (!is_capturing && + (widget->property(interface_type_property).toInt() == INTERFACE_TYPE_BUTTON) && + (widget->property(interface_role_property).toInt() == INTERFACE_ROLE_CONTROL)) + { + widget->setEnabled(false); + } + else if (widget->property(interface_role_property).toInt() == INTERFACE_ROLE_CONTROL) + { + bool widget_enabled = !interface_[ifname].widget_disabled[num]; + widget->setEnabled(widget_enabled); + if (label_widget_.contains(num)) + { + label_widget_[num]->setEnabled(widget_enabled); + } + } + } + + foreach (int num, control_widget_.keys()) + { + QWidget *widget = control_widget_[num]; + if ((widget->property(interface_type_property).toInt() == INTERFACE_TYPE_BUTTON) && + (widget->property(interface_role_property).toInt() == INTERFACE_ROLE_RESET)) + { + widget->setEnabled(!is_capturing); + } + } +} + +void InterfaceToolbar::on_interfacesComboBox_currentIndexChanged(const QString &ifname) +{ + foreach (int num, control_widget_.keys()) + { + QWidget *widget = control_widget_[num]; + if (interface_[ifname].list[num].size() > 0) + { + // This is a QComboBox. Clear list and add new entries. + setWidgetValue(widget, commandControlRemove, QByteArray()); + + foreach (QByteArray value, interface_[ifname].list[num]) + { + setWidgetValue(widget, commandControlAdd, value); + } + } + + if (widget->property(interface_role_property).toInt() == INTERFACE_ROLE_CONTROL) + { + setWidgetValue(widget, commandControlSet, interface_[ifname].value[num]); + } + } + + updateWidgets(); +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/interface_toolbar.h b/ui/qt/interface_toolbar.h new file mode 100644 index 0000000000..ebde37db4d --- /dev/null +++ b/ui/qt/interface_toolbar.h @@ -0,0 +1,120 @@ +/* interface_toolbar.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef INTERFACE_TOOLBAR_H +#define INTERFACE_TOOLBAR_H + +#include + +#include "ui/iface_toolbar.h" +#include "funnel_text_dialog.h" +#include "interface_toolbar_reader.h" + +#include +#include +#include + + +namespace Ui { +class InterfaceToolbar; +} + +struct interface_values +{ + QThread *reader_thread; + int out_fd; + QMap value; + QMap > list; + FunnelTextDialog *log_dialog; + QString log_text; + QMap widget_disabled; +}; + +class InterfaceToolbar : public QFrame +{ + Q_OBJECT + +public: + explicit InterfaceToolbar(QWidget *parent = 0, const iface_toolbar *toolbar = NULL); + ~InterfaceToolbar(); + + void startCapture(QString ifname, QString control_in, QString control_out); + void stopCapture(); + bool hasInterface(QString ifname); + +public slots: + void controlReceived(QString ifname, int num, int command, QByteArray message); + +signals: + void closeReader(); + +private slots: + void startReaderThread(QString ifname, QString control_in); + void updateWidgets(); + + void onButtonPressed(); + void onCheckBoxChanged(int state); + void onComboBoxChanged(int idx); + void onLineEditChanged(); + void onLogButtonPressed(); + void onHelpButtonPressed(); + void onResetButtonPressed(); + + void closeLog(); + + void on_interfacesComboBox_currentIndexChanged(const QString &ifname); + +private: + void initializeControls(const iface_toolbar *toolbar); + void setDefaultValue(int num, const QByteArray &value); + void sendChangedValues(QString ifname); + QWidget *createCheckbox(iface_toolbar_control *control); + QWidget *createButton(iface_toolbar_control *control); + QWidget *createSelector(iface_toolbar_control *control); + QWidget *createString(iface_toolbar_control *control); + void controlSend(QString ifname, int num, int type, const QByteArray &payload); + void setWidgetValue(QWidget *widget, int type, QByteArray payload); + void setInterfaceValue(QString ifname, QWidget *widget, int num, int type, QByteArray payload); + + Ui::InterfaceToolbar *ui; + QMap interface_; + QMap default_value_; + QMap > default_list_; + QMap control_widget_; + QMap label_widget_; + QString help_link_; + bool use_spacer_; +}; + +#endif // INTERFACE_TOOLBAR_H + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/interface_toolbar.ui b/ui/qt/interface_toolbar.ui new file mode 100644 index 0000000000..20faae5a57 --- /dev/null +++ b/ui/qt/interface_toolbar.ui @@ -0,0 +1,69 @@ + + + InterfaceToolbar + + + + 0 + 0 + 600 + 32 + + + + Frame + + + QFrame::NoFrame + + + QFrame::Plain + + + + 0 + + + 0 + + + + + Select interface + + + Interface + + + + + + + Select interface + + + + + + + + + + Qt::Horizontal + + + + 40 + 5 + + + + + + + + + + + + diff --git a/ui/qt/interface_toolbar_lineedit.cpp b/ui/qt/interface_toolbar_lineedit.cpp new file mode 100644 index 0000000000..181bcb3be5 --- /dev/null +++ b/ui/qt/interface_toolbar_lineedit.cpp @@ -0,0 +1,149 @@ +/* interface_toolbar_lineedit.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include "interface_toolbar_lineedit.h" +#include "stock_icon_tool_button.h" +#include "epan/prefs.h" +#include "color_utils.h" + +#include + +// To do: +// - Make a narrower apply button + +InterfaceToolbarLineEdit::InterfaceToolbarLineEdit(QWidget *parent, QString validation_regex, bool is_required) : + QLineEdit(parent), + validation_regex_(validation_regex), + is_required_(is_required), + text_edited_(false) +{ + apply_button_ = new StockIconToolButton(this, "x-filter-apply"); + apply_button_->setCursor(Qt::ArrowCursor); + apply_button_->setEnabled(false); + apply_button_->setToolTip(tr("Apply changes")); + apply_button_->setIconSize(QSize(24, 14)); + apply_button_->setStyleSheet( + "QToolButton {" + " border: none;" + " background: transparent;" // Disables platform style on Windows. + " padding: 0 0 0 0;" + "}" + ); + + updateStyleSheet(isValid()); + + connect(this, SIGNAL(textChanged(const QString &)), this, SLOT(validateText())); + connect(this, SIGNAL(textEdited(const QString &)), this, SLOT(validateEditedText())); + connect(this, SIGNAL(returnPressed()), this, SLOT(applyEditedText())); + connect(apply_button_, SIGNAL(clicked()), this, SLOT(applyEditedText())); +} + +void InterfaceToolbarLineEdit::validateText() +{ + bool valid = isValid(); + + apply_button_->setEnabled(valid); + updateStyleSheet(valid); +} + +void InterfaceToolbarLineEdit::validateEditedText() +{ + text_edited_ = true; +} + +void InterfaceToolbarLineEdit::applyEditedText() +{ + if (text_edited_ && isValid()) + { + emit editedTextApplied(); + disableApplyButton(); + } +} + +void InterfaceToolbarLineEdit::disableApplyButton() +{ + apply_button_->setEnabled(false); + text_edited_ = false; +} + +bool InterfaceToolbarLineEdit::isValid() +{ + bool valid = true; + + if (is_required_ && text().length() == 0) + { + valid = false; + } + + if (!validation_regex_.isEmpty() && text().length() > 0) + { + QRegExp expr(validation_regex_); + if (!expr.isValid() || expr.indexIn(text(), 0) == -1) + { + valid = false; + } + } + + return valid; +} + +void InterfaceToolbarLineEdit::updateStyleSheet(bool is_valid) +{ + int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + QSize apsz = apply_button_->sizeHint(); + + QString style_sheet = QString( + "InterfaceToolbarLineEdit {" + " padding-right: %1px;" + " background-color: %2;" + "}" + ) + .arg(apsz.width() + frameWidth) + .arg(is_valid ? QString("") : ColorUtils::fromColorT(prefs.gui_text_invalid).name()); + + setStyleSheet(style_sheet); +} + +void InterfaceToolbarLineEdit::resizeEvent(QResizeEvent *) +{ + int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + QSize apsz = apply_button_->sizeHint(); + + apply_button_->move(contentsRect().right() - frameWidth - apsz.width() + 2, + contentsRect().top()); + apply_button_->setMinimumHeight(contentsRect().height()); + apply_button_->setMaximumHeight(contentsRect().height()); +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/interface_toolbar_lineedit.h b/ui/qt/interface_toolbar_lineedit.h new file mode 100644 index 0000000000..e26a50c976 --- /dev/null +++ b/ui/qt/interface_toolbar_lineedit.h @@ -0,0 +1,71 @@ +/* interface_toolbar_lineedit.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef INTERFACE_TOOLBAR_LINEEDIT_H +#define INTERFACE_TOOLBAR_LINEEDIT_H + +#include + +class StockIconToolButton; + +class InterfaceToolbarLineEdit : public QLineEdit +{ + Q_OBJECT + +public: + explicit InterfaceToolbarLineEdit(QWidget *parent = 0, QString validation_regex = QString(), bool is_required = false); + void disableApplyButton(); + +protected: + void resizeEvent(QResizeEvent *); + +signals: + void editedTextApplied(); + +private slots: + void validateText(); + void validateEditedText(); + void applyEditedText(); + +private: + bool isValid(); + void updateStyleSheet(bool is_valid); + + StockIconToolButton *apply_button_; + QString validation_regex_; + bool is_required_; + bool text_edited_; +}; + +#endif // INTERFACE_TOOLBAR_LINEEDIT_H + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/interface_toolbar_reader.cpp b/ui/qt/interface_toolbar_reader.cpp new file mode 100644 index 0000000000..5c8c52a247 --- /dev/null +++ b/ui/qt/interface_toolbar_reader.cpp @@ -0,0 +1,140 @@ +/* interface_toolbar_reader.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include + +#include "interface_toolbar_reader.h" +#include "sync_pipe.h" +#include "wsutil/file_util.h" + +#include + +const int header_size = 6; + +// To do: +// - Add support for WIN32 + +void InterfaceToolbarReader::loop() +{ +#ifndef _WIN32 + struct timeval timeout; + QByteArray header; + QByteArray payload; + fd_set readfds; + + int fd = ws_open(control_in_.toUtf8(), O_RDONLY | O_BINARY | O_NONBLOCK, 0); + if (fd == -1) + { + emit finished(); + return; + } + + forever + { + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + + timeout.tv_sec = 2; + timeout.tv_usec = 0; + + int ret = select(fd + 1, &readfds, NULL, NULL, &timeout); + if (ret == -1) + { + break; + } +#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) + if (QThread::currentThread()->isInterruptionRequested()) + { + break; + } +#endif + + if (ret > 0 && FD_ISSET(fd, &readfds)) + { + header.resize(header_size); + if (ws_read(fd, header.data(), header_size) != header_size) + { + break; + } + + unsigned char high_nibble = header[1] & 0xFF; + unsigned char mid_nibble = header[2] & 0xFF; + unsigned char low_nibble = header[3] & 0xFF; + ssize_t payload_len = (ssize_t)((high_nibble << 16) + (mid_nibble << 8) + low_nibble) - 2; + + payload.resize(payload_len); + if (payload_len > 0) + { + ssize_t total_len = 0; + while (total_len < payload_len) + { + ssize_t read_len = ws_read(fd, payload.data() + total_len, payload_len - total_len); + if (read_len == -1) + { + if (errno != EAGAIN) + { + break; + } + } + else + { + total_len += read_len; + } + } + if (total_len != payload_len) + { + break; + } + } + if (header[0] == SP_TOOLBAR_CTRL) + { + emit received(ifname_, (unsigned char)header[4], (unsigned char)header[5], payload); + } + } + } + + ws_close(fd); +#endif + emit finished(); +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/interface_toolbar_reader.h b/ui/qt/interface_toolbar_reader.h new file mode 100644 index 0000000000..1b34db4fb0 --- /dev/null +++ b/ui/qt/interface_toolbar_reader.h @@ -0,0 +1,65 @@ +/* interface_toolbar_reader.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef INTERFACE_TOOLBAR_READER_H +#define INTERFACE_TOOLBAR_READER_H + +#include +#include + +namespace Ui { +class InterfaceToolbarReader; +} + +class InterfaceToolbarReader : public QObject +{ + Q_OBJECT + +public: + InterfaceToolbarReader(QString ifname, QString control_in, QObject *parent = 0) : + QObject(parent), ifname_(ifname), control_in_(control_in) {} + +public slots: + void loop(); + +signals: + void received(QString ifname, int num, int command, QByteArray payload); + void finished(); + +private: + QString ifname_; + QString control_in_; +}; + +#endif // INTERFACE_TOOLBAR_READER_H + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/main_window.cpp b/ui/qt/main_window.cpp index 1570b4f4cc..0952594516 100644 --- a/ui/qt/main_window.cpp +++ b/ui/qt/main_window.cpp @@ -33,6 +33,7 @@ #include #include "ui/commandline.h" +#include "ui/iface_toolbar.h" #ifdef HAVE_LIBPCAP #include "ui/capture.h" @@ -61,6 +62,7 @@ #include "file_set_dialog.h" #include "funnel_statistics.h" #include "import_text_dialog.h" +#include "interface_toolbar.h" #include "packet_list.h" #include "proto_tree.h" #include "simple_dialog.h" @@ -205,6 +207,23 @@ static void plugin_if_mainwindow_update_toolbars(gconstpointer user_data) if (g_hash_table_lookup_extended(data_set, "toolbar_name", NULL, NULL)) { QString toolbarName((const char *)g_hash_table_lookup(data_set, "toolbar_name")); gbl_cur_main_window_->removeAdditionalToolbar(toolbarName); + + } +} + +static void mainwindow_add_toolbar(const iface_toolbar *toolbar_entry) +{ + if (gbl_cur_main_window_ && toolbar_entry) + { + gbl_cur_main_window_->addInterfaceToolbar(toolbar_entry); + } +} + +static void mainwindow_remove_toolbar(const gchar *menu_title) +{ + if (gbl_cur_main_window_ && menu_title) + { + gbl_cur_main_window_->removeInterfaceToolbar(menu_title); } } @@ -759,6 +778,17 @@ MainWindow::MainWindow(QWidget *parent) : #endif plugin_if_register_gui_cb(PLUGIN_IF_REMOVE_TOOLBAR, plugin_if_mainwindow_update_toolbars); +#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) && !defined(_WIN32) + // Register Interface Toolbar callbacks + // + // Qt version must be 5.2 or higher because the use of + // QThread::requestInterruption() in interface_toolbar.cpp and + // QThread::isInterruptionRequested() in interface_toolbar_reader.cpp + // + // The toolbar in/out control pipes are not supported on WIN32 yet. + iface_toolbar_register_cb(mainwindow_add_toolbar, mainwindow_remove_toolbar); +#endif + main_ui_->mainStack->setCurrentWidget(main_welcome_); } @@ -779,6 +809,13 @@ QMenu *MainWindow::createPopupMenu() menu->addAction(main_ui_->actionViewFilterToolbar); menu->addAction(main_ui_->actionViewWirelessToolbar); + if (!main_ui_->menuInterfaceToolbars->actions().isEmpty()) { + QMenu *submenu = menu->addMenu(main_ui_->menuInterfaceToolbars->title()); + foreach (QAction *action, main_ui_->menuInterfaceToolbars->actions()) { + submenu->addAction(action); + } + } + if (!main_ui_->menuAdditionalToolbars->actions().isEmpty()) { QMenu *subMenu = menu->addMenu(main_ui_->menuAdditionalToolbars->title()); foreach (QAction *action, main_ui_->menuAdditionalToolbars->actions()) { @@ -795,6 +832,76 @@ QMenu *MainWindow::createPopupMenu() return menu; } +void MainWindow::addInterfaceToolbar(const iface_toolbar *toolbar_entry) +{ + QMenu *menu = main_ui_->menuInterfaceToolbars; + bool visible = g_list_find_custom(recent.interface_toolbars, toolbar_entry->menu_title, (GCompareFunc) strcmp) ? true : false; + + QString title = QString().fromUtf8(toolbar_entry->menu_title); + QAction *action = new QAction(title, menu); + action->setEnabled(true); + action->setCheckable(true); + action->setChecked(visible); + action->setToolTip(tr("Show or hide the toolbar")); + + QAction *before = NULL; + foreach (QAction *action, menu->actions()) { + // Ensure we add the menu entries in sorted order + if (action->text().compare(title, Qt::CaseInsensitive) > 0) { + before = action; + break; + } + } + menu->insertAction(before, action); + + InterfaceToolbar *interface_toolbar = new InterfaceToolbar(this, toolbar_entry); + + QToolBar *toolbar = new QToolBar(this); + toolbar->addWidget(interface_toolbar); + toolbar->setMovable(false); + toolbar->setVisible(visible); + + action->setData(qVariantFromValue(toolbar)); + + addToolBar(Qt::TopToolBarArea, toolbar); + insertToolBarBreak(toolbar); + + if (show_hide_actions_) { + show_hide_actions_->addAction(action); + } + + menu->menuAction()->setVisible(true); +} + +void MainWindow::removeInterfaceToolbar(const gchar *menu_title) +{ + QMenu *menu = main_ui_->menuInterfaceToolbars; + QAction *action = NULL; + QMap::iterator i; + + QString title = QString().fromUtf8(menu_title); + foreach (action, menu->actions()) { + if (title.compare(action->text()) == 0) { + break; + } + } + + if (action) { + if (show_hide_actions_) { + show_hide_actions_->removeAction(action); + } + menu->removeAction(action); + + QToolBar *toolbar = action->data().value(); + removeToolBar(toolbar); + + delete action; + delete toolbar; + } + + menu->menuAction()->setVisible(!menu->actions().isEmpty()); +} + void MainWindow::setPipeInputHandler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb) { pipe_source_ = source; @@ -1895,6 +2002,9 @@ void MainWindow::initShowHideMainWidgets() showHideMainWidgets(shmwa); } + // Initial hide the Interface Toolbar submenu + main_ui_->menuInterfaceToolbars->menuAction()->setVisible(false); + /* Initially hide the additional toolbars menus */ main_ui_->menuAdditionalToolbars->menuAction()->setVisible(false); @@ -2356,7 +2466,7 @@ void MainWindow::resizeEvent(QResizeEvent *event) } /* Update main window items based on whether there's a capture in progress. */ -void MainWindow::setForCaptureInProgress(bool capture_in_progress) +void MainWindow::setForCaptureInProgress(bool capture_in_progress, GArray *ifaces) { setMenusForCaptureInProgress(capture_in_progress); @@ -2368,6 +2478,20 @@ void MainWindow::setForCaptureInProgress(bool capture_in_progress) // set_capture_if_dialog_for_capture_in_progress(capture_in_progress); #endif + +#ifdef HAVE_EXTCAP + QList toolbars = findChildren(); + foreach (InterfaceToolbar *toolbar, toolbars) { + if (capture_in_progress && ifaces) { + for (guint i = 0; i < ifaces->len; i++) { + interface_options interface_opts = g_array_index(ifaces, interface_options, i); + toolbar->startCapture(interface_opts.name, interface_opts.extcap_control_in, interface_opts.extcap_control_out); + } + } else { + toolbar->stopCapture(); + } + } +#endif } static QList menu_groups = QList() diff --git a/ui/qt/main_window.h b/ui/qt/main_window.h index aeb3a18052..23e1de7cce 100644 --- a/ui/qt/main_window.h +++ b/ui/qt/main_window.h @@ -31,6 +31,7 @@ #include "file.h" #include "ui/ui_util.h" +#include "ui/iface_toolbar.h" #include #include @@ -100,6 +101,9 @@ public: void removeAdditionalToolbar(QString toolbarName); + void addInterfaceToolbar(const iface_toolbar *toolbar_entry); + void removeInterfaceToolbar(const gchar *menu_title); + protected: virtual bool eventFilter(QObject *obj, QEvent *event); virtual void keyPressEvent(QKeyEvent *event); @@ -224,7 +228,7 @@ private: void externalMenuHelper(ext_menu_t * menu, QMenu * subMenu, gint depth); - void setForCaptureInProgress(bool capture_in_progress = false); + void setForCaptureInProgress(bool capture_in_progress = false, GArray *ifaces = NULL); QMenu* findOrAddMenu(QMenu *parent_menu, QString& menu_text); void recursiveCopyProtoTreeItems(QTreeWidgetItem *item, QString &clip, int ident_level); diff --git a/ui/qt/main_window.ui b/ui/qt/main_window.ui index 39bfc55cb1..8ee6ec69c9 100644 --- a/ui/qt/main_window.ui +++ b/ui/qt/main_window.ui @@ -268,6 +268,11 @@ &View + + + Interface Toolbars + + &Zoom @@ -345,6 +350,7 @@ + diff --git a/ui/qt/main_window_slots.cpp b/ui/qt/main_window_slots.cpp index 4103da00b8..33eaa56318 100644 --- a/ui/qt/main_window_slots.cpp +++ b/ui/qt/main_window_slots.cpp @@ -119,6 +119,7 @@ #include "funnel_statistics.h" #include "gsm_map_summary_dialog.h" #include "iax2_analysis_dialog.h" +#include "interface_toolbar.h" #include "io_graph_dialog.h" #include #include "lbm_stream_dialog.h" @@ -486,6 +487,15 @@ void MainWindow::layoutToolbars() main_ui_->wirelessToolBar->setVisible(recent.wireless_toolbar_show); main_ui_->statusBar->setVisible(recent.statusbar_show); + foreach (QAction *action, main_ui_->menuInterfaceToolbars->actions()) { + QToolBar *toolbar = action->data().value(); + if (g_list_find_custom(recent.interface_toolbars, action->text().toUtf8(), (GCompareFunc) strcmp)) { + toolbar->setVisible(true); + } else { + toolbar->setVisible(false); + } + } + QList toolbars = findChildren(); foreach (QToolBar *bar, toolbars) { AdditionalToolBar *iftoolbar = dynamic_cast(bar); @@ -495,6 +505,7 @@ void MainWindow::layoutToolbars() visible = true; iftoolbar->setVisible(visible); + } } } @@ -523,6 +534,14 @@ void MainWindow::updateRecentActions() main_ui_->actionViewPacketDetails->setChecked(recent.tree_view_show && prefs_has_layout_pane_content(layout_pane_content_pdetails)); main_ui_->actionViewPacketBytes->setChecked(recent.byte_view_show && prefs_has_layout_pane_content(layout_pane_content_pbytes)); + foreach (QAction *action, main_ui_->menuInterfaceToolbars->actions()) { + if (g_list_find_custom(recent.interface_toolbars, action->text().toUtf8(), (GCompareFunc) strcmp)) { + action->setChecked(true); + } else { + action->setChecked(false); + } + } + foreach (QAction * action, main_ui_->menuAdditionalToolbars->actions()) { ext_toolbar_t * toolbar = VariantPointer::asPtr(action->data()); bool checked = false; @@ -628,7 +647,7 @@ void MainWindow::queuedFilterAction(QString action_filter, FilterAction::Action // Capture callbacks -void MainWindow::captureCapturePrepared(capture_session *) { +void MainWindow::captureCapturePrepared(capture_session *session) { #ifdef HAVE_LIBPCAP setTitlebarForCaptureInProgress(); @@ -636,7 +655,7 @@ void MainWindow::captureCapturePrepared(capture_session *) { /* Disable menu items that make no sense if you're currently running a capture. */ - setForCaptureInProgress(true); + setForCaptureInProgress(true, session->capture_opts->ifaces); // set_capture_if_dialog_for_capture_in_progress(TRUE); // /* Don't set up main window for a capture file. */ @@ -645,14 +664,14 @@ void MainWindow::captureCapturePrepared(capture_session *) { #endif // HAVE_LIBPCAP } -void MainWindow::captureCaptureUpdateStarted(capture_session *) { +void MainWindow::captureCaptureUpdateStarted(capture_session *session) { #ifdef HAVE_LIBPCAP /* We've done this in "prepared" above, but it will be cleared while switching to the next multiple file. */ setTitlebarForCaptureInProgress(); - setForCaptureInProgress(true); + setForCaptureInProgress(true, session->capture_opts->ifaces); setForCapturedPackets(true); #endif // HAVE_LIBPCAP @@ -2257,6 +2276,19 @@ void MainWindow::showHideMainWidgets(QAction *action) recent.byte_view_show = show; main_ui_->actionViewPacketBytes->setChecked(show); } else { + foreach (QAction *action, main_ui_->menuInterfaceToolbars->actions()) { + QToolBar *toolbar = action->data().value(); + if (widget == toolbar) { + GList *entry = g_list_find_custom(recent.interface_toolbars, action->text().toUtf8(), (GCompareFunc) strcmp); + if (show && !entry) { + recent.interface_toolbars = g_list_append(recent.interface_toolbars, g_strdup(action->text().toUtf8())); + } else if (!show && entry) { + recent.interface_toolbars = g_list_remove(recent.interface_toolbars, entry->data); + } + action->setChecked(show); + } + } + ext_toolbar_t * toolbar = VariantPointer::asPtr(action->data()); if (toolbar) { GList *entry = g_list_find_custom(recent.gui_additional_toolbars, toolbar->name, (GCompareFunc) strcmp); diff --git a/ui/recent.c b/ui/recent.c index 30c44f485a..898296c1c0 100644 --- a/ui/recent.c +++ b/ui/recent.c @@ -75,6 +75,7 @@ #define RECENT_GUI_RLC_PDUS_FROM_MAC_FRAMES "gui.rlc_pdus_from_mac_frames" #define RECENT_GUI_CUSTOM_COLORS "gui.custom_colors" #define RECENT_GUI_TOOLBAR_SHOW "gui.additional_toolbar_show" +#define RECENT_GUI_INTERFACE_TOOLBAR_SHOW "gui.interface_toolbar_show" #define RECENT_GUI_GEOMETRY "gui.geom." @@ -866,6 +867,12 @@ write_profile_recent(void) fprintf(rf, RECENT_GUI_TOOLBAR_SHOW ": %s\n", string_list); g_free(string_list); + fprintf(rf, "\n# Interface Toolbars show.\n"); + fprintf(rf, "# List of interface toolbars to show.\n"); + string_list = join_string_list(recent.interface_toolbars); + fprintf(rf, RECENT_GUI_INTERFACE_TOOLBAR_SHOW ": %s\n", string_list); + g_free(string_list); + fclose(rf); /* XXX - catch I/O errors (e.g. "ran out of disk space") and return @@ -1121,6 +1128,8 @@ read_set_recent_pair_static(gchar *key, const gchar *value, recent.gui_fileopen_remembered_dir = g_strdup(value); } else if (strcmp(key, RECENT_GUI_TOOLBAR_SHOW) == 0) { recent.gui_additional_toolbars = prefs_get_string_list(value); + } else if (strcmp(key, RECENT_GUI_INTERFACE_TOOLBAR_SHOW) == 0) { + recent.interface_toolbars = prefs_get_string_list(value); } return PREFS_SET_OK; @@ -1294,6 +1303,11 @@ recent_read_profile_static(char **rf_path_return, int *rf_errno_return) recent.gui_additional_toolbars = NULL; } + if (recent.interface_toolbars) { + g_list_free_full (recent.interface_toolbars, g_free); + recent.interface_toolbars = NULL; + } + /* Construct the pathname of the user's profile recent file. */ rf_path = get_persconffile_path(RECENT_FILE_NAME, TRUE); diff --git a/ui/recent.h b/ui/recent.h index 2f33302b9b..d41dd60d3d 100644 --- a/ui/recent.h +++ b/ui/recent.h @@ -111,6 +111,7 @@ typedef struct recent_settings_tag { gboolean gui_rlc_use_pdus_from_mac; GList *custom_colors; GList *gui_additional_toolbars; + GList *interface_toolbars; } recent_settings_t; /** Global recent settings. */