1 #include <nm_core.h>
2 #include <nm_main_loop.h>
3 #include <nm_menu.h>
4 #include <nm_form.h>
5 #include <nm_utils.h>
6 #include <nm_string.h>
7 #include <nm_window.h>
8 #include <nm_add_vm.h>
9 #include <nm_viewer.h>
10 #include <nm_machine.h>
11 #include <nm_rename_vm.h>
12 #include <nm_edit_vm.h>
13 #include <nm_clone_vm.h>
14 #include <nm_database.h>
15 #include <nm_cfg_file.h>
16 #include <nm_edit_net.h>
17 #include <nm_9p_share.h>
18 #include <nm_usb_plug.h>
19 #include <nm_add_drive.h>
20 #include <nm_edit_boot.h>
21 #include <nm_ovf_import.h>
22 #include <nm_vm_control.h>
23 #include <nm_mon_daemon.h>
24 #include <nm_easter_egg.h>
25 #include <nm_vm_snapshot.h>
26 #include <nm_qmp_control.h>
27 #include <nm_lan_settings.h>
28 
29 static const char NM_SEARCH_STR[] = "Search:";
30 
31 nm_filter_t nm_filter;
32 
33 static size_t nm_search_vm(const nm_vect_t *list, int *err);
34 static int nm_filter_check(const nm_str_t *input);
35 static int nm_search_cmp_cb(const void *s1, const void *s2);
36 
nm_filter_clean()37 static inline void nm_filter_clean()
38 {
39     nm_str_free(&nm_filter.query);
40     nm_filter.type = NM_FILTER_NONE;
41     nm_filter.flags = 0;
42 }
43 
nm_start_main_loop(void)44 void nm_start_main_loop(void)
45 {
46     int nemu = 0, regen_data = 1;
47     int clear_action = 1;
48     size_t vm_list_len, old_hl = 0;
49     nm_menu_data_t vms = NM_INIT_MENU_DATA;
50     nm_vmctl_data_t vm_props = NM_VMCTL_INIT_DATA;
51     nm_vect_t vms_v = NM_INIT_VECT;
52     nm_vect_t vm_list = NM_INIT_VECT;
53     const nm_cfg_t *cfg = nm_cfg_get();
54 
55     nm_filter = NM_INIT_FILTER;
56     init_pair(NM_COLOR_BLACK, COLOR_BLACK, COLOR_WHITE);
57     init_pair(NM_COLOR_RED, COLOR_RED, COLOR_WHITE);
58     if (cfg->hl_is_set && can_change_color()) {
59         init_color(COLOR_WHITE + 1,
60                 cfg->hl_color.r, cfg->hl_color.g, cfg->hl_color.b);
61         init_pair(NM_COLOR_HIGHLIGHT, COLOR_WHITE + 1, -1);
62     } else {
63         init_pair(NM_COLOR_HIGHLIGHT, COLOR_GREEN, -1);
64     }
65 
66     nm_create_windows();
67     nm_init_help_main();
68     nm_init_side();
69     nm_init_action(NULL);
70 
71     for (;;) {
72         int ch;
73 
74         if (nm_filter.flags & NM_FILTER_UPDATE) {
75             regen_data = 1;
76             werase(side_window);
77             werase(action_window);
78             nm_init_side();
79             nm_init_action(NULL);
80             nm_filter.flags &= ~NM_FILTER_UPDATE;
81         }
82 
83         if (regen_data) {
84             nm_str_t query = NM_INIT_STR;
85 
86             nm_vect_free(&vm_list, nm_str_vect_free_cb);
87             nm_vect_free(&vms_v, NULL);
88 
89             switch (nm_filter.type) {
90             case NM_FILTER_NONE:
91                 nm_str_format(&query, "%s", NM_GET_VMS_SQL);
92                 break;
93             case NM_FILTER_GROUP:
94                 nm_str_format(&query, NM_GET_VMS_FILTER_GROUP_SQL, nm_filter.query.data);
95                 break;
96             }
97 
98             nm_db_select(query.data, &vm_list);
99             nm_str_free(&query);
100 
101             vm_list_len = (getmaxy(side_window) - 4);
102 
103             vms.highlight = 1;
104 
105             if (old_hl > 1) {
106                 if (vm_list.n_memb < old_hl)
107                     vms.highlight = (old_hl - 1);
108                 else
109                     vms.highlight = old_hl;
110                 old_hl = 0;
111             }
112 
113             if (vm_list_len < vm_list.n_memb)
114                 vms.item_last = vm_list_len;
115             else
116                 vms.item_last = vm_list_len = vm_list.n_memb;
117 
118             for (size_t n = 0; n < vm_list.n_memb; n++) {
119                 nm_menu_item_t vm = NM_INIT_MENU_ITEM;
120                 vm.name = (nm_str_t *) nm_vect_at(&vm_list, n);
121                 nm_vect_insert(&vms_v, &vm, sizeof(vm), NULL);
122             }
123 
124             vms.v = &vms_v;
125 
126             regen_data = 0;
127         }
128 
129         if (vm_list.n_memb > 0) {
130             const nm_str_t *name = nm_vect_item_name_cur(&vms);
131             int status = nm_vect_item_status_cur(&vms);
132 
133             if (clear_action) {
134                 nm_vmctl_free_data(&vm_props);
135                 nm_vmctl_get_data(name, &vm_props);
136                 werase(action_window);
137                 nm_init_action(NULL);
138                 clear_action = 0;
139             }
140 
141             nm_print_vm_menu(&vms);
142             nm_print_vm_info(name, &vm_props, status);
143             wrefresh(side_window);
144             wrefresh(action_window);
145         } else if ((!vm_list.n_memb) && (nm_filter.type == NM_FILTER_GROUP)) {
146                 nm_filter_clean();
147                 regen_data = 1;
148                 nm_warn(_(NM_MSG_NO_GROUP));
149                 werase(side_window);
150                 werase(action_window);
151                 nm_init_side();
152                 nm_init_action(NULL);
153                 continue;
154         } else {
155             if (clear_action) {
156                 werase(action_window);
157                 nm_init_action(NULL);
158                 clear_action = 0;
159             }
160         }
161 
162         ch = wgetch(side_window);
163 
164         /* Clear action window only if key pressed.
165          * Otherwise text will be flicker in tty. */
166         if (ch != ERR)
167             clear_action = 1;
168 
169         if (vm_list.n_memb > 0)
170             nm_menu_scroll(&vms, vm_list_len, ch);
171 
172         if (ch == NM_KEY_Q) {
173             nm_destroy_windows();
174             nm_curses_deinit();
175             nm_db_close();
176             nm_cfg_free();
177             nm_mach_free();
178             break;
179         }
180 
181         if (vm_list.n_memb > 0) {
182             const nm_str_t *name = nm_vect_item_name_cur(&vms);
183             int vm_status = nm_vect_item_status_cur(&vms);
184 
185             switch (ch) {
186             case NM_KEY_R:
187                 if (vm_status) {
188                     nm_warn(_(NM_MSG_RUNNING));
189                     break;
190                 }
191                 nm_vmctl_start(name, 0);
192                 break;
193 
194             case NM_KEY_T:
195                 if (vm_status) {
196                     nm_warn(_(NM_MSG_RUNNING));
197                     break;
198                 }
199                 nm_vmctl_start(name, NM_VMCTL_TEMP);
200                 break;
201 
202             case NM_KEY_P:
203                 if (vm_status)
204                     nm_qmp_vm_shut(name);
205                 break;
206 
207             case NM_KEY_F:
208                 if (vm_status)
209                     nm_qmp_vm_stop(name);
210                 break;
211 
212             case NM_KEY_Z:
213                 if (vm_status)
214                     nm_qmp_vm_reset(name);
215                 break;
216 
217             case NM_KEY_P_UP:
218                 if (vm_status)
219                     nm_qmp_vm_pause(name);
220                 break;
221 
222             case NM_KEY_R_UP:
223                 if (vm_status)
224                     nm_qmp_vm_resume(name);
225                 break;
226 
227             case NM_KEY_K:
228                 if (vm_status)
229                     nm_vmctl_kill(name);
230                 break;
231 
232 #if defined(NM_WITH_VNC_CLIENT) || defined(NM_WITH_SPICE)
233             case NM_KEY_C:
234                 if (vm_status)
235                     nm_vmctl_connect(name);
236                 break;
237 #endif
238 
239             case NM_KEY_M:
240                 nm_print_cmd(name);
241                 nm_init_side();
242                 nm_init_help_main();
243                 break;
244 
245             case NM_KEY_A:
246                 nm_add_drive(name);
247                 break;
248 
249             case NM_KEY_V:
250                 if (vm_status) {
251                     nm_warn(_(NM_MSG_MUST_STOP));
252                     break;
253                 }
254                 nm_del_drive(name);
255                 nm_init_side();
256                 break;
257 
258 #if defined (NM_OS_LINUX)
259             case NM_KEY_PLUS:
260                 nm_usb_plug(name, vm_status);
261                 break;
262 
263             case NM_KEY_MINUS:
264                 nm_usb_unplug(name, vm_status);
265                 break;
266 
267             case NM_KEY_H:
268                 nm_9p_share(name);
269                 break;
270 #endif /* NM_OS_LINUX */
271 
272             case NM_KEY_Y:
273                 if (vm_status) {
274                     nm_warn(_(NM_MSG_MUST_STOP));
275                     break;
276                 }
277                 nm_rename_vm(name);
278                 regen_data = 1;
279                 old_hl = vms.highlight;
280                 nm_mon_ping();
281                 break;
282 
283             case NM_KEY_E:
284                 nm_edit_vm(name);
285                 break;
286 
287             case NM_KEY_B:
288                 nm_edit_boot(name);
289                 break;
290 
291             case NM_KEY_C_UP:
292                 nm_viewer(name);
293                 break;
294 
295             case NM_KEY_S_UP:
296                 if (access(cfg->daemon_pid.data, R_OK) == -1) {
297                     nm_warn(_(NM_MSG_NO_DAEMON));
298                     break;
299                 }
300                 if (!vm_status) {
301                     nm_warn(NM_MSG_MUST_RUN);
302                     break;
303                 }
304                 if (nm_usb_check_plugged(name) != NM_OK) {
305                     nm_warn(NM_MSG_SNAP_USB);
306                     break;
307                 }
308                 nm_vm_snapshot_create(name);
309                 break;
310 
311             case NM_KEY_X_UP:
312                 if (access(cfg->daemon_pid.data, R_OK) == -1) {
313                     nm_warn(_(NM_MSG_NO_DAEMON));
314                     break;
315                 }
316                 nm_vm_snapshot_load(name, vm_status);
317                 break;
318 
319             case NM_KEY_D_UP:
320                 if (access(cfg->daemon_pid.data, R_OK) == -1) {
321                     nm_warn(_(NM_MSG_NO_DAEMON));
322                     break;
323                 }
324                 nm_vm_snapshot_delete(name, vm_status);
325                 break;
326 
327             case NM_KEY_L:
328                 if (vm_status) {
329                     nm_warn(_(NM_MSG_MUST_STOP));
330                     break;
331                 }
332 
333                 nm_clone_vm(name);
334                 regen_data = 1;
335                 old_hl = vms.highlight;
336                 nm_mon_ping();
337                 break;
338 
339             case NM_KEY_D:
340                 if (vm_status) {
341                     nm_warn(_(NM_MSG_MUST_STOP));
342                     break;
343                 }
344                 {
345                     int ans = nm_notify(_(NM_MSG_DELETE));
346                     if (ans == 'y') {
347                         nm_vmctl_delete(name);
348                         regen_data = 1;
349                         old_hl = vms.highlight;
350                         if (vms.item_first != 0) {
351                             vms.item_first--;
352                             vms.item_last--;
353                         }
354                         nm_mon_ping();
355                     }
356                     werase(side_window);
357                     werase(action_window);
358                     nm_init_side();
359                     nm_init_action(NULL);
360                 }
361                 break;
362 
363             case NM_KEY_I:
364                 nm_edit_net(name);
365                 nm_init_side();
366                 break;
367 
368 #if defined (NM_OS_LINUX)
369             case NM_KEY_N_UP:
370                 nm_lan_settings();
371                 nm_init_side();
372                 break;
373 #endif
374             }
375         }
376 
377         if (ch == NM_KEY_SLASH) {
378             int err = NM_FALSE;
379             size_t pos = nm_search_vm(&vm_list, &err);
380             int cols = getmaxx(side_window);
381 
382             if (err == NM_TRUE) {
383                 nm_warn(_(NM_MSG_SMALL_WIN));
384                 break;
385             }
386 
387             if (pos > vm_list_len) {
388                 vms.highlight = vm_list_len;
389                 vms.item_first = pos - vm_list_len;
390                 vms.item_last = pos;
391             } else if (pos != 0) {
392                 vms.item_first = 0;
393                 vms.item_last = vm_list_len;
394                 vms.highlight = pos;
395             }
396 
397             if (nm_filter.flags & NM_FILTER_UPDATE) {
398                 regen_data = 1;
399                 werase(side_window);
400                 werase(action_window);
401                 nm_init_action(NULL);
402                 nm_filter.flags &= ~NM_FILTER_UPDATE;
403             } else if (nm_filter.flags & NM_FILTER_RESET) {
404                 nm_filter_clean();
405                 regen_data = 1;
406                 werase(side_window);
407                 werase(action_window);
408                 nm_init_action(NULL);
409                 nm_filter.flags &= ~NM_FILTER_RESET;
410             }
411 
412             NM_ERASE_TITLE(side, cols);
413             nm_init_side();
414         }
415 
416         if (ch == NM_KEY_I_UP) {
417             nm_add_vm();
418             regen_data = 1;
419             old_hl = vms.highlight;
420             nm_mon_ping();
421         }
422 
423         if (ch == NM_KEY_A_UP) {
424             nm_import_vm();
425             regen_data = 1;
426             old_hl = vms.highlight;
427             nm_mon_ping();
428         }
429 
430         if (ch == NM_KEY_U) {
431             nm_vmctl_clear_all_tap();
432         }
433 #if defined (NM_WITH_OVF_SUPPORT)
434         if (ch == NM_KEY_O_UP) {
435             nm_ovf_import();
436             regen_data = 1;
437             old_hl = vms.highlight;
438             nm_mon_ping();
439         }
440 #endif
441 
442         if (ch == NM_KEY_QUESTION) {
443             nm_print_help();
444         }
445 
446         if (ch == KEY_LEFT) {
447             if (nm_window_scale_inc() == NM_OK)
448                 redraw_window = 1;
449         }
450 
451         if (ch == KEY_RIGHT) {
452             if (nm_window_scale_dec() == NM_OK)
453                 redraw_window = 1;
454         }
455 
456         if (ch == 0x6e || ch == 0x45 || ch == 0x4d || ch == 0x55) {
457             if (ch == 0x6e && !nemu)
458                  nemu++;
459             if (ch == 0x45 && nemu == 1)
460                  nemu++;
461             if (ch == 0x4d && nemu == 2)
462                  nemu++;
463             if (ch == 0x55 && nemu == 3) {
464                 werase(action_window);
465                 nm_init_action("Nemu Kurotsuchi");
466                 nm_print_nemu();
467                 nemu = 0;
468             }
469         }
470 
471         if (redraw_window) {
472             nm_destroy_windows();
473             endwin();
474             refresh();
475             nm_create_windows();
476             nm_init_help_main();
477             nm_init_side();
478             nm_init_action(NULL);
479 
480             vm_list_len = (getmaxy(side_window) - 4);
481             /* TODO save last pos */
482             if (vm_list_len < vm_list.n_memb) {
483                 vms.item_last = vm_list_len;
484                 vms.item_first = 0;
485                 vms.highlight = 1;
486             }
487             else
488                 vms.item_last = vm_list_len = vm_list.n_memb;
489 
490             redraw_window = 0;
491         }
492     }
493 
494     nm_filter_clean();
495     nm_vmctl_free_data(&vm_props);
496     nm_vect_free(&vms_v, NULL);
497     nm_vect_free(&vm_list, nm_str_vect_free_cb);
498 }
499 
nm_search_init_windows(nm_form_t * form)500 static void nm_search_init_windows(nm_form_t *form)
501 {
502     if (form) {
503         nm_form_window_init();
504         nm_form_data_t *form_data = (nm_form_data_t *)form_userptr(form);
505         if (form_data)
506             form_data->parent_window = side_window;
507     }
508 
509     nm_init_side();
510     nm_init_action(NULL);
511     nm_init_help_main();
512 
513     nm_print_vm_menu(NULL);
514     nm_print_vm_info(NULL, NULL, 0);
515     wrefresh(action_window);
516     wrefresh(side_window);
517 }
518 
nm_search_vm(const nm_vect_t * list,int * err)519 static size_t nm_search_vm(const nm_vect_t *list, int *err)
520 {
521     if (!err)
522         return 0;
523 
524     nm_form_data_t *form_data = NULL;
525     nm_form_t *form = NULL;
526     nm_field_t *fields[3] = {NULL};
527     size_t pos = 0;
528     void *match = NULL;
529     nm_str_t input = NM_INIT_STR;
530     size_t msg_len = mbstowcs(NULL, _(NM_SEARCH_STR), strlen(NM_SEARCH_STR));
531 
532     nm_search_init_windows(NULL);
533 
534     wattroff(side_window, COLOR_PAIR(NM_COLOR_HIGHLIGHT));
535     NM_ERASE_TITLE(side, getmaxx(side_window));
536     wrefresh(side_window);
537 
538     form_data = nm_form_data_new(
539         side_window, nm_search_init_windows, msg_len, 1, NM_FALSE);
540 
541     form_data->field_vpad = 0;
542     form_data->field_hpad = 1;
543     form_data->form_ratio = 0.99;
544     form_data->form_vpad = 0;
545     form_data->w_start_y = 1;
546 
547     if (nm_form_data_update(form_data, 0, 0) != NM_OK)
548         goto out;
549 
550     fields[0] = nm_field_label_new(0, form_data);
551     fields[1] = nm_field_default_new(0, form_data);
552     fields[2] = NULL;
553 
554     set_field_back(fields[1], A_UNDERLINE);
555     field_opts_off(fields[1], O_STATIC);
556     set_field_buffer(fields[0], 0, _(NM_SEARCH_STR));
557     nm_fields_unset_status(fields);
558 
559     form = nm_form_new(form_data, fields);
560     nm_form_post(form);
561 
562     if (nm_form_draw(&form) != NM_OK)
563         goto out;
564 
565     nm_get_field_buf(fields[1], &input);
566     if (!input.len)
567         goto out;
568 
569     if (nm_filter_check(&input) == NM_OK)
570         goto out;
571 
572     match = bsearch(&input, list->data, list->n_memb, sizeof(void *), nm_search_cmp_cb);
573 
574     if (match != NULL) {
575         pos = (((unsigned char *)match - (unsigned char *)list->data) / sizeof(void *));
576         pos++;
577     }
578 
579     /* An incomplete match can happen not at the
580      * beginning of the list with the same prefixes
581      * in nm_search_cmp_cb.
582      * So use backward linear search to fix it */
583     if (pos <= 1)
584         goto out;
585 
586     for (uint32_t n = pos - 1; n != 0; n--) {
587         char *fo = strstr(nm_vect_str_ctx(list, n - 1), input.data);
588 
589         if (fo != NULL && fo == nm_vect_str_ctx(list, n - 1))
590             pos--;
591         else
592             break;
593     }
594 
595 out:
596     nm_form_free(form);
597     nm_form_data_free(form_data);
598     nm_fields_free(fields);
599     nm_str_free(&input);
600 
601     return pos;
602 }
603 
nm_filter_check(const nm_str_t * input)604 static int nm_filter_check(const nm_str_t *input)
605 {
606     if (input->len < 2)
607         return NM_ERR;
608 
609     if (input->data[0] != 'g' && input->data[1] != ':')
610         return NM_ERR;
611 
612     if (input->len == 2) {
613         nm_filter.flags |= NM_FILTER_RESET;
614         return NM_OK;
615     }
616 
617     nm_str_format(&nm_filter.query, "%s", input->data + 2);
618     nm_filter.type = NM_FILTER_GROUP;
619     nm_filter.flags |= NM_FILTER_UPDATE;
620 
621     return NM_OK;
622 }
623 
nm_search_cmp_cb(const void * s1,const void * s2)624 static int nm_search_cmp_cb(const void *s1, const void *s2)
625 {
626     int rc;
627     const nm_str_t *str1 = s1;
628     const nm_str_t **str2 = (const nm_str_t **) s2;
629 
630     rc = strcmp(str1->data, (*str2)->data);
631 
632     if (rc != 0) {
633         char *fo = strstr((*str2)->data, str1->data);
634         if (fo != NULL && fo == (*str2)->data)
635             rc = 0;
636     }
637 
638     return rc;
639 }
640 /* vim:set ts=4 sw=4: */
641