Browse Source

hotkey: Add hotkeys to ACK message to client if current view was changed

Serj Kalichev 1 year ago
parent
commit
ea61168f20
6 changed files with 169 additions and 8 deletions
  1. 1 0
      klish/kentry.h
  2. 2 0
      klish/kexec.h
  3. 4 1
      klish/kscheme/kentry.c
  4. 9 3
      klish/ksession/kexec.c
  5. 95 3
      klish/ktp/ktpd_session.c
  6. 58 1
      klish/xml-helper/load.c

+ 1 - 0
klish/kentry.h

@@ -117,6 +117,7 @@ bool_t kentry_add_hotkeys(kentry_t *entry, khotkey_t *hotkey);
 ssize_t kentry_hotkeys_len(const kentry_t *entry);
 kentry_hotkeys_node_t *kentry_hotkeys_iter(const kentry_t *entry);
 khotkey_t *kentry_hotkeys_each(kentry_hotkeys_node_t **iter);
+int kentry_hotkey_compare(const void *first, const void *second);
 
 // Fast access for nested entries with special purposes
 kentry_t *kentry_nested_by_purpose(const kentry_t *entry, kentry_purpose_e purpose);

+ 2 - 0
klish/kexec.h

@@ -44,6 +44,8 @@ bool_t kexec_set_buferr(kexec_t *exec, faux_buf_t *buferr);
 // Return code
 bool_t kexec_done(const kexec_t *exec);
 bool_t kexec_retcode(const kexec_t *exec, int *status);
+// Saved path
+kpath_t *kexec_saved_path(const kexec_t *exec);
 // CONTEXTs
 bool_t kexec_add_contexts(kexec_t *exec, kcontext_t *context);
 ssize_t kexec_contexts_len(const kexec_t *exec);

+ 4 - 1
klish/kscheme/kentry.c

@@ -12,6 +12,7 @@
 #include <klish/khotkey.h>
 
 
+// WARNING: Changing this structure don't forget to update kentry_link()
 struct kentry_s {
 	char *name; // Mandatory name (identifier within entries tree)
 	char *help; // Help for the entry
@@ -109,7 +110,7 @@ KNESTED_EACH(entry, kaction_t *, actions);
 
 // HOTKEY list
 KGET(entry, faux_list_t *, hotkeys);
-static KCMP_NESTED(entry, hotkey, key);
+KCMP_NESTED(entry, hotkey, key);
 KADD_NESTED(entry, khotkey_t *, hotkeys);
 KNESTED_LEN(entry, hotkeys);
 KNESTED_ITER(entry, hotkeys);
@@ -175,6 +176,7 @@ static void kentry_free_non_link(kentry_t *entry)
 
 	faux_list_free(entry->entrys);
 	faux_list_free(entry->actions);
+	faux_list_free(entry->hotkeys);
 	faux_free(entry->nested_by_purpose);
 }
 
@@ -239,6 +241,7 @@ bool_t kentry_link(kentry_t *dst, const kentry_t *src)
 	dst->filter = src->filter;
 	dst->entrys = src->entrys;
 	dst->actions = src->actions;
+	dst->hotkeys = src->hotkeys;
 	dst->nested_by_purpose = src->nested_by_purpose;
 	// udata - orig
 	// udata_free_fn - orig

+ 9 - 3
klish/ksession/kexec.c

@@ -59,6 +59,9 @@ KSET(exec, faux_buf_t *, bufout);
 KGET(exec, faux_buf_t *, buferr);
 KSET(exec, faux_buf_t *, buferr);
 
+// Saved path
+KGET(exec, kpath_t *, saved_path);
+
 // CONTEXT list
 KADD_NESTED(exec, kcontext_t *, contexts);
 KNESTED_LEN(exec, contexts);
@@ -272,9 +275,12 @@ static bool_t kexec_prepare(kexec_t *exec)
 		// The first context is a context of main command. The other
 		// contexts are contexts of filters. So save current path from
 		// first context.
-		if (iter == faux_list_head(exec->contexts))
-			exec->saved_path = kpath_clone(
-				ksession_path(kcontext_session(context)));
+		if (iter == faux_list_head(exec->contexts)) {
+			ksession_t *session = kcontext_session(context);
+			if (session && ksession_path(session))
+				exec->saved_path = kpath_clone(
+					ksession_path(session));
+		}
 
 		// Set the same STDERR to all contexts
 		kcontext_set_stderr(context, global_stderr);

+ 95 - 3
klish/ktp/ktpd_session.c

@@ -53,7 +53,8 @@ static bool_t wait_for_actions_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
 bool_t client_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
 	void *associated_data, void *user_data);
 static bool_t ktpd_session_exec(ktpd_session_t *ktpd, const char *line,
-	int *retcode, faux_error_t *error, bool_t dry_run);
+	int *retcode, faux_error_t *error,
+	bool_t dry_run, bool_t *view_was_changed);
 static bool_t action_stdout_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
 	void *associated_data, void *user_data);
 static bool_t action_stderr_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
@@ -162,6 +163,79 @@ static char *generate_prompt(ktpd_session_t *ktpd)
 }
 
 
+// Format: <key>'\0'<cmd>
+static bool_t add_hotkey(faux_msg_t *msg, khotkey_t *hotkey)
+{
+	const char *key = NULL;
+	const char *cmd = NULL;
+	char *whole_str = NULL;
+	size_t key_s = 0;
+	size_t cmd_s = 0;
+
+	key = khotkey_key(hotkey);
+	key_s = strlen(key);
+	cmd = khotkey_cmd(hotkey);
+	cmd_s = strlen(key);
+
+	whole_str = faux_zmalloc(key_s + 1 + cmd_s);
+	memcpy(whole_str, key, key_s);
+	memcpy(whole_str + key_s + 1, cmd, cmd_s);
+
+	faux_msg_add_param(msg, KTP_PARAM_HOTKEY, whole_str, key_s + 1 + cmd_s);
+	faux_free(whole_str);
+
+	return BOOL_TRUE;
+}
+
+
+static bool_t add_hotkeys_to_msg(ktpd_session_t *ktpd, faux_msg_t *msg)
+{
+	faux_list_t *list = NULL;
+	kpath_t *path = NULL;
+	kentry_hotkeys_node_t *l_iter = NULL;
+	khotkey_t *hotkey = NULL;
+
+	assert(ktpd);
+	assert(msg);
+
+	path = ksession_path(ktpd->session);
+	assert(path);
+	if (kpath_len(path) == 1) {
+		// We don't need additional list because there is only one
+		// VIEW in the path so hotkey's list is only one too. Get it.
+		list = kentry_hotkeys(klevel_entry(
+			(klevel_t *)faux_list_data(kpath_iter(path))));
+	} else {
+		faux_list_node_t *iterr = NULL;
+		klevel_t *level = NULL;
+		// Create temp hotkeys list to add hotkeys from all VIEWs in
+		// the path and exclude duplications. Don't free elements
+		// because they are just a references.
+		list = faux_list_new(FAUX_LIST_UNSORTED, FAUX_LIST_UNIQUE,
+			kentry_hotkey_compare, NULL, NULL);
+		// Begin with the end. Because hotkeys from nested VIEWs has
+		// higher priority.
+		iterr = kpath_iterr(path);
+		while ((level = kpath_eachr(&iterr))) {
+			const kentry_t *entry = klevel_entry(level);
+			kentry_hotkeys_node_t *hk_iter = kentry_hotkeys_iter(entry);
+			while ((hotkey = kentry_hotkeys_each(&hk_iter)))
+				faux_list_add(list, hotkey);
+		}
+	}
+
+	// Add found hotkeys to msg
+	l_iter = faux_list_head(list);
+	while ((hotkey = (khotkey_t *)faux_list_each(&l_iter)))
+		add_hotkey(msg, hotkey);
+
+	if (kpath_len(path) != 1)
+		faux_list_free(list);
+
+	return BOOL_TRUE;
+}
+
+
 // Now it's not really an auth function. Just a hand-shake with client and
 // passing prompt to client.
 static bool_t ktpd_session_process_auth(ktpd_session_t *ktpd, faux_msg_t *msg)
@@ -183,6 +257,7 @@ static bool_t ktpd_session_process_auth(ktpd_session_t *ktpd, faux_msg_t *msg)
 		faux_msg_add_param(ack, KTP_PARAM_PROMPT, prompt, strlen(prompt));
 		faux_str_free(prompt);
 	}
+	add_hotkeys_to_msg(ktpd, ack);
 	faux_msg_send_async(ack, ktpd->async);
 	faux_msg_free(ack);
 
@@ -201,6 +276,7 @@ static bool_t ktpd_session_process_cmd(ktpd_session_t *ktpd, faux_msg_t *msg)
 	uint32_t status = KTP_STATUS_NONE;
 	bool_t ret = BOOL_TRUE;
 	char *prompt = NULL;
+	bool_t view_was_changed = BOOL_FALSE;
 
 	assert(ktpd);
 	assert(msg);
@@ -218,7 +294,8 @@ static bool_t ktpd_session_process_cmd(ktpd_session_t *ktpd, faux_msg_t *msg)
 	error = faux_error_new();
 
 	ktpd->exec = NULL;
-	rc = ktpd_session_exec(ktpd, line, &retcode, error, dry_run);
+	rc = ktpd_session_exec(ktpd, line, &retcode, error,
+		dry_run, &view_was_changed);
 	faux_str_free(line);
 
 	// Command is scheduled. Eloop will wait for ACTION completion.
@@ -259,6 +336,9 @@ static bool_t ktpd_session_process_cmd(ktpd_session_t *ktpd, faux_msg_t *msg)
 		faux_msg_add_param(ack, KTP_PARAM_PROMPT, prompt, strlen(prompt));
 		faux_str_free(prompt);
 	}
+	// Add hotkeys
+	if (view_was_changed)
+		add_hotkeys_to_msg(ktpd, ack);
 	faux_msg_send_async(ack, ktpd->async);
 	faux_msg_free(ack);
 
@@ -269,7 +349,8 @@ static bool_t ktpd_session_process_cmd(ktpd_session_t *ktpd, faux_msg_t *msg)
 
 
 static bool_t ktpd_session_exec(ktpd_session_t *ktpd, const char *line,
-	int *retcode, faux_error_t *error, bool_t dry_run)
+	int *retcode, faux_error_t *error,
+	bool_t dry_run, bool_t *view_was_changed_p)
 {
 	kexec_t *exec = NULL;
 
@@ -300,6 +381,10 @@ static bool_t ktpd_session_exec(ktpd_session_t *ktpd, const char *line,
 	// If kexec contains only non-exec (for example dry-run) ACTIONs then
 	// we don't need event loop and can return here.
 	if (kexec_retcode(exec, retcode)) {
+		if (view_was_changed_p)
+			*view_was_changed_p = !kpath_is_equal(
+				ksession_path(ktpd->session),
+				kexec_saved_path(exec));
 		kexec_free(exec);
 		return BOOL_TRUE;
 	}
@@ -329,6 +414,7 @@ static bool_t wait_for_actions_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
 	ktp_cmd_e cmd = KTP_CMD_ACK;
 	uint32_t status = KTP_STATUS_NONE;
 	char *prompt = NULL;
+	bool_t view_was_changed = BOOL_FALSE;
 
 	if (!ktpd)
 		return BOOL_FALSE;
@@ -349,6 +435,9 @@ static bool_t wait_for_actions_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
 	faux_eloop_del_fd(eloop, kexec_stdout(ktpd->exec));
 	faux_eloop_del_fd(eloop, kexec_stderr(ktpd->exec));
 
+	view_was_changed = !kpath_is_equal(
+		ksession_path(ktpd->session), kexec_saved_path(ktpd->exec));
+
 	kexec_free(ktpd->exec);
 	ktpd->exec = NULL;
 	ktpd->state = KTPD_SESSION_STATE_IDLE;
@@ -369,6 +458,9 @@ static bool_t wait_for_actions_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
 		faux_msg_add_param(ack, KTP_PARAM_PROMPT, prompt, strlen(prompt));
 		faux_str_free(prompt);
 	}
+	// Add hotkeys
+	if (view_was_changed)
+		add_hotkeys_to_msg(ktpd, ack);
 	faux_msg_send_async(ack, ktpd->async);
 	faux_msg_free(ack);
 

+ 58 - 1
klish/xml-helper/load.c

@@ -33,7 +33,8 @@ static kxml_process_fn
 	process_ptype,
 	process_plugin,
 	process_klish,
-	process_entry;
+	process_entry,
+	process_hotkey;
 
 // Different TAGs types
 typedef enum {
@@ -53,6 +54,7 @@ typedef enum {
 	KTAG_COMPL,
 	KTAG_HELP,
 	KTAG_PROMPT,
+	KTAG_HOTKEY,
 	KTAG_MAX,
 } ktags_e;
 
@@ -73,6 +75,7 @@ static const char * const kxml_tags[] = {
 	"COMPL",
 	"HELP",
 	"PROMPT",
+	"HOTKEY",
 };
 
 static kxml_process_fn *kxml_handlers[] = {
@@ -92,6 +95,7 @@ static kxml_process_fn *kxml_handlers[] = {
 	process_command,
 	process_command,
 	process_command,
+	process_hotkey,
 };
 
 
@@ -903,3 +907,56 @@ err:
 
 	return res;
 }
+
+
+static bool_t process_hotkey(const kxml_node_t *element, void *parent,
+	faux_error_t *error)
+{
+	ihotkey_t ihotkey = {};
+	khotkey_t *hotkey = NULL;
+	bool_t res = BOOL_FALSE;
+	ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
+	kentry_t *parent_entry = (kentry_t *)parent;
+
+	ihotkey.key = kxml_node_attr(element, "key");
+	if (!ihotkey.key) {
+		faux_error_sprintf(error, TAG": hotkey without \"key\" attribute");
+		return BOOL_FALSE;
+	}
+	ihotkey.cmd = kxml_node_attr(element, "cmd");
+	if (!ihotkey.cmd) {
+		faux_error_sprintf(error, TAG": hotkey without \"cmd\" attribute");
+		return BOOL_FALSE;
+	}
+
+	hotkey = ihotkey_load(&ihotkey, error);
+	if (!hotkey)
+		goto err;
+
+	if (	(KTAG_ENTRY != parent_tag) &&
+		(KTAG_VIEW != parent_tag)) {
+		faux_error_sprintf(error,
+			TAG": Tag \"%s\" can't contain HOTKEY tag",
+			kxml_tag_name(parent_tag));
+		khotkey_free(hotkey);
+		goto err;
+	}
+	if (!kentry_add_hotkeys(parent_entry, hotkey)) {
+		faux_error_sprintf(error,
+			TAG": Can't add HOTKEY \"%s\" to ENTRY \"%s\". "
+			"Probably duplication",
+			khotkey_key(hotkey),
+			kentry_name(parent_entry));
+		khotkey_free(hotkey);
+		goto err;
+	}
+
+	// HOTKEY doesn't have children
+
+	res = BOOL_TRUE;
+err:
+	kxml_node_attr_free(ihotkey.key);
+	kxml_node_attr_free(ihotkey.cmd);
+
+	return res;
+}