20 from .gui_server
import start_qml_gui
31 from threading
import Thread, Lock
40 locale.setlocale(locale.LC_ALL,
"")
41 preferred_encoding = locale.getpreferredencoding()
52 log_line_lr_scroll = 0
53 longest_visible_line = 0
66 default_log_filters = [
"mouth.viseme",
"mouth.display",
"mouth.icon",
"DEBUG"]
67 log_filters = list(default_log_filters)
83 screen_mode = SCR_MAIN
86 FULL_REDRAW_FREQUENCY = 10
87 last_full_redraw = time.time()-(FULL_REDRAW_FREQUENCY-1)
89 is_screen_dirty =
True 102 CLR_LOG_CMDMESSAGE = 0
107 ctrl_c_was_pressed =
False 111 global ctrl_c_was_pressed
112 ctrl_c_was_pressed =
True 116 global ctrl_c_was_pressed
117 if ctrl_c_was_pressed:
118 ctrl_c_was_pressed =
False 124 signal.signal(signal.SIGINT, ctrl_c_handler)
131 """ Force n to be between smallest and largest, inclusive """ 132 return max(smallest, min(n, largest))
137 If default locale supports UTF-8 reencode the string otherwise 138 remove the offending characters. 140 if preferred_encoding ==
'ASCII':
141 return ''.join([i
if ord(i) < 128
else ' ' for i
in text])
143 return text.encode(preferred_encoding)
149 config_file = os.path.join(os.path.expanduser(
"~"),
".mycroft_cli.conf")
153 """ Load the mycroft config and connect it to updates over the messagebus. 155 Configuration.init(bus)
156 return Configuration.get()
160 """ Connect to the mycroft messagebus and load and register config 163 Sets the bus and config global variables 179 with io.open(config_file,
'r') as f: 180 config = json.load(f) 181 if "filters" in config:
182 log_filters = config[
"filters"]
183 if "cy_chat_area" in config:
184 cy_chat_area = config[
"cy_chat_area"]
185 if "show_last_key" in config:
186 show_last_key = config[
"show_last_key"]
187 if "max_log_lines" in config:
188 max_log_lines = config[
"max_log_lines"]
189 if "show_meter" in config:
190 show_meter = config[
"show_meter"]
191 except Exception
as e:
192 LOG.info(
"Ignoring failed load of settings file")
197 config[
"filters"] = log_filters
198 config[
"cy_chat_area"] = cy_chat_area
199 config[
"show_last_key"] = show_last_key
200 config[
"max_log_lines"] = max_log_lines
201 config[
"show_meter"] = show_meter
202 with io.open(config_file,
'w')
as f:
203 f.write(str(json.dumps(config, ensure_ascii=
False)))
212 Thread.__init__(self)
216 log_files.append(filename)
224 if not st_results.st_mtime == self.st_results.st_mtime:
239 global log_line_offset
252 if find_str
not in line:
255 for filtered_text
in log_filters:
256 if filtered_text
in line:
262 mergedLog.append(self.
logid + line.rstrip())
267 filteredLog.append(self.
logid + line.rstrip())
268 mergedLog.append(self.
logid + line.rstrip())
273 if len(mergedLog) >= max_log_lines:
275 cToDel = len(mergedLog) - max_log_lines
276 if len(filteredLog) == len(mergedLog):
277 del filteredLog[:cToDel]
278 del mergedLog[:cToDel]
281 if len(filteredLog) != len(mergedLog):
286 if os.path.isfile(filename):
288 thread.setDaemon(
True)
294 Thread.__init__(self)
304 not st_results.st_ctime == self.st_results.st_ctime
or 305 not st_results.st_mtime == self.st_results.st_mtime):
318 with io.open(self.
filename,
'r') as fh: 322 parts = line.split(
"=")
323 meter_thresh = float(parts[-1])
324 meter_cur = float(parts[-2].split(
" ")[0])
329 Thread.__init__(self)
334 global is_screen_dirty
343 is_screen_dirty =
False 345 if screen_mode == SCR_MAIN:
348 elif screen_mode == SCR_HELP:
356 if os.path.isfile(filename):
358 thread.setDaemon(
True)
363 """ Show a message for the user (mixed in the logs) """ 366 global log_line_offset
370 message =
"@" + message
371 filteredLog.append(message)
372 mergedLog.append(message)
374 if log_line_offset != 0:
382 global log_line_offset
398 for line
in mergedLog:
402 if find_str
and find_str !=
"":
404 if find_str
not in line:
408 for filtered_text
in log_filters:
409 if filtered_text
and filtered_text
in line:
414 filteredLog.append(line)
422 utterance = event.data.get(
'utterance')
423 utterance = TTS.remove_ssml(utterance)
425 print(
">> " + utterance)
427 chat.append(
">> " + utterance)
434 utterance = event.data.get(
'utterances')[0]
435 history.append(utterance)
436 chat.append(utterance)
441 """ Run the mycroft messagebus referenced by bus. 444 bus: Mycroft messagebus instance 460 def draw(x, y, msg, pad=None, pad_chr=None, clr=None):
461 """Draw a text to the screen 464 x (int): X coordinate (col), 0-based from upper-left 465 y (int): Y coordinate (row), 0-based from upper-left 466 msg (str): string to render to screen 467 pad (bool or int, optional): if int, pads/clips to given length, if 468 True use right edge of the screen. 469 pad_chr (char, optional): pad character, default is space 470 clr (int, optional): curses color, Defaults to CLR_LOG1. 472 if y < 0
or y > curses.LINES
or x < 0
or x > curses.COLS:
475 if x + len(msg) > curses.COLS:
476 s = msg[:curses.COLS-x]
483 s += ch * (pad-x-len(msg))
486 if x+pad > curses.COLS:
488 s += ch * (pad-len(msg))
493 scr.addstr(y, x, s, clr)
504 global CLR_CHAT_QUERY
511 global CLR_LOG_CMDMESSAGE
515 if curses.has_colors():
516 curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
517 bg = curses.COLOR_BLACK
518 for i
in range(1, curses.COLORS):
519 curses.init_pair(i + 1, i, bg)
526 CLR_HEADING = curses.color_pair(1)
527 CLR_CHAT_RESP = curses.color_pair(4)
528 CLR_CHAT_QUERY = curses.color_pair(7)
529 CLR_FIND = curses.color_pair(4)
530 CLR_CMDLINE = curses.color_pair(7)
531 CLR_INPUT = curses.color_pair(7)
532 CLR_LOG1 = curses.color_pair(3)
533 CLR_LOG2 = curses.color_pair(6)
534 CLR_LOG_DEBUG = curses.color_pair(4)
535 CLR_LOG_ERROR = curses.color_pair(2)
536 CLR_LOG_CMDMESSAGE = curses.color_pair(2)
537 CLR_METER_CUR = curses.color_pair(2)
538 CLR_METER = curses.color_pair(4)
542 global log_line_offset
546 num_lines = size_log_area // 2
550 log_line_offset -= num_lines
552 log_line_offset += num_lines
553 if log_line_offset > len(filteredLog):
554 log_line_offset = len(filteredLog) - 10
555 if log_line_offset < 0:
561 if not show_meter
or meter_cur == -1:
577 if meter_cur > meter_peak:
578 meter_peak = meter_cur + 1
581 if meter_peak > meter_thresh * 3:
582 scale = meter_thresh * 3
583 h_cur =
clamp(int((float(meter_cur) / scale) * height), 0, height - 1)
585 int((float(meter_thresh) / scale) * height), 0, height - 1)
586 clr = curses.color_pair(4)
588 str_level =
"{0:3} ".format(int(meter_cur))
589 str_thresh =
"{0:4.2f}".format(meter_thresh)
590 meter_width = len(str_level) + len(str_thresh) + 4
591 for i
in range(0, height):
597 meter =
" " * len(str_level)
610 meter +=
" " * (meter_width - len(meter))
611 scr.addstr(curses.LINES - 1 - i, curses.COLS -
612 len(meter) - 1, meter, clr)
616 if meter_cur > meter_thresh:
617 clr_bar = curses.color_pair(3)
619 clr_bar = curses.color_pair(5)
620 scr.addstr(curses.LINES - 1 - i, curses.COLS - len(str_thresh) - 4,
625 clr = curses.color_pair(2)
626 x = curses.COLS - gui_width
629 cnt = len(gui_text)+1
630 if cnt > curses.LINES-15:
631 cnt = curses.LINES-15
632 for i
in range(0, cnt):
633 draw(x, y+1+i,
" !", clr=CLR_HEADING)
634 if i < len(gui_text):
635 draw(x+2, y+1+i, gui_text[i], pad=gui_width-3)
637 draw(x+2, y+1+i,
"*"*(gui_width-3))
638 draw(x+(gui_width-1), y+1+i,
"!", clr=CLR_HEADING)
639 draw(x, y+cnt,
" "+
"-"*(gui_width-2)+
" ", clr=CLR_HEADING)
643 global is_screen_dirty
647 is_screen_dirty =
True 651 global log_line_offset
652 global longest_visible_line
653 global last_full_redraw
657 if time.time() - last_full_redraw > FULL_REDRAW_FREQUENCY:
662 last_full_redraw = time.time()
667 cLogs = len(filteredLog) + 1
668 size_log_area = curses.LINES - (cy_chat_area + 5)
669 start =
clamp(cLogs - size_log_area, 0, cLogs - 1) - log_line_offset
670 end = cLogs - log_line_offset
677 auto_scroll = (end == cLogs)
680 log_line_offset = cLogs - end
684 scr.addstr(0, 0,
"Search Results: ", CLR_HEADING)
685 scr.addstr(0, 16, find_str, CLR_FIND)
686 scr.addstr(0, 16 + len(find_str),
" ctrl+X to end" +
687 " " * (curses.COLS - 31 - 12 - len(find_str)) +
688 str(start) +
"-" + str(end) +
" of " + str(cLogs),
691 scr.addstr(0, 0,
"Log Output:" +
" " * (curses.COLS - 31) +
692 str(start) +
"-" + str(end) +
" of " + str(cLogs),
694 ver =
" mycroft-core " + mycroft.version.CORE_VERSION_STR +
" ===" 695 scr.addstr(1, 0,
"=" * (curses.COLS-1-len(ver)), CLR_HEADING)
696 scr.addstr(1, curses.COLS-1-len(ver), ver, CLR_HEADING)
700 for i
in range(start, end):
702 log =
' ^--- NEWEST ---^ ' 706 if len(log) > 25
and log[5] ==
'-' and log[8] ==
'-':
712 if " - DEBUG - " in log:
713 log = log.replace(
"Skills ",
"")
715 elif " - ERROR - " in log:
721 clr = CLR_LOG_CMDMESSAGE
727 if len(log) > curses.COLS:
728 start = len_line - (curses.COLS - 4) - log_line_lr_scroll
731 end = start + (curses.COLS - 4)
733 log = log[start:end] +
"~~~~" 734 elif end >= len_line - 1:
735 log =
"~~~~" + log[start:end]
737 log =
"~~" + log[start:end] +
"~~" 738 if len_line > longest_visible_line:
739 longest_visible_line = len_line
744 y_log_legend = curses.LINES - (3 + cy_chat_area)
745 scr.addstr(y_log_legend, curses.COLS // 2 + 2,
748 scr.addstr(y_log_legend + 1, curses.COLS // 2 + 2,
751 if len(log_files) > 0:
752 scr.addstr(y_log_legend + 2, curses.COLS // 2 + 2,
753 os.path.basename(log_files[0]) +
", other",
755 if len(log_files) > 1:
756 scr.addstr(y_log_legend + 3, curses.COLS // 2 + 2,
757 os.path.basename(log_files[1]), CLR_LOG2)
760 y_meter = y_log_legend
762 scr.addstr(y_meter, curses.COLS - 14,
" Mic Level ",
766 y_chat_history = curses.LINES - (3 + cy_chat_area)
767 chat_width = curses.COLS // 2 - 2
769 scr.addstr(y_chat_history, 0,
make_titlebar(
"History", chat_width),
773 idx_chat = len(chat) - 1
774 while len(chat_out) < cy_chat_area
and idx_chat >= 0:
775 if chat[idx_chat][0] ==
'>':
776 wrapper = textwrap.TextWrapper(initial_indent=
"",
777 subsequent_indent=
" ",
780 wrapper = textwrap.TextWrapper(width=chat_width)
782 chatlines = wrapper.wrap(chat[idx_chat])
783 for txt
in reversed(chatlines):
784 if len(chat_out) >= cy_chat_area:
786 chat_out.insert(0, txt)
791 y = curses.LINES - (2 + cy_chat_area)
793 if txt.startswith(
">> ")
or txt.startswith(
" "):
800 if show_gui
and curses.COLS > 20
and curses.LINES > 20:
805 if len(line) > 0
and line[0] ==
":":
806 scr.addstr(curses.LINES - 2, 0,
"Command ('help' for options):",
808 scr.addstr(curses.LINES - 1, 0,
":", CLR_CMDLINE)
811 prompt =
"Input (':' for command, Ctrl+C to quit)" 813 prompt +=
" === keycode: "+last_key
814 scr.addstr(curses.LINES - 2, 0,
818 scr.addstr(curses.LINES - 1, 0,
">", CLR_HEADING)
821 scr.addstr(curses.LINES - 1, 2, ln[-(curses.COLS - 3):], CLR_INPUT)
828 return title +
" " + (
"=" * (bar_length - 1 - len(title)))
836 'Log Scrolling shortcuts',
838 (
"Up / Down / PgUp / PgDn",
"scroll thru history"),
839 (
"Ctrl+T / Ctrl+PgUp",
"scroll to top of logs (jump to oldest)"),
840 (
"Ctrl+B / Ctrl+PgDn",
"scroll to bottom of logs" +
842 (
"Left / Right",
"scroll long lines left/right"),
843 (
"Home / End",
"scroll to start/end of long lines")
847 "Query History shortcuts",
849 (
"Ctrl+N / Ctrl+Right",
"previous query"),
850 (
"Ctrl+P / Ctrl+Left",
"next query")
854 "General Commands (type ':' to enter command mode)",
856 (
":quit or :exit",
"exit the program"),
857 (
":meter (show|hide)",
"display the microphone level"),
858 (
":keycode (show|hide)",
"display typed key codes (mainly debugging)"),
859 (
":history (# lines)",
"set size of visible history buffer"),
860 (
":clear",
"flush the logs")
864 "Log Manipulation Commands",
866 (
":filter 'STR'",
"adds a log filter (optional quotes)"),
867 (
":filter remove 'STR'",
"removes a log filter"),
868 (
":filter (clear|reset)",
"reset filters"),
869 (
":filter (show|list)",
"display current filters"),
870 (
":find 'STR'",
"show logs containing 'str'"),
871 (
":log level (DEBUG|INFO|ERROR)",
"set logging level"),
872 (
":log bus (on|off)",
"control logging of messagebus messages")
876 "Skill Debugging Commands",
878 (
":skills",
"list installed skills"),
879 (
":activate SKILL",
"activate skill, e.g. 'activate skill-wiki'"),
880 (
":deactivate SKILL",
"deactivate skill"),
881 (
":keep SKILL",
"deactivate all skills except " +
882 "the indicated skill")
887 for s
in help_struct:
889 help_longest = max(help_longest, len(ent[0]))
894 for section
in help_struct:
895 lines += 2 + len(section[1])
896 return ceil(lines / (curses.LINES - 4))
902 scr.addstr(0, 0,
center(25) +
"Mycroft Command Line Help", CLR_HEADING)
903 scr.addstr(1, 0,
"=" * (curses.COLS - 1), CLR_HEADING)
905 def render_help(txt, y_pos, i, first_line, last_line, clr):
906 if i >= first_line
and i < last_line:
907 scr.addstr(y_pos, 0, txt, clr)
911 def render_footer(page, total):
912 text =
"Page {} of {} [ Any key to continue ]".format(page, total)
913 scr.addstr(curses.LINES - 1, 0,
center(len(text)) + text, CLR_HEADING)
920 first = subscreen * (curses.LINES - 7)
921 last = first + (curses.LINES - 7)
923 for section
in help_struct:
924 y = render_help(section[0], y, i, first, last, CLR_HEADING)
926 y = render_help(
"=" * (curses.COLS - 1), y, i, first, last,
930 for line
in section[1]:
931 words = line[1].split()
932 ln = line[0].ljust(help_longest + 1)
934 if len(ln) + 1 + len(w) < curses.COLS:
937 y = render_help(ln, y, i, first, last, CLR_CMDLINE)
938 ln =
" ".ljust(help_longest + 2) + w
939 y = render_help(ln, y, i, first, last, CLR_CMDLINE)
942 y = render_help(
" ", y, i, first, last, CLR_CMDLINE)
958 if screen_mode != SCR_HELP:
959 screen_mode = SCR_HELP
968 if screen_mode == SCR_HELP:
971 screen_mode = SCR_MAIN
980 Show list of loaded skills in as many column as necessary 988 screen_mode = SCR_SKILLS
998 scr.addstr(0, 0,
center(25) +
"Loaded skills", CLR_CMDLINE)
999 scr.addstr(1, 1,
"=" * (curses.COLS - 2), CLR_CMDLINE)
1005 skill_names = sorted(skills.keys())
1006 for skill
in skill_names:
1007 if skills[skill][
'active']:
1008 color = curses.color_pair(4)
1010 color = curses.color_pair(2)
1012 scr.addstr(row, column,
" {}".format(skill), color)
1014 col_width = max(col_width, len(skill))
1015 if row == curses.LINES - 2
and column > 0
and skill != skill_names[-1]:
1017 scr.addstr(curses.LINES - 1, 0,
1018 center(23) +
"Press any key to continue", CLR_HEADING)
1022 elif row == curses.LINES - 2:
1026 column += col_width + 2
1028 if column > curses.COLS - 20:
1032 scr.addstr(curses.LINES - 1, 0,
center(23) +
"Press any key to return",
1040 return " " * ((curses.COLS - str_len) // 2)
1050 if isinstance(keyword, list):
1052 cmd = cmd.replace(w,
"").strip()
1054 cmd = cmd.replace(keyword,
"").strip()
1059 if last_char ==
'"' or last_char ==
"'":
1060 parts = cmd.split(last_char)
1063 parts = cmd.split(
" ")
1073 global show_last_key
1075 if "show" in cmd
and "log" in cmd:
1079 elif "exit" in cmd
or "quit" in cmd:
1081 elif "keycode" in cmd:
1083 if "hide" in cmd
or "off" in cmd:
1084 show_last_key =
False 1085 elif "show" in cmd
or "on" in cmd:
1086 show_last_key =
True 1087 elif "meter" in cmd:
1089 if "hide" in cmd
or "off" in cmd:
1091 elif "show" in cmd
or "on" in cmd:
1096 elif "filter" in cmd:
1097 if "show" in cmd
or "list" in cmd:
1102 if "reset" in cmd
or "clear" in cmd:
1103 log_filters = list(default_log_filters)
1108 if "remove" in cmd
and param
in log_filters:
1109 log_filters.remove(param)
1111 log_filters.append(param)
1115 elif "clear" in cmd:
1121 bus.emit(
Message(
"mycroft.debug.log", data={
'level': level}))
1124 if state
in [
"on",
"true",
"yes"]:
1125 bus.emit(
Message(
"mycroft.debug.log", data={
'bus':
True}))
1126 elif state
in [
"off",
"false",
"no"]:
1127 bus.emit(
Message(
"mycroft.debug.log", data={
'bus':
False}))
1128 elif "history" in cmd:
1131 if not lines
or lines < 1:
1133 max_chat_area = curses.LINES - 7
1134 if lines > max_chat_area:
1135 lines = max_chat_area
1136 cy_chat_area = lines
1137 elif "skills" in cmd:
1139 message = bus.wait_for_response(
1140 Message(
'skillmanager.list'), reply_type=
'mycroft.skills.list')
1145 screen_mode = SCR_MAIN
1147 elif "deactivate" in cmd:
1148 skills = cmd.split()[1:]
1151 bus.emit(
Message(
"skillmanager.deactivate", data={
'skill': s}))
1157 bus.emit(
Message(
"skillmanager.keep", data={
'skill': s[1]}))
1161 elif "activate" in cmd:
1162 skills = cmd.split()[1:]
1165 bus.emit(
Message(
"skillmanager.activate", data={
'skill': s}))
1186 global log_line_lr_scroll
1187 global longest_visible_line
1200 bus.on(
'speak', handle_speak)
1201 bus.on(
'message', handle_message)
1202 bus.on(
'recognizer_loop:utterance', handle_utterance)
1203 bus.on(
'connected', handle_is_connected)
1204 bus.on(
'reconnecting', handle_reconnecting)
1209 gui_thread.setDaemon(
True)
1231 except curses.error:
1236 if isinstance(c, int):
1253 if time.time()-start > 1:
1259 if time.time()-start > 1:
1262 if c1 == 79
and c2 == 120:
1264 elif c1 == 79
and c2 == 116:
1266 elif c1 == 79
and c2 == 114:
1268 elif c1 == 79
and c2 == 118:
1269 c = curses.KEY_RIGHT
1270 elif c1 == 79
and c2 == 121:
1271 c = curses.KEY_PPAGE
1272 elif c1 == 79
and c2 == 115:
1273 c = curses.KEY_NPAGE
1274 elif c1 == 79
and c2 == 119:
1276 elif c1 == 79
and c2 == 113:
1282 last_key = str(c) +
",ESC+" + str(c1) +
"+" + str(c2)
1288 last_key = str(code)
1290 last_key = str(code)
1296 elif c == curses.KEY_RESIZE:
1298 y, x = scr.getmaxyx()
1299 curses.resizeterm(y, x)
1304 elif screen_mode == SCR_HELP:
1308 elif c ==
'\n' or code == 10
or code == 13
or code == 343:
1319 bus.emit(
Message(
"recognizer_loop:utterance",
1320 {
'utterances': [line.strip()],
1321 'lang': config.get(
'lang',
'en-us')}))
1324 elif code == 16
or code == 545:
1326 hist_idx =
clamp(hist_idx + 1, -1, len(history) - 1)
1328 line = history[len(history) - hist_idx - 1]
1331 elif code == 14
or code == 560:
1333 hist_idx =
clamp(hist_idx - 1, -1, len(history) - 1)
1335 line = history[len(history) - hist_idx - 1]
1338 elif c == curses.KEY_LEFT:
1340 log_line_lr_scroll += curses.COLS // 4
1341 elif c == curses.KEY_RIGHT:
1343 log_line_lr_scroll -= curses.COLS // 4
1344 if log_line_lr_scroll < 0:
1345 log_line_lr_scroll = 0
1346 elif c == curses.KEY_HOME:
1348 log_line_lr_scroll = longest_visible_line
1349 elif c == curses.KEY_END:
1351 log_line_lr_scroll = 0
1352 elif c == curses.KEY_UP:
1354 elif c == curses.KEY_DOWN:
1356 elif c == curses.KEY_NPAGE:
1359 elif c == curses.KEY_PPAGE:
1362 elif code == 2
or code == 550:
1364 elif code == 20
or code == 555:
1366 elif code == curses.KEY_BACKSPACE
or code == 127:
1372 if show_gui
is None:
1374 show_gui =
not show_gui
1382 elif line.startswith(
":"):
1388 elif code > 31
and isinstance(c, str):
1402 bus.on(
'speak', handle_speak)
1408 print(
"Input (Ctrl+C to quit):")
1409 line = sys.stdin.readline()
1410 bus.emit(
Message(
"recognizer_loop:utterance",
1411 {
'utterances': [line.strip()]}))
1412 except KeyboardInterrupt
as e:
1415 except KeyboardInterrupt
as e:
1422 """ Connect to the mycroft messagebus and launch a thread handling the 1425 Returns: WebsocketClient 1429 event_thread = Thread(target=connect, args=[bus])
1430 event_thread.setDaemon(
True)
1431 event_thread.start()
def start_mic_monitor(filename)
def _get_cmd_param(cmd, keyword)
Main UI lopo.
def make_titlebar(title, bar_length)
def handle_reconnecting()
def rebuild_filtered_log()
def connect_to_messagebus()
def clamp(n, smallest, largest)
Helper functions.
def ctrl_c_handler(signum, frame)
def handle_is_connected(msg)
def handle_speak(event)
Capturing output from Mycroft.
def init_screen()
Screen handling.
def read_file_from(self, bytefrom)
def draw(x, y, msg, pad=None, pad_chr=None, clr=None)
"Graphic primitives"
def start_qml_gui(messagebus, output_buf)
def load_mycroft_config(bus)
def handle_utterance(event)
def __init__(self, filename, logid)
def add_log_message(message)
def handle_message(msg)
Capturing the messagebus.
def start_log_monitor(filename)
def show_skills(skills)
Skill debugging.
def scroll_log(up, num_lines=None)
def __init__(self, filename)