rofi  1.5.4
ssh.c
Go to the documentation of this file.
1 /*
2  * rofi
3  *
4  * MIT/X11 License
5  * Copyright © 2013-2017 Qball Cow <qball@gmpclient.org>
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining
8  * a copy of this software and associated documentation files (the
9  * "Software"), to deal in the Software without restriction, including
10  * without limitation the rights to use, copy, modify, merge, publish,
11  * distribute, sublicense, and/or sell copies of the Software, and to
12  * permit persons to whom the Software is furnished to do so, subject to
13  * the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be
16  * included in all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25  *
26  */
27 
36 #define G_LOG_DOMAIN "Dialogs.Ssh"
37 
38 #include <config.h>
39 #include <glib.h>
40 #include <stdlib.h>
41 #include <stdio.h>
42 
43 #include <unistd.h>
44 #include <signal.h>
45 #include <sys/types.h>
46 #include <dirent.h>
47 #include <strings.h>
48 #include <string.h>
49 #include <ctype.h>
50 #include <errno.h>
51 #include <helper.h>
52 #include <glob.h>
53 
54 #include "rofi.h"
55 #include "settings.h"
56 #include "history.h"
57 #include "dialogs/ssh.h"
58 
59 
60 typedef struct _SshEntry {
61  char *hostname;
62  int port;
67 typedef struct
68 {
73  unsigned int hosts_list_length;
75 
79 #define SSH_CACHE_FILE "rofi-2.sshcache"
80 
85 #define SSH_TOKEN_DELIM "= \t\r\n"
86 
87 
95 static int execshssh ( const SshEntry *entry)
96 {
97  char **args = NULL;
98  int argsv = 0;
99  gchar *portstr = NULL;
100  if ( entry->port > 0 ) {
101  portstr = g_strdup_printf("%d", entry->port);
102  }
103  helper_parse_setup ( config.ssh_command, &args, &argsv,
104  "{host}", entry->hostname,
105  "{port}", portstr,
106  (char *) 0 );
107  g_free ( portstr );
108 
109  gsize l = strlen ( "Connecting to '' via rofi" ) + strlen ( entry->hostname ) + 1;
110  gchar *desc = g_newa ( gchar, l );
111 
112  g_snprintf ( desc, l, "Connecting to '%s' via rofi", entry->hostname );
113 
114  RofiHelperExecuteContext context = {
115  .name = "ssh",
116  .description = desc,
117  .command = "ssh",
118  };
119  return helper_execute ( NULL, args, "ssh ", entry->hostname, &context );
120 }
121 
127 static void exec_ssh ( const SshEntry *entry )
128 {
129  if ( !(entry->hostname )|| !(entry->hostname[0]) ) {
130  return;
131  }
132 
133  if ( !execshssh ( entry ) ) {
134  return;
135  }
136 
137  // This happens in non-critical time (After launching app)
138  // It is allowed to be a bit slower.
139  char *path = g_build_filename ( cache_dir, SSH_CACHE_FILE, NULL );
140  // TODO update.
141  if ( entry->port > 0 ) {
142  char *store = g_strdup_printf("%s\x1F%d", entry->hostname, entry->port );
143  history_set ( path, store );
144  g_free ( store );
145  } else {
146  history_set ( path, entry->hostname );
147  }
148  g_free ( path );
149 }
150 
156 static void delete_ssh ( const char *host )
157 {
158  if ( !host || !host[0] ) {
159  return;
160  }
161  char *path = g_build_filename ( cache_dir, SSH_CACHE_FILE, NULL );
162  history_remove ( path, host );
163  g_free ( path );
164 }
165 
174 static SshEntry *read_known_hosts_file ( const char *path, SshEntry * retv, unsigned int *length )
175 {
176  FILE *fd = fopen ( path, "r" );
177  if ( fd != NULL ) {
178  char *buffer = NULL;
179  size_t buffer_length = 0;
180  // Reading one line per time.
181  while ( getline ( &buffer, &buffer_length, fd ) > 0 ) {
182  // Strip whitespace.
183  char *start = g_strstrip(&(buffer[0]));
184  // Find start.
185  if ( *start == '#' || *start == '@' ){
186  // skip comments or cert-authority or revoked items.
187  continue;
188  }
189  if ( *start == '|' ) {
190  // Skip hashed hostnames.
191  continue;
192  }
193  // Find end of hostname set.
194  char *end = strstr ( start, " " );
195  if ( end == NULL ) {
196  // Something is wrong.
197  continue;
198  }
199  *end = '\0';
200  char *sep = start;
201  start = strsep(&sep,", " );
202  while ( start )
203  {
204  int port = 0;
205  if ( start[0] == '[' ) {
206  start++;
207  char *end = strchr ( start, ']');
208  if ( end[1] == ':' ){
209  *end = '\0';
210  errno = 0;
211  gchar *endptr = NULL;
212  gint64 number = g_ascii_strtoll ( &(end[2]), &endptr, 10);
213  if ( errno != 0 ) {
214  g_warning ( "Failed to parse port number: %s.", &(end[2]) );
215  } else if ( endptr == &(end[2])) {
216  g_warning ( "Failed to parse port number: %s, invalid number.", &(end[2]) );
217  } else if ( number < 0 || number > 65535 ) {
218  g_warning ( "Failed to parse port number: %s, out of range.", &(end[2]) );
219  } else {
220  port = number;
221  }
222  }
223  }
224  // Is this host name already in the list?
225  // We often get duplicates in hosts file, so lets check this.
226  int found = 0;
227  for ( unsigned int j = 0; j < ( *length ); j++ ) {
228  if ( !g_ascii_strcasecmp ( start, retv[j].hostname ) ) {
229  found = 1;
230  break;
231  }
232  }
233 
234  if ( !found ) {
235  // Add this host name to the list.
236  retv = g_realloc ( retv, ( ( *length ) + 2 ) * sizeof ( SshEntry ) );
237  retv[( *length )].hostname = g_strdup ( start );
238  retv[( *length )].port = port;
239  retv[( *length ) + 1].hostname = NULL;
240  retv[( *length ) + 1].port = 0;
241  ( *length )++;
242  }
243  start = strsep(&sep,", " );
244  }
245  }
246  if ( buffer != NULL ) {
247  free ( buffer );
248  }
249  if ( fclose ( fd ) != 0 ) {
250  g_warning ( "Failed to close hosts file: '%s'", g_strerror ( errno ) );
251  }
252  } else {
253  g_debug ( "Failed to open KnownHostFile: '%s'", path );
254  }
255 
256  return retv;
257 }
258 
267 static SshEntry *read_hosts_file ( SshEntry * retv, unsigned int *length )
268 {
269  // Read the hosts file.
270  FILE *fd = fopen ( "/etc/hosts", "r" );
271  if ( fd != NULL ) {
272  char *buffer = NULL;
273  size_t buffer_length = 0;
274  // Reading one line per time.
275  while ( getline ( &buffer, &buffer_length, fd ) > 0 ) {
276  // Evaluate one line.
277  unsigned int index = 0, ti = 0;
278  char *token = buffer;
279 
280  // Tokenize it.
281  do {
282  char c = buffer[index];
283  // Break on space, tab, newline and \0.
284  if ( c == ' ' || c == '\t' || c == '\n' || c == '\0' || c == '#' ) {
285  buffer[index] = '\0';
286  // Ignore empty tokens
287  if ( token[0] != '\0' ) {
288  ti++;
289  // and first token.
290  if ( ti > 1 ) {
291  // Is this host name already in the list?
292  // We often get duplicates in hosts file, so lets check this.
293  int found = 0;
294  for ( unsigned int j = 0; j < ( *length ); j++ ) {
295  if ( !g_ascii_strcasecmp ( token, retv[j].hostname ) ) {
296  found = 1;
297  break;
298  }
299  }
300 
301  if ( !found ) {
302  // Add this host name to the list.
303  retv = g_realloc ( retv,
304  ( ( *length ) + 2 ) * sizeof ( SshEntry ) );
305  retv[( *length )].hostname = g_strdup ( token );
306  retv[( *length )].port = 0;
307  retv[( *length ) + 1].hostname = NULL;
308  ( *length )++;
309  }
310  }
311  }
312  // Set start to next element.
313  token = &buffer[index + 1];
314  // Everything after comment ignore.
315  if ( c == '#' ) {
316  break;
317  }
318  }
319  // Skip to the next entry.
320  index++;
321  } while ( buffer[index] != '\0' && buffer[index] != '#' );
322  }
323  if ( buffer != NULL ) {
324  free ( buffer );
325  }
326  if ( fclose ( fd ) != 0 ) {
327  g_warning ( "Failed to close hosts file: '%s'", g_strerror ( errno ) );
328  }
329  }
330 
331  return retv;
332 }
333 
334 static void add_known_hosts_file ( SSHModePrivateData *pd, const char *token )
335 {
336  GList *item = g_list_find_custom ( pd->user_known_hosts, token, (GCompareFunc)g_strcmp0 );
337  if ( item == NULL ) {
338  g_debug("Add '%s' to UserKnownHost list", token);
339  pd->user_known_hosts = g_list_append ( pd->user_known_hosts, g_strdup ( token ) );
340  } else {
341  g_debug("File '%s' already in UserKnownHostsFile list", token);
342  }
343 }
344 
345 static void parse_ssh_config_file ( SSHModePrivateData *pd, const char *filename, SshEntry **retv, unsigned int *length, unsigned int num_favorites )
346 {
347  FILE *fd = fopen ( filename, "r" );
348 
349  g_debug ( "Parsing ssh config file: %s", filename );
350  if ( fd != NULL ) {
351  char *buffer = NULL;
352  size_t buffer_length = 0;
353  char *strtok_pointer = NULL;
354  while ( getline ( &buffer, &buffer_length, fd ) > 0 ) {
355  // Each line is either empty, a comment line starting with a '#'
356  // character or of the form "keyword [=] arguments", where there may
357  // be multiple (possibly quoted) arguments separated by whitespace.
358  // The keyword is separated from its arguments by whitespace OR by
359  // optional whitespace and a '=' character.
360  char *token = strtok_r ( buffer, SSH_TOKEN_DELIM, &strtok_pointer );
361  // Skip empty lines and comment lines. Also skip lines where the
362  // keyword is not "Host".
363  if ( !token || *token == '#' ) {
364  continue;
365  }
366  char *low_token = g_ascii_strdown(token, -1);
367  if ( g_strcmp0 ( low_token, "include" ) == 0 ) {
368  token = strtok_r ( NULL, SSH_TOKEN_DELIM, &strtok_pointer );
369  g_debug ( "Found Include: %s", token );
370  gchar *path = rofi_expand_path ( token );
371  gchar *full_path = NULL;
372  if ( !g_path_is_absolute ( path ) ) {
373  char *dirname = g_path_get_dirname ( filename );
374  full_path = g_build_filename ( dirname, path, NULL );
375  g_free ( dirname );
376  }
377  else {
378  full_path = g_strdup ( path );
379  }
380  glob_t globbuf = { .gl_pathc = 0, .gl_pathv = NULL, .gl_offs = 0 };
381 
382  if ( glob ( full_path, 0, NULL, &globbuf ) == 0 ) {
383  for ( size_t iter = 0; iter < globbuf.gl_pathc; iter++ ) {
384  parse_ssh_config_file ( pd, globbuf.gl_pathv[iter], retv, length, num_favorites );
385  }
386  }
387  globfree ( &globbuf );
388 
389  g_free ( full_path );
390  g_free ( path );
391  }
392  else if ( g_strcmp0 ( low_token, "userknownhostsfile" ) == 0 ) {
393  while ( ( token = strtok_r ( NULL, SSH_TOKEN_DELIM, &strtok_pointer ) ) ) {
394  g_debug("Found extra UserKnownHostsFile: %s", token);
395  add_known_hosts_file ( pd, token );
396  }
397  }
398  else if ( g_strcmp0 ( low_token, "host" ) == 0 ) {
399  // Now we know that this is a "Host" line.
400  // The "Host" keyword is followed by one more host names separated
401  // by whitespace; while host names may be quoted with double quotes
402  // to represent host names containing spaces, we don't support this
403  // (how many host names contain spaces?).
404  while ( ( token = strtok_r ( NULL, SSH_TOKEN_DELIM, &strtok_pointer ) ) ) {
405  // We do not want to show wildcard entries, as you cannot ssh to them.
406  const char *const sep = "*?";
407  if ( *token == '!' || strpbrk ( token, sep ) ) {
408  continue;
409  }
410 
411  // If comment, skip from now on.
412  if ( *token == '#' ) {
413  break;
414  }
415 
416  // Is this host name already in the history file?
417  // This is a nice little penalty, but doable? time will tell.
418  // given num_favorites is max 25.
419  int found = 0;
420  for ( unsigned int j = 0; j < num_favorites; j++ ) {
421  if ( !g_ascii_strcasecmp ( token, ( *retv )[j].hostname ) ) {
422  found = 1;
423  break;
424  }
425  }
426 
427  if ( found ) {
428  continue;
429  }
430 
431  // Add this host name to the list.
432  ( *retv ) = g_realloc ( ( *retv ), ( ( *length ) + 2 ) * sizeof ( SshEntry ) );
433  ( *retv )[( *length )].hostname = g_strdup ( token );
434  ( *retv )[( *length )].port = 0;
435  ( *retv )[( *length ) + 1].hostname = NULL;
436  ( *length )++;
437  }
438  }
439  g_free ( low_token );
440  }
441  if ( buffer != NULL ) {
442  free ( buffer );
443  }
444 
445  if ( fclose ( fd ) != 0 ) {
446  g_warning ( "Failed to close ssh configuration file: '%s'", g_strerror ( errno ) );
447  }
448  }
449 }
450 
458 static SshEntry * get_ssh ( SSHModePrivateData *pd, unsigned int *length )
459 {
460  SshEntry *retv = NULL;
461  unsigned int num_favorites = 0;
462  char *path;
463 
464  if ( g_get_home_dir () == NULL ) {
465  return NULL;
466  }
467 
468  path = g_build_filename ( cache_dir, SSH_CACHE_FILE, NULL );
469  char **h = history_get_list ( path, length );
470 
471  retv = malloc ( (*length)*sizeof(SshEntry));
472  for ( unsigned int i = 0; i < (*length); i++ ){
473  int port = 0;
474  char *portstr = strchr ( h[i], '\x1F' );
475  if ( portstr != NULL ) {
476  *portstr = '\0';
477  errno = 0;
478  gchar *endptr= NULL;
479  gint64 number = g_ascii_strtoll ( &(portstr[1]), &endptr, 10);
480  if ( errno != 0 ) {
481  g_warning ( "Failed to parse port number: %s.", &(portstr[1]) );
482  } else if ( endptr == &(portstr[1])) {
483  g_warning ( "Failed to parse port number: %s, invalid number.", &(portstr[1]) );
484  } else if ( number < 0 || number > 65535 ) {
485  g_warning ( "Failed to parse port number: %s, out of range.", &(portstr[1]) );
486  } else {
487  port = number;
488  }
489  }
490  retv[i].hostname = h[i];
491  retv[i].port = port;
492  }
493  g_free (h);
494 
495  g_free ( path );
496  num_favorites = ( *length );
497 
498  const char *hd = g_get_home_dir ();
499  path = g_build_filename ( hd, ".ssh", "config", NULL );
500  parse_ssh_config_file ( pd, path, &retv, length, num_favorites );
501 
502  if ( config.parse_known_hosts == TRUE ) {
503  char *path = g_build_filename ( g_get_home_dir (), ".ssh", "known_hosts", NULL );
504  retv = read_known_hosts_file ( path, retv, length );
505  g_free ( path );
506  for ( GList *iter = g_list_first ( pd->user_known_hosts); iter; iter = g_list_next ( iter ) ) {
507  char *path = rofi_expand_path ( (const char *)iter->data);
508  retv = read_known_hosts_file ( (const char*)path, retv, length );
509  g_free (path);
510  }
511  }
512  if ( config.parse_hosts == TRUE ) {
513  retv = read_hosts_file ( retv, length );
514  }
515 
516 
517  g_free ( path );
518 
519  return retv;
520 }
521 
522 
529 static int ssh_mode_init ( Mode *sw )
530 {
531  if ( mode_get_private_data ( sw ) == NULL ) {
532  SSHModePrivateData *pd = g_malloc0 ( sizeof ( *pd ) );
533  mode_set_private_data ( sw, (void *) pd );
534  pd->hosts_list = get_ssh ( pd, &( pd->hosts_list_length ) );
535  }
536  return TRUE;
537 }
538 
546 static unsigned int ssh_mode_get_num_entries ( const Mode *sw )
547 {
548  const SSHModePrivateData *rmpd = (const SSHModePrivateData *) mode_get_private_data ( sw );
549  return rmpd->hosts_list_length;
550 }
556 static void ssh_mode_destroy ( Mode *sw )
557 {
559  if ( rmpd != NULL ) {
560  for ( unsigned int i = 0; i < rmpd->hosts_list_length; i++ ){
561  g_free( rmpd->hosts_list[i].hostname );
562  }
563  g_list_free_full ( rmpd->user_known_hosts, g_free );
564  g_free ( rmpd->hosts_list );
565  g_free ( rmpd );
566  mode_set_private_data ( sw, NULL );
567  }
568 }
569 
580 static ModeMode ssh_mode_result ( Mode *sw, int mretv, char **input, unsigned int selected_line )
581 {
582  ModeMode retv = MODE_EXIT;
584  if ( mretv & MENU_NEXT ) {
585  retv = NEXT_DIALOG;
586  }
587  else if ( mretv & MENU_PREVIOUS ) {
588  retv = PREVIOUS_DIALOG;
589  }
590  else if ( mretv & MENU_QUICK_SWITCH ) {
591  retv = ( mretv & MENU_LOWER_MASK );
592  }
593  else if ( ( mretv & MENU_OK ) && rmpd->hosts_list[selected_line].hostname != NULL ) {
594  exec_ssh ( &(rmpd->hosts_list[selected_line]) );
595  }
596  else if ( ( mretv & MENU_CUSTOM_INPUT ) && *input != NULL && *input[0] != '\0' ) {
597  SshEntry entry = { .hostname = *input, .port = 0 };
598  exec_ssh ( &entry );
599  }
600  else if ( ( mretv & MENU_ENTRY_DELETE ) && rmpd->hosts_list[selected_line].hostname ) {
601  delete_ssh ( rmpd->hosts_list[selected_line].hostname );
602  // Stay
603  retv = RELOAD_DIALOG;
604  ssh_mode_destroy ( sw );
605  ssh_mode_init ( sw );
606  }
607  return retv;
608 }
609 
622 static char *_get_display_value ( const Mode *sw, unsigned int selected_line, G_GNUC_UNUSED int *state, G_GNUC_UNUSED GList **attr_list, int get_entry )
623 {
625  return get_entry ? g_strdup ( rmpd->hosts_list[selected_line].hostname ) : NULL;
626 }
627 
637 static int ssh_token_match ( const Mode *sw, rofi_int_matcher **tokens, unsigned int index )
638 {
640  return helper_token_match ( tokens, rmpd->hosts_list[index].hostname );
641 }
642 #include "mode-private.h"
644 {
645  .name = "ssh",
646  .cfg_name_key = "display-ssh",
647  ._init = ssh_mode_init,
648  ._get_num_entries = ssh_mode_get_num_entries,
649  ._result = ssh_mode_result,
650  ._destroy = ssh_mode_destroy,
651  ._token_match = ssh_token_match,
652  ._get_display_value = _get_display_value,
653  ._get_completion = NULL,
654  ._preprocess_input = NULL,
655  .private_data = NULL,
656  .free = NULL
657 };
MENU_QUICK_SWITCH
@ MENU_QUICK_SWITCH
Definition: mode.h:79
history.h
cache_dir
const char * cache_dir
Definition: rofi.c:84
ssh_mode_get_num_entries
static unsigned int ssh_mode_get_num_entries(const Mode *sw)
Definition: ssh.c:546
rofi_mode::name
char * name
Definition: mode-private.h:156
settings.h
parse_ssh_config_file
static void parse_ssh_config_file(SSHModePrivateData *pd, const char *filename, SshEntry **retv, unsigned int *length, unsigned int num_favorites)
Definition: ssh.c:345
SshEntry
struct _SshEntry SshEntry
SSHModePrivateData::hosts_list_length
unsigned int hosts_list_length
Definition: ssh.c:73
MENU_OK
@ MENU_OK
Definition: mode.h:69
SSHModePrivateData::user_known_hosts
GList * user_known_hosts
Definition: ssh.c:69
rofi_int_matcher_t
Definition: rofi-types.h:235
read_known_hosts_file
static SshEntry * read_known_hosts_file(const char *path, SshEntry *retv, unsigned int *length)
Definition: ssh.c:174
mode_set_private_data
void mode_set_private_data(Mode *mode, void *pd)
Definition: mode.c:134
RofiHelperExecuteContext
Definition: helper.h:267
PREVIOUS_DIALOG
@ PREVIOUS_DIALOG
Definition: mode.h:58
ssh_mode_destroy
static void ssh_mode_destroy(Mode *sw)
Definition: ssh.c:556
MENU_PREVIOUS
@ MENU_PREVIOUS
Definition: mode.h:81
execshssh
static int execshssh(const SshEntry *entry)
Definition: ssh.c:95
_SshEntry::hostname
char * hostname
Definition: ssh.c:61
NEXT_DIALOG
@ NEXT_DIALOG
Definition: mode.h:54
ssh_mode_init
static int ssh_mode_init(Mode *sw)
Definition: ssh.c:529
exec_ssh
static void exec_ssh(const SshEntry *entry)
Definition: ssh.c:127
helper_parse_setup
int helper_parse_setup(char *string, char ***output, int *length,...)
Definition: helper.c:83
delete_ssh
static void delete_ssh(const char *host)
Definition: ssh.c:156
mode-private.h
helper_execute
gboolean helper_execute(const char *wd, char **args, const char *error_precmd, const char *error_cmd, RofiHelperExecuteContext *context)
Definition: helper.c:964
Settings::parse_hosts
unsigned int parse_hosts
Definition: settings.h:138
MODE_EXIT
@ MODE_EXIT
Definition: mode.h:52
_SshEntry::port
int port
Definition: ssh.c:62
MENU_ENTRY_DELETE
@ MENU_ENTRY_DELETE
Definition: mode.h:77
SSHModePrivateData
Definition: ssh.c:68
_SshEntry
Definition: ssh.c:60
Settings::ssh_command
char * ssh_command
Definition: settings.h:85
rofi_expand_path
char * rofi_expand_path(const char *input)
Definition: helper.c:658
rofi_mode
Definition: mode-private.h:152
rofi.h
ssh_mode
Mode ssh_mode
Definition: ssh.c:643
read_hosts_file
static SshEntry * read_hosts_file(SshEntry *retv, unsigned int *length)
Definition: ssh.c:267
SSHModePrivateData::hosts_list
SshEntry * hosts_list
Definition: ssh.c:71
history_set
void history_set(const char *filename, const char *entry)
Definition: history.c:178
history_get_list
char ** history_get_list(const char *filename, unsigned int *length)
Definition: history.c:325
SSH_TOKEN_DELIM
#define SSH_TOKEN_DELIM
Definition: ssh.c:85
get_ssh
static SshEntry * get_ssh(SSHModePrivateData *pd, unsigned int *length)
Definition: ssh.c:458
MENU_CUSTOM_INPUT
@ MENU_CUSTOM_INPUT
Definition: mode.h:75
Settings::parse_known_hosts
unsigned int parse_known_hosts
Definition: settings.h:140
mode_get_private_data
void * mode_get_private_data(const Mode *mode)
Definition: mode.c:128
MENU_NEXT
@ MENU_NEXT
Definition: mode.h:73
add_known_hosts_file
static void add_known_hosts_file(SSHModePrivateData *pd, const char *token)
Definition: ssh.c:334
ssh_token_match
static int ssh_token_match(const Mode *sw, rofi_int_matcher **tokens, unsigned int index)
Definition: ssh.c:637
helper_token_match
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition: helper.c:445
ModeMode
ModeMode
Definition: mode.h:50
RELOAD_DIALOG
@ RELOAD_DIALOG
Definition: mode.h:56
MENU_LOWER_MASK
@ MENU_LOWER_MASK
Definition: mode.h:85
helper.h
ssh_mode_result
static ModeMode ssh_mode_result(Mode *sw, int mretv, char **input, unsigned int selected_line)
Definition: ssh.c:580
ssh.h
config
Settings config
history_remove
void history_remove(const char *filename, const char *entry)
Definition: history.c:259
RofiHelperExecuteContext::name
const gchar * name
Definition: helper.h:269
_get_display_value
static char * _get_display_value(const Mode *sw, unsigned int selected_line, G_GNUC_UNUSED int *state, G_GNUC_UNUSED GList **attr_list, int get_entry)
Definition: ssh.c:622
SSH_CACHE_FILE
#define SSH_CACHE_FILE
Definition: ssh.c:79