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