[PATCH v6 0/5] python/aqmp: AQMP TUI

2021-08-23 Thread G S Niteesh Babu
Gitlab: https://gitlab.com/niteesh.gs/qemu/-/commits/aqmp-tui-prototype-v6
Based-on: <20210803182941.504537-1-js...@redhat.com> [v3,00/25] python:
introduce Asynchronous QMP package
CI: https://gitlab.com/niteesh.gs/qemu/-/pipelines/358117062

Updates since v5:

1) Moved all docstrings under init to class
2) Reworked the format_json function
3) Renamed the has_tui_handler function to has_handler_type
4) Added OSError to be considered for retrying
5) Reworked the editor to add messages to the end of the history stack.

G S Niteesh Babu (5):
  python: Add dependencies for AQMP TUI
  python/aqmp-tui: Add AQMP TUI
  python: Add entry point for aqmp-tui
  python: add optional pygments dependency
  python/aqmp-tui: Add syntax highlighting

 python/Pipfile.lock  |  20 ++
 python/qemu/aqmp/aqmp_tui.py | 652 +++
 python/setup.cfg |  27 +-
 3 files changed, 698 insertions(+), 1 deletion(-)
 create mode 100644 python/qemu/aqmp/aqmp_tui.py

-- 
2.17.1




[PATCH v6 2/5] python/aqmp-tui: Add AQMP TUI

2021-08-23 Thread G S Niteesh Babu
Added AQMP TUI.

Implements the follwing basic features:
1) Command transmission/reception.
2) Shows events asynchronously.
3) Shows server status in the bottom status bar.
4) Automatic retries on disconnects and error conditions.

Also added type annotations and necessary pylint/mypy configurations.

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 620 +++
 python/setup.cfg |  13 +-
 2 files changed, 632 insertions(+), 1 deletion(-)
 create mode 100644 python/qemu/aqmp/aqmp_tui.py

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
new file mode 100644
index 00..ac533541d2
--- /dev/null
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -0,0 +1,620 @@
+# Copyright (c) 2021
+#
+# Authors:
+#  Niteesh Babu G S 
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or
+# later.  See the COPYING file in the top-level directory.
+"""
+AQMP TUI
+
+AQMP TUI is an asynchronous interface built on top the of the AQMP library.
+It is the successor of QMP-shell and is bought-in as a replacement for it.
+
+Example Usage: aqmp-tui 
+Full Usage: aqmp-tui --help
+"""
+
+import argparse
+import asyncio
+import json
+import logging
+from logging import Handler, LogRecord
+import signal
+from typing import (
+List,
+Optional,
+Tuple,
+Type,
+Union,
+cast,
+)
+
+import urwid
+import urwid_readline
+
+from ..qmp import QEMUMonitorProtocol, QMPBadPortError
+from .error import ProtocolError
+from .message import DeserializationError, Message, UnexpectedTypeError
+from .protocol import ConnectError, Runstate
+from .qmp_client import ExecInterruptedError, QMPClient
+from .util import create_task, pretty_traceback
+
+
+# The name of the signal that is used to update the history list
+UPDATE_MSG: str = 'UPDATE_MSG'
+
+
+def format_json(msg: str) -> str:
+"""
+Formats valid/invalid multi-line JSON message into a single-line message.
+
+Formatting is first tried using the standard json module. If that fails
+due to an decoding error then a simple string manipulation is done to
+achieve a single line JSON string.
+
+Converting into single line is more asthetically pleasing when looking
+along with error messages.
+
+Eg:
+Input:
+  [ 1,
+true,
+3 ]
+The above input is not a valid QMP message and produces the following error
+"QMP message is not a JSON object."
+When displaying this in TUI in multiline mode we get
+
+[ 1,
+  true,
+  3 ]: QMP message is not a JSON object.
+
+whereas in singleline mode we get the following
+
+[1, true, 3]: QMP message is not a JSON object.
+
+The single line mode is more asthetically pleasing.
+
+:param msg:
+The message to formatted into single line.
+
+:return: Formatted singleline message.
+"""
+try:
+msg = json.loads(msg)
+return str(json.dumps(msg))
+except json.decoder.JSONDecodeError:
+msg = msg.replace('\n', '')
+words = msg.split(' ')
+words = list(filter(None, words))
+return ' '.join(words)
+
+
+def has_handler_type(logger: logging.Logger,
+ handler_type: Type[Handler]) -> bool:
+"""
+The Logger class has no interface to check if a certain type of handler is
+installed or not. So we provide an interface to do so.
+
+:param logger:
+Logger object
+:param handler_type:
+The type of the handler to be checked.
+
+:return: returns True if handler of type `handler_type`.
+"""
+for handler in logger.handlers:
+if isinstance(handler, handler_type):
+return True
+return False
+
+
+class App(QMPClient):
+"""
+Implements the AQMP TUI.
+
+Initializes the widgets and starts the urwid event loop.
+
+:param address:
+Address of the server to connect to.
+:param num_retries:
+The number of times to retry before stopping to reconnect.
+:param retry_delay:
+The delay(sec) before each retry
+"""
+def __init__(self, address: Union[str, Tuple[str, int]], num_retries: int,
+ retry_delay: Optional[int]) -> None:
+urwid.register_signal(type(self), UPDATE_MSG)
+self.window = Window(self)
+self.address = address
+self.aloop: Optional[asyncio.AbstractEventLoop] = None
+self.num_retries = num_retries
+self.retry_delay = retry_delay if retry_delay else 2
+self.retry: bool = False
+self.exiting: bool = False
+super().__init__()
+
+def add_to_history(self, msg: str, level: Optional[str] = None) -> None:
+"""
+Appends the msg to the history list.
+
+:param msg:
+The raw messag

[PATCH v6 3/5] python: Add entry point for aqmp-tui

2021-08-23 Thread G S Niteesh Babu
Add an entry point for aqmp-tui. This will allow it to be run from
the command line using "aqmp-tui localhost:1234"
More options available in the TUI can be found using "aqmp-tui -h"

Signed-off-by: G S Niteesh Babu 
---
 python/setup.cfg | 1 +
 1 file changed, 1 insertion(+)

diff --git a/python/setup.cfg b/python/setup.cfg
index e9ceaea637..0850c7a10f 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -66,6 +66,7 @@ console_scripts =
 qom-fuse = qemu.qmp.qom_fuse:QOMFuse.entry_point [fuse]
 qemu-ga-client = qemu.qmp.qemu_ga_client:main
 qmp-shell = qemu.qmp.qmp_shell:main
+aqmp-tui = qemu.aqmp.aqmp_tui:main [tui]
 
 [flake8]
 extend-ignore = E722  # Prefer pylint's bare-except checks to flake8's
-- 
2.17.1




[PATCH v6 4/5] python: add optional pygments dependency

2021-08-23 Thread G S Niteesh Babu
Added pygments as optional dependency for AQMP TUI.
This is required for the upcoming syntax highlighting feature
in AQMP TUI.
The dependency has also been added in the devel optional group.

Added mypy 'ignore_missing_imports' for pygments since it does
not have any type stubs.

Signed-off-by: G S Niteesh Babu 
---
 python/Pipfile.lock | 8 
 python/setup.cfg| 5 +
 2 files changed, 13 insertions(+)

diff --git a/python/Pipfile.lock b/python/Pipfile.lock
index da7a4ee164..d2a7dbd88b 100644
--- a/python/Pipfile.lock
+++ b/python/Pipfile.lock
@@ -200,6 +200,14 @@
 ],
 "version": "==2.0.0"
 },
+"pygments": {
+"hashes": [
+
"sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f",
+
"sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"
+],
+"markers": "python_version >= '3.5'",
+"version": "==2.9.0"
+},
 "pylint": {
 "hashes": [
 
"sha256:082a6d461b54f90eea49ca90fff4ee8b6e45e8029e5dbd72f6107ef84f3779c0",
diff --git a/python/setup.cfg b/python/setup.cfg
index 0850c7a10f..435f86384a 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -46,6 +46,7 @@ devel =
 tox >= 3.18.0
 urwid >= 2.1.2
 urwid-readline >= 0.13
+Pygments >= 2.9.0
 
 # Provides qom-fuse functionality
 fuse =
@@ -55,6 +56,7 @@ fuse =
 tui =
 urwid >= 2.1.2
 urwid-readline >= 0.13
+Pygments >= 2.9.0
 
 [options.entry_points]
 console_scripts =
@@ -97,6 +99,9 @@ ignore_missing_imports = True
 [mypy-urwid_readline]
 ignore_missing_imports = True
 
+[mypy-pygments]
+ignore_missing_imports = True
+
 [pylint.messages control]
 # Disable the message, report, category or checker with the given id(s). You
 # can either give multiple identifiers separated by comma (,) or put this
-- 
2.17.1




[PATCH v6 1/5] python: Add dependencies for AQMP TUI

2021-08-23 Thread G S Niteesh Babu
Added dependencies for the upcoming AQMP TUI under the optional
'tui' group.

The same dependencies have also been added under the devel group
since no work around has been found for optional groups to imply
other optional groups.

Signed-off-by: G S Niteesh Babu 
---
 python/Pipfile.lock | 12 
 python/setup.cfg|  8 
 2 files changed, 20 insertions(+)

diff --git a/python/Pipfile.lock b/python/Pipfile.lock
index 457f5c3fe8..da7a4ee164 100644
--- a/python/Pipfile.lock
+++ b/python/Pipfile.lock
@@ -289,6 +289,18 @@
 "markers": "python_version < '3.8'",
 "version": "==3.10.0.0"
 },
+"urwid": {
+"hashes": [
+
"sha256:588bee9c1cb208d0906a9f73c613d2bd32c3ed3702012f51efe318a3f2127eae"
+],
+"version": "==2.1.2"
+},
+"urwid-readline": {
+"hashes": [
+
"sha256:018020cbc864bb5ed87be17dc26b069eae2755cb29f3a9c569aac3bded1efaf4"
+],
+"version": "==0.13"
+},
 "virtualenv": {
 "hashes": [
 
"sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467",
diff --git a/python/setup.cfg b/python/setup.cfg
index 152c683f41..589a90be21 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -44,11 +44,18 @@ devel =
 mypy >= 0.770
 pylint >= 2.8.0
 tox >= 3.18.0
+urwid >= 2.1.2
+urwid-readline >= 0.13
 
 # Provides qom-fuse functionality
 fuse =
 fusepy >= 2.0.4
 
+# AQMP TUI dependencies
+tui =
+urwid >= 2.1.2
+urwid-readline >= 0.13
+
 [options.entry_points]
 console_scripts =
 qom = qemu.qmp.qom:main
@@ -132,5 +139,6 @@ allowlist_externals = make
 deps =
 .[devel]
 .[fuse]  # Workaround to trigger tox venv rebuild
+.[tui]   # Workaround to trigger tox venv rebuild
 commands =
 make check
-- 
2.17.1




[PATCH v6 5/5] python/aqmp-tui: Add syntax highlighting

2021-08-23 Thread G S Niteesh Babu
Add syntax highlighting for the incoming and outgoing QMP messages.
This is achieved using the pygments module which was added in a
previous commit.

The current implementation is a really simple one which doesn't
allow for any configuration. In future this has to be improved
to allow for easier theme config using an external config of
some sort.

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 36 ++--
 1 file changed, 34 insertions(+), 2 deletions(-)

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
index ac533541d2..a2929f771c 100644
--- a/python/qemu/aqmp/aqmp_tui.py
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -30,6 +30,8 @@
 cast,
 )
 
+from pygments import lexers
+from pygments import token as Token
 import urwid
 import urwid_readline
 
@@ -45,6 +47,22 @@
 UPDATE_MSG: str = 'UPDATE_MSG'
 
 
+palette = [
+(Token.Punctuation, '', '', '', 'h15,bold', 'g7'),
+(Token.Text, '', '', '', '', 'g7'),
+(Token.Name.Tag, '', '', '', 'bold,#f88', 'g7'),
+(Token.Literal.Number.Integer, '', '', '', '#fa0', 'g7'),
+(Token.Literal.String.Double, '', '', '', '#6f6', 'g7'),
+(Token.Keyword.Constant, '', '', '', '#6af', 'g7'),
+('DEBUG', '', '', '', '#ddf', 'g7'),
+('INFO', '', '', '', 'g100', 'g7'),
+('WARNING', '', '', '', '#ff6', 'g7'),
+('ERROR', '', '', '', '#a00', 'g7'),
+('CRITICAL', '', '', '', '#a00', 'g7'),
+('background', '', 'black', '', '', 'g7'),
+]
+
+
 def format_json(msg: str) -> str:
 """
 Formats valid/invalid multi-line JSON message into a single-line message.
@@ -353,6 +371,9 @@ def run(self, debug: bool = False) -> None:
 :param debug:
 Enables/Disables asyncio event loop debugging
 """
+screen = urwid.raw_display.Screen()
+screen.set_terminal_properties(256)
+
 self.aloop = asyncio.get_event_loop()
 self.aloop.set_debug(debug)
 
@@ -364,6 +385,8 @@ def run(self, debug: bool = False) -> None:
 event_loop = urwid.AsyncioEventLoop(loop=self.aloop)
 main_loop = urwid.MainLoop(urwid.AttrMap(self.window, 'background'),
unhandled_input=self.unhandled_input,
+   screen=screen,
+   palette=palette,
handle_mouse=True,
event_loop=event_loop)
 
@@ -487,7 +510,8 @@ def __init__(self, parent: App) -> None:
 self.history = urwid.SimpleFocusListWalker([])
 super().__init__(self.history)
 
-def add_to_history(self, history: str) -> None:
+def add_to_history(self,
+   history: Union[str, List[Tuple[str, str]]]) -> None:
 """
 Appends a message to the list and set the focus to the last appended
 message.
@@ -531,10 +555,18 @@ def cb_add_to_history(self, msg: str, level: 
Optional[str] = None) -> None:
 
 :param msg:
 The message to be appended to the history box.
+:param level:
+The log level of the message, if it is a log message.
 """
+formatted = []
 if level:
 msg = f'[{level}]: {msg}'
-self.history.add_to_history(msg)
+formatted.append((level, msg))
+else:
+lexer = lexers.JsonLexer()  # pylint: disable=no-member
+for token in lexer.get_tokens(msg):
+formatted.append(token)
+self.history.add_to_history(formatted)
 
 
 class Window(urwid.Frame):
-- 
2.17.1




[PATCH v5 5/5] python/aqmp-tui: Add syntax highlighting

2021-08-23 Thread G S Niteesh Babu
Add syntax highlighting for the incoming and outgoing QMP messages.
This is achieved using the pygments module which was added in a
previous commit.

The current implementation is a really simple one which doesn't
allow for any configuration. In future this has to be improved
to allow for easier theme config using an external config of
some sort.

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 36 ++--
 1 file changed, 34 insertions(+), 2 deletions(-)

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
index d3180e38bf..9b28eebb90 100644
--- a/python/qemu/aqmp/aqmp_tui.py
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -29,6 +29,8 @@
 cast,
 )
 
+from pygments import lexers
+from pygments import token as Token
 import urwid
 import urwid_readline
 
@@ -44,6 +46,22 @@
 UPDATE_MSG: str = 'UPDATE_MSG'
 
 
+palette = [
+(Token.Punctuation, '', '', '', 'h15,bold', 'g7'),
+(Token.Text, '', '', '', '', 'g7'),
+(Token.Name.Tag, '', '', '', 'bold,#f88', 'g7'),
+(Token.Literal.Number.Integer, '', '', '', '#fa0', 'g7'),
+(Token.Literal.String.Double, '', '', '', '#6f6', 'g7'),
+(Token.Keyword.Constant, '', '', '', '#6af', 'g7'),
+('DEBUG', '', '', '', '#ddf', 'g7'),
+('INFO', '', '', '', 'g100', 'g7'),
+('WARNING', '', '', '', '#ff6', 'g7'),
+('ERROR', '', '', '', '#a00', 'g7'),
+('CRITICAL', '', '', '', '#a00', 'g7'),
+('background', '', 'black', '', '', 'g7'),
+]
+
+
 def format_json(msg: str) -> str:
 """
 Formats given multi-line JSON message into a single-line message.
@@ -359,6 +377,9 @@ def run(self, debug: bool = False) -> None:
 :param debug:
 Enables/Disables asyncio event loop debugging
 """
+screen = urwid.raw_display.Screen()
+screen.set_terminal_properties(256)
+
 self.aloop = asyncio.get_event_loop()
 self.aloop.set_debug(debug)
 
@@ -370,6 +391,8 @@ def run(self, debug: bool = False) -> None:
 event_loop = urwid.AsyncioEventLoop(loop=self.aloop)
 main_loop = urwid.MainLoop(urwid.AttrMap(self.window, 'background'),
unhandled_input=self.unhandled_input,
+   screen=screen,
+   palette=palette,
handle_mouse=True,
event_loop=event_loop)
 
@@ -493,7 +516,8 @@ def __init__(self, parent: App) -> None:
 self.history = urwid.SimpleFocusListWalker([])
 super().__init__(self.history)
 
-def add_to_history(self, history: str) -> None:
+def add_to_history(self,
+   history: Union[str, List[Tuple[str, str]]]) -> None:
 """
 Appends a message to the list and set the focus to the last appended
 message.
@@ -541,10 +565,18 @@ def cb_add_to_history(self, msg: str, level: 
Optional[str] = None) -> None:
 
 :param msg:
 The message to be appended to the history box.
+:param level:
+The log level of the message, if it is a log message.
 """
+formatted = []
 if level:
 msg = f'[{level}]: {msg}'
-self.history.add_to_history(msg)
+formatted.append((level, msg))
+else:
+lexer = lexers.JsonLexer()  # pylint: disable=no-member
+for token in lexer.get_tokens(msg):
+formatted.append(token)
+self.history.add_to_history(formatted)
 
 
 class Window(urwid.Frame):
-- 
2.17.1




[PATCH v5 4/5] python: add optional pygments dependency

2021-08-23 Thread G S Niteesh Babu
Added pygments as optional dependency for AQMP TUI.
This is required for the upcoming syntax highlighting feature
in AQMP TUI.
The dependency has also been added in the devel optional group.

Added mypy 'ignore_missing_imports' for pygments since it does
not have any type stubs.

Signed-off-by: G S Niteesh Babu 
---
 python/Pipfile.lock | 8 
 python/setup.cfg| 5 +
 2 files changed, 13 insertions(+)

diff --git a/python/Pipfile.lock b/python/Pipfile.lock
index da7a4ee164..d2a7dbd88b 100644
--- a/python/Pipfile.lock
+++ b/python/Pipfile.lock
@@ -200,6 +200,14 @@
 ],
 "version": "==2.0.0"
 },
+"pygments": {
+"hashes": [
+
"sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f",
+
"sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"
+],
+"markers": "python_version >= '3.5'",
+"version": "==2.9.0"
+},
 "pylint": {
 "hashes": [
 
"sha256:082a6d461b54f90eea49ca90fff4ee8b6e45e8029e5dbd72f6107ef84f3779c0",
diff --git a/python/setup.cfg b/python/setup.cfg
index 0850c7a10f..435f86384a 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -46,6 +46,7 @@ devel =
 tox >= 3.18.0
 urwid >= 2.1.2
 urwid-readline >= 0.13
+Pygments >= 2.9.0
 
 # Provides qom-fuse functionality
 fuse =
@@ -55,6 +56,7 @@ fuse =
 tui =
 urwid >= 2.1.2
 urwid-readline >= 0.13
+Pygments >= 2.9.0
 
 [options.entry_points]
 console_scripts =
@@ -97,6 +99,9 @@ ignore_missing_imports = True
 [mypy-urwid_readline]
 ignore_missing_imports = True
 
+[mypy-pygments]
+ignore_missing_imports = True
+
 [pylint.messages control]
 # Disable the message, report, category or checker with the given id(s). You
 # can either give multiple identifiers separated by comma (,) or put this
-- 
2.17.1




[PATCH v5 2/5] python/aqmp-tui: Add AQMP TUI

2021-08-23 Thread G S Niteesh Babu
Added AQMP TUI.

Implements the follwing basic features:
1) Command transmission/reception.
2) Shows events asynchronously.
3) Shows server status in the bottom status bar.
4) Automatic retries on disconnects and error conditions.

Also added type annotations and necessary pylint/mypy configurations.

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 637 +++
 python/setup.cfg |  13 +-
 2 files changed, 649 insertions(+), 1 deletion(-)
 create mode 100644 python/qemu/aqmp/aqmp_tui.py

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
new file mode 100644
index 00..d3180e38bf
--- /dev/null
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -0,0 +1,637 @@
+# Copyright (c) 2021
+#
+# Authors:
+#  Niteesh Babu G S 
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or
+# later.  See the COPYING file in the top-level directory.
+"""
+AQMP TUI
+
+AQMP TUI is an asynchronous interface built on top the of the AQMP library.
+It is the successor of QMP-shell and is bought-in as a replacement for it.
+
+Example Usage: aqmp-tui 
+Full Usage: aqmp-tui --help
+"""
+
+import argparse
+import asyncio
+import logging
+from logging import Handler, LogRecord
+import signal
+from typing import (
+List,
+Optional,
+Tuple,
+Type,
+Union,
+cast,
+)
+
+import urwid
+import urwid_readline
+
+from ..qmp import QEMUMonitorProtocol, QMPBadPortError
+from .error import ProtocolError
+from .message import DeserializationError, Message, UnexpectedTypeError
+from .protocol import ConnectError, Runstate
+from .qmp_client import ExecInterruptedError, QMPClient
+from .util import create_task, pretty_traceback
+
+
+# The name of the signal that is used to update the history list
+UPDATE_MSG: str = 'UPDATE_MSG'
+
+
+def format_json(msg: str) -> str:
+"""
+Formats given multi-line JSON message into a single-line message.
+Converting into single line is more asthetically pleasing when looking
+along with error messages.
+
+Eg:
+Input:
+  [ 1,
+true,
+3 ]
+The above input is not a valid QMP message and produces the following error
+"QMP message is not a JSON object."
+When displaying this in TUI in multiline mode we get
+
+[ 1,
+  true,
+  3 ]: QMP message is not a JSON object.
+
+whereas in singleline mode we get the following
+
+[1, true, 3]: QMP message is not a JSON object.
+
+The single line mode is more asthetically pleasing.
+
+:param msg:
+The message to formatted into single line.
+
+:return: Formatted singleline message.
+
+NOTE: We cannot use the JSON module here because it is only capable of
+format valid JSON messages. But here the goal is to also format invalid
+JSON messages.
+"""
+msg = msg.replace('\n', '')
+words = msg.split(' ')
+words = [word for word in words if word != '']
+return ' '.join(words)
+
+
+def has_tui_handler(logger: logging.Logger,
+handler_type: Type[Handler]) -> bool:
+"""
+The Logger class has no interface to check if a certain type of handler is
+installed or not. So we provide an interface to do so.
+
+:param logger:
+Logger object
+:param handler_type:
+The type of the handler to be checked.
+
+:return: returns True if handler of type `handler_type` is installed else
+ False.
+"""
+handlers = logger.handlers
+for handler in handlers:
+if isinstance(handler, handler_type):
+return True
+return False
+
+
+class App(QMPClient):
+"""
+Implements the AQMP TUI.
+
+Initializes the widgets and starts the urwid event loop.
+"""
+def __init__(self, address: Union[str, Tuple[str, int]], num_retries: int,
+ retry_delay: Optional[int]) -> None:
+"""
+Initializes the TUI.
+
+:param address:
+Address of the server to connect to.
+:param num_retries:
+The number of times to retry before stopping to reconnect.
+:param retry_delay:
+The delay(sec) before each retry
+"""
+urwid.register_signal(type(self), UPDATE_MSG)
+self.window = Window(self)
+self.address = address
+self.aloop: Optional[asyncio.AbstractEventLoop] = None
+self.num_retries = num_retries
+self.retry_delay = retry_delay if retry_delay else 2
+self.retry: bool = False
+self.exiting: bool = False
+super().__init__()
+
+def add_to_history(self, msg: str, level: Optional[str] = None) -> None:
+"""
+Appends the msg to the history list.
+
+:param msg:
+The raw message 

[PATCH v5 3/5] python: Add entry point for aqmp-tui

2021-08-23 Thread G S Niteesh Babu
Add an entry point for aqmp-tui. This will allow it to be run from
the command line using "aqmp-tui localhost:1234"
More options available in the TUI can be found using "aqmp-tui -h"

Signed-off-by: G S Niteesh Babu 
---
 python/setup.cfg | 1 +
 1 file changed, 1 insertion(+)

diff --git a/python/setup.cfg b/python/setup.cfg
index e9ceaea637..0850c7a10f 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -66,6 +66,7 @@ console_scripts =
 qom-fuse = qemu.qmp.qom_fuse:QOMFuse.entry_point [fuse]
 qemu-ga-client = qemu.qmp.qemu_ga_client:main
 qmp-shell = qemu.qmp.qmp_shell:main
+aqmp-tui = qemu.aqmp.aqmp_tui:main [tui]
 
 [flake8]
 extend-ignore = E722  # Prefer pylint's bare-except checks to flake8's
-- 
2.17.1




[PATCH v5 1/5] python: Add dependencies for AQMP TUI

2021-08-23 Thread G S Niteesh Babu
Added dependencies for the upcoming AQMP TUI under the optional
'tui' group.

The same dependencies have also been added under the devel group
since no work around has been found for optional groups to imply
other optional groups.

Signed-off-by: G S Niteesh Babu 
---
 python/Pipfile.lock | 12 
 python/setup.cfg|  8 
 2 files changed, 20 insertions(+)

diff --git a/python/Pipfile.lock b/python/Pipfile.lock
index 457f5c3fe8..da7a4ee164 100644
--- a/python/Pipfile.lock
+++ b/python/Pipfile.lock
@@ -289,6 +289,18 @@
 "markers": "python_version < '3.8'",
 "version": "==3.10.0.0"
 },
+"urwid": {
+"hashes": [
+
"sha256:588bee9c1cb208d0906a9f73c613d2bd32c3ed3702012f51efe318a3f2127eae"
+],
+"version": "==2.1.2"
+},
+"urwid-readline": {
+"hashes": [
+
"sha256:018020cbc864bb5ed87be17dc26b069eae2755cb29f3a9c569aac3bded1efaf4"
+],
+"version": "==0.13"
+},
 "virtualenv": {
 "hashes": [
 
"sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467",
diff --git a/python/setup.cfg b/python/setup.cfg
index 152c683f41..589a90be21 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -44,11 +44,18 @@ devel =
 mypy >= 0.770
 pylint >= 2.8.0
 tox >= 3.18.0
+urwid >= 2.1.2
+urwid-readline >= 0.13
 
 # Provides qom-fuse functionality
 fuse =
 fusepy >= 2.0.4
 
+# AQMP TUI dependencies
+tui =
+urwid >= 2.1.2
+urwid-readline >= 0.13
+
 [options.entry_points]
 console_scripts =
 qom = qemu.qmp.qom:main
@@ -132,5 +139,6 @@ allowlist_externals = make
 deps =
 .[devel]
 .[fuse]  # Workaround to trigger tox venv rebuild
+.[tui]   # Workaround to trigger tox venv rebuild
 commands =
 make check
-- 
2.17.1




[PATCH v5 0/5] python/aqmp: AQMP-TUI

2021-08-23 Thread G S Niteesh Babu
Gitlab: https://gitlab.com/niteesh.gs/qemu/-/commits/aqmp-tui-prototype-v5
Based-on: <20210803182941.504537-1-js...@redhat.com> [v3,00/25] python:
introduce Asynchronous QMP package
CI: https://gitlab.com/niteesh.gs/qemu/-/pipelines/357960983

Updates since v4:

1) Changed reference to parent name from master to parent.
2) Merged connection manager to the AQMP TUI patch
3) Fixed some docstrings.
4) Added scrolling support to history box.

G S Niteesh Babu (5):
  python: Add dependencies for AQMP TUI
  python/aqmp-tui: Add AQMP TUI
  python: Add entry point for aqmp-tui
  python: add optional pygments dependency
  python/aqmp-tui: Add syntax highlighting

 python/Pipfile.lock  |  20 ++
 python/qemu/aqmp/aqmp_tui.py | 669 +++
 python/setup.cfg |  27 +-
 3 files changed, 715 insertions(+), 1 deletion(-)
 create mode 100644 python/qemu/aqmp/aqmp_tui.py

-- 
2.17.1




[PATCH v4 6/7] python/aqmp-tui: Add syntax highlighting

2021-08-19 Thread G S Niteesh Babu
Add syntax highlighting for the incoming and outgoing QMP messages.
This is achieved using the pygments module which was added in a
previous commit.

The current implementation is a really simple one which doesn't
allow for any configuration. In future this has to be improved
to allow for easier theme config using an external config of
some sort.

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 36 ++--
 1 file changed, 34 insertions(+), 2 deletions(-)

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
index 12c9c4162a..03d4808acd 100644
--- a/python/qemu/aqmp/aqmp_tui.py
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -29,6 +29,8 @@
 cast,
 )
 
+from pygments import lexers
+from pygments import token as Token
 import urwid
 import urwid_readline
 
@@ -43,6 +45,22 @@
 UPDATE_MSG: str = 'UPDATE_MSG'
 
 
+palette = [
+(Token.Punctuation, '', '', '', 'h15,bold', 'g7'),
+(Token.Text, '', '', '', '', 'g7'),
+(Token.Name.Tag, '', '', '', 'bold,#f88', 'g7'),
+(Token.Literal.Number.Integer, '', '', '', '#fa0', 'g7'),
+(Token.Literal.String.Double, '', '', '', '#6f6', 'g7'),
+(Token.Keyword.Constant, '', '', '', '#6af', 'g7'),
+('DEBUG', '', '', '', '#ddf', 'g7'),
+('INFO', '', '', '', 'g100', 'g7'),
+('WARNING', '', '', '', '#ff6', 'g7'),
+('ERROR', '', '', '', '#a00', 'g7'),
+('CRITICAL', '', '', '', '#a00', 'g7'),
+('background', '', 'black', '', '', 'g7'),
+]
+
+
 def format_json(msg: str) -> str:
 """
 Formats given multi-line JSON message into a single-line message.
@@ -303,6 +321,9 @@ def run(self, debug: bool = False) -> None:
 :param debug:
 Enables/Disables asyncio event loop debugging
 """
+screen = urwid.raw_display.Screen()
+screen.set_terminal_properties(256)
+
 self.aloop = asyncio.get_event_loop()
 self.aloop.set_debug(debug)
 
@@ -314,6 +335,8 @@ def run(self, debug: bool = False) -> None:
 event_loop = urwid.AsyncioEventLoop(loop=self.aloop)
 main_loop = urwid.MainLoop(urwid.AttrMap(self.window, 'background'),
unhandled_input=self.unhandled_input,
+   screen=screen,
+   palette=palette,
handle_mouse=True,
event_loop=event_loop)
 
@@ -434,7 +457,8 @@ def __init__(self, master: App) -> None:
 self.history = urwid.SimpleFocusListWalker([])
 super().__init__(self.history)
 
-def add_to_history(self, history: str) -> None:
+def add_to_history(self,
+   history: Union[str, List[Tuple[str, str]]]) -> None:
 """
 Appends a message to the list and set the focus to the last appended
 message.
@@ -473,10 +497,18 @@ def cb_add_to_history(self, msg: str, level: 
Optional[str] = None) -> None:
 
 :param msg:
 The message to be appended to the history box.
+:param level:
+The log level of the message, if it is a log message.
 """
+formatted = []
 if level:
 msg = f'[{level}]: {msg}'
-self.history.add_to_history(msg)
+formatted.append((level, msg))
+else:
+lexer = lexers.JsonLexer()  # pylint: disable=no-member
+for token in lexer.get_tokens(msg):
+formatted.append(token)
+self.history.add_to_history(formatted)
 
 
 class Window(urwid.Frame):
-- 
2.17.1




[PATCH v4 5/7] python: add optional pygments dependency

2021-08-19 Thread G S Niteesh Babu
Added pygments as optional dependency for AQMP TUI.
This is required for the upcoming syntax highlighting feature
in AQMP TUI.
The dependency has also been added in the devel optional group.

Added mypy 'ignore_missing_imports' for pygments since it does
not have any type stubs.

Signed-off-by: G S Niteesh Babu 
---
 python/Pipfile.lock | 8 
 python/setup.cfg| 5 +
 2 files changed, 13 insertions(+)

diff --git a/python/Pipfile.lock b/python/Pipfile.lock
index da7a4ee164..d2a7dbd88b 100644
--- a/python/Pipfile.lock
+++ b/python/Pipfile.lock
@@ -200,6 +200,14 @@
 ],
 "version": "==2.0.0"
 },
+"pygments": {
+"hashes": [
+
"sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f",
+
"sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"
+],
+"markers": "python_version >= '3.5'",
+"version": "==2.9.0"
+},
 "pylint": {
 "hashes": [
 
"sha256:082a6d461b54f90eea49ca90fff4ee8b6e45e8029e5dbd72f6107ef84f3779c0",
diff --git a/python/setup.cfg b/python/setup.cfg
index 64ed0be0a7..1c2e879a4c 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -46,6 +46,7 @@ devel =
 tox >= 3.18.0
 urwid >= 2.1.2
 urwid-readline >= 0.13
+Pygments >= 2.9.0
 
 # Provides qom-fuse functionality
 fuse =
@@ -55,6 +56,7 @@ fuse =
 tui =
 urwid >= 2.1.2
 urwid-readline >= 0.13
+Pygments >= 2.9.0
 
 [options.entry_points]
 console_scripts =
@@ -97,6 +99,9 @@ ignore_missing_imports = True
 [mypy-urwid_readline]
 ignore_missing_imports = True
 
+[mypy-pygments]
+ignore_missing_imports = True
+
 [pylint.messages control]
 # Disable the message, report, category or checker with the given id(s). You
 # can either give multiple identifiers separated by comma (,) or put this
-- 
2.17.1




[PATCH v4 7/7] python/aqmp-tui: Add QMP connection manager

2021-08-19 Thread G S Niteesh Babu
The connection manager will take care of connecting/disconnecting
to the server. This will also try to reconnect to the server in
certain situations where the client has been disconnected due to
some error condition.

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 127 +--
 1 file changed, 105 insertions(+), 22 deletions(-)

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
index 03d4808acd..c47abe0a25 100644
--- a/python/qemu/aqmp/aqmp_tui.py
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -35,8 +35,9 @@
 import urwid_readline
 
 from ..qmp import QEMUMonitorProtocol, QMPBadPortError
+from .error import ProtocolError
 from .message import DeserializationError, Message, UnexpectedTypeError
-from .protocol import ConnectError
+from .protocol import ConnectError, Runstate
 from .qmp_client import ExecInterruptedError, QMPClient
 from .util import create_task, pretty_traceback
 
@@ -128,17 +129,26 @@ class App(QMPClient):
 
 Initializes the widgets and starts the urwid event loop.
 """
-def __init__(self, address: Union[str, Tuple[str, int]]) -> None:
+def __init__(self, address: Union[str, Tuple[str, int]], num_retries: int,
+ retry_delay: Optional[int]) -> None:
 """
 Initializes the TUI.
 
 :param address:
 Address of the server to connect to.
+:param num_retries:
+The number of times to retry before stopping to reconnect.
+:param retry_delay:
+The delay(sec) before each retry
 """
 urwid.register_signal(type(self), UPDATE_MSG)
 self.window = Window(self)
 self.address = address
 self.aloop: Optional[asyncio.AbstractEventLoop] = None
+self.num_retries = num_retries
+self.retry_delay = retry_delay if retry_delay else 2
+self.retry: bool = False
+self.disconnecting: bool = False
 super().__init__()
 
 def add_to_history(self, msg: str, level: Optional[str] = None) -> None:
@@ -212,10 +222,10 @@ def handle_event(self, event: Message) -> None:
 """
 try:
 await self._raw(msg, assign_id='id' not in msg)
-except ExecInterruptedError:
-logging.info('Error server disconnected before reply')
+except ExecInterruptedError as err:
+logging.info('Error server disconnected before reply %s', str(err))
 self.add_to_history('Server disconnected before reply', 'ERROR')
-self._set_status("[Server Disconnected]")
+await self.disconnect()
 except Exception as err:
 logging.error('Exception from _send_to_server: %s', str(err))
 raise err
@@ -237,10 +247,10 @@ def cb_send_to_server(self, raw_msg: str) -> None:
 create_task(self._send_to_server(msg))
 except (ValueError, TypeError) as err:
 logging.info('Invalid message: %s', str(err))
-self.add_to_history(f'{raw_msg}: {err}')
+self.add_to_history(f'{raw_msg}: {err}', 'ERROR')
 except (DeserializationError, UnexpectedTypeError) as err:
 logging.info('Invalid message: %s', err.error_message)
-self.add_to_history(f'{raw_msg}: {err.error_message}')
+self.add_to_history(f'{raw_msg}: {err.error_message}', 'ERROR')
 
 def unhandled_input(self, key: str) -> None:
 """
@@ -266,18 +276,32 @@ def kill_app(self) -> None:
 
 :raise Exception: When an unhandled exception is caught.
 """
-# It is ok to call disconnect even in disconnect state
+await self.disconnect()
+logging.debug('Disconnect finished. Exiting app')
+raise urwid.ExitMainLoop()
+
+async def disconnect(self) -> None:
+"""
+Overrides the disconnect method to handle the errors locally.
+"""
+if self.disconnecting:
+return
 try:
-await self.disconnect()
-logging.debug('Disconnect finished. Exiting app')
-except EOFError:
-# We receive an EOF during disconnect, ignore that
-pass
+self.disconnecting = True
+await super().disconnect()
+self.retry = False
+except EOFError as err:
+logging.info('disconnect: %s', str(err))
+self.retry = True
+except ProtocolError as err:
+logging.info('disconnect: %s', str(err))
+self.retry = False
 except Exception as err:
-logging.info('_kill_app: %s', str(err))
-# Let the app crash after providing a proper stack trace
+logging.error('disconnect: Unhandled exception %s', str(err))
+self.retry = False
 raise err
-raise urwi

[PATCH v4 2/7] python: Add dependencies for AQMP TUI

2021-08-19 Thread G S Niteesh Babu
Added dependencies for the upcoming AQMP TUI under the optional
'tui' group.

The same dependencies have also been added under the devel group
since no work around has been found for optional groups to imply
other optional groups.

Signed-off-by: G S Niteesh Babu 
---
 python/Pipfile.lock | 12 
 python/setup.cfg|  8 
 2 files changed, 20 insertions(+)

diff --git a/python/Pipfile.lock b/python/Pipfile.lock
index 457f5c3fe8..da7a4ee164 100644
--- a/python/Pipfile.lock
+++ b/python/Pipfile.lock
@@ -289,6 +289,18 @@
 "markers": "python_version < '3.8'",
 "version": "==3.10.0.0"
 },
+"urwid": {
+"hashes": [
+
"sha256:588bee9c1cb208d0906a9f73c613d2bd32c3ed3702012f51efe318a3f2127eae"
+],
+"version": "==2.1.2"
+},
+"urwid-readline": {
+"hashes": [
+
"sha256:018020cbc864bb5ed87be17dc26b069eae2755cb29f3a9c569aac3bded1efaf4"
+],
+"version": "==0.13"
+},
 "virtualenv": {
 "hashes": [
 
"sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467",
diff --git a/python/setup.cfg b/python/setup.cfg
index e83c88db2c..a0ed3279d8 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -44,11 +44,18 @@ devel =
 mypy >= 0.770
 pylint >= 2.8.0
 tox >= 3.18.0
+urwid >= 2.1.2
+urwid-readline >= 0.13
 
 # Provides qom-fuse functionality
 fuse =
 fusepy >= 2.0.4
 
+# AQMP TUI dependencies
+tui =
+urwid >= 2.1.2
+urwid-readline >= 0.13
+
 [options.entry_points]
 console_scripts =
 qom = qemu.qmp.qom:main
@@ -133,5 +140,6 @@ allowlist_externals = make
 deps =
 .[devel]
 .[fuse]  # Workaround to trigger tox venv rebuild
+.[tui]   # Workaround to trigger tox venv rebuild
 commands =
 make check
-- 
2.17.1




[PATCH v4 4/7] python: Add entry point for aqmp-tui

2021-08-19 Thread G S Niteesh Babu
Add an entry point for aqmp-tui. This will allow it to be run from
the command line using "aqmp-tui localhost:1234"
More options available in the TUI can be found using "aqmp-tui -h"

Signed-off-by: G S Niteesh Babu 
---
 python/setup.cfg | 1 +
 1 file changed, 1 insertion(+)

diff --git a/python/setup.cfg b/python/setup.cfg
index 1ff2b907a2..64ed0be0a7 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -66,6 +66,7 @@ console_scripts =
 qom-fuse = qemu.qmp.qom_fuse:QOMFuse.entry_point [fuse]
 qemu-ga-client = qemu.qmp.qemu_ga_client:main
 qmp-shell = qemu.qmp.qmp_shell:main
+aqmp-tui = qemu.aqmp.aqmp_tui:main [tui]
 
 [flake8]
 extend-ignore = E722  # Prefer pylint's bare-except checks to flake8's
-- 
2.17.1




[PATCH v4 3/7] python/aqmp-tui: Add AQMP TUI draft

2021-08-19 Thread G S Niteesh Babu
Added a draft of AQMP TUI.

Implements the follwing basic features:
1) Command transmission/reception.
2) Shows events asynchronously.
3) Shows server status in the bottom status bar.

Also added type annotations and necessary pylint,
mypy configurations

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 566 +++
 python/setup.cfg |  15 +-
 2 files changed, 579 insertions(+), 2 deletions(-)
 create mode 100644 python/qemu/aqmp/aqmp_tui.py

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
new file mode 100644
index 00..12c9c4162a
--- /dev/null
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -0,0 +1,566 @@
+# Copyright (c) 2021
+#
+# Authors:
+#  Niteesh Babu G S 
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or
+# later.  See the COPYING file in the top-level directory.
+"""
+AQMP TUI
+
+AQMP TUI is an asynchronous interface built on top the of the AQMP library.
+It is the successor of QMP-shell and is bought-in as a replacement for it.
+
+Example Usage: aqmp-tui 
+Full Usage: aqmp-tui --help
+"""
+
+import argparse
+import asyncio
+import logging
+from logging import Handler, LogRecord
+import signal
+from typing import (
+List,
+Optional,
+Tuple,
+Type,
+Union,
+cast,
+)
+
+import urwid
+import urwid_readline
+
+from ..qmp import QEMUMonitorProtocol, QMPBadPortError
+from .message import DeserializationError, Message, UnexpectedTypeError
+from .protocol import ConnectError
+from .qmp_client import ExecInterruptedError, QMPClient
+from .util import create_task, pretty_traceback
+
+
+# The name of the signal that is used to update the history list
+UPDATE_MSG: str = 'UPDATE_MSG'
+
+
+def format_json(msg: str) -> str:
+"""
+Formats given multi-line JSON message into a single-line message.
+Converting into single line is more asthetically pleasing when looking
+along with error messages.
+
+Eg:
+Input:
+  [ 1,
+true,
+3 ]
+The above input is not a valid QMP message and produces the following error
+"QMP message is not a JSON object."
+When displaying this in TUI in multiline mode we get
+
+[ 1,
+  true,
+  3 ]: QMP message is not a JSON object.
+
+whereas in singleline mode we get the following
+
+[1, true, 3]: QMP message is not a JSON object.
+
+The single line mode is more asthetically pleasing.
+
+:param msg:
+The message to formatted into single line.
+
+:return: Formatted singleline message.
+
+NOTE: We cannot use the JSON module here because it is only capable of
+format valid JSON messages. But here the goal is to also format invalid
+JSON messages.
+"""
+msg = msg.replace('\n', '')
+words = msg.split(' ')
+words = [word for word in words if word != '']
+return ' '.join(words)
+
+
+def has_tui_handler(logger: logging.Logger,
+handler_type: Type[Handler]) -> bool:
+"""
+The Logger class has no interface to check if a certain type of handler is
+installed or not. So we provide an interface to do so.
+
+:param logger:
+Logger object
+:param handler_type:
+The type of the handler to be checked.
+
+:return: returns True if handler of type `handler_type` is installed else
+ False.
+"""
+handlers = logger.handlers
+for handler in handlers:
+if isinstance(handler, handler_type):
+return True
+return False
+
+
+class App(QMPClient):
+"""
+Implements the AQMP TUI.
+
+Initializes the widgets and starts the urwid event loop.
+"""
+def __init__(self, address: Union[str, Tuple[str, int]]) -> None:
+"""
+Initializes the TUI.
+
+:param address:
+Address of the server to connect to.
+"""
+urwid.register_signal(type(self), UPDATE_MSG)
+self.window = Window(self)
+self.address = address
+self.aloop: Optional[asyncio.AbstractEventLoop] = None
+super().__init__()
+
+def add_to_history(self, msg: str, level: Optional[str] = None) -> None:
+"""
+Appends the msg to the history list.
+
+:param msg:
+The raw message to be appended in string type.
+"""
+urwid.emit_signal(self, UPDATE_MSG, msg, level)
+
+def _cb_outbound(self, msg: Message) -> Message:
+"""
+Callback: outbound message hook.
+
+Appends the outgoing messages to the history box.
+
+:param msg: raw outbound message.
+:return: final outbound message.
+"""
+str_msg = str(msg)
+
+if not has_tui_handler(logging.getLogger(), TU

[PATCH v4 1/7] python: disable pylint errors for aqmp-tui

2021-08-19 Thread G S Niteesh Babu
Disable missing-docstring and fixme pylint warnings.
This is because since the AQMP is just a prototype
it is currently not documented properly and lot
of todo and fixme's are still in place.

Signed-off-by: G S Niteesh Babu 
---
 python/setup.cfg | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/python/setup.cfg b/python/setup.cfg
index 077395f96e..e83c88db2c 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -90,6 +90,8 @@ ignore_missing_imports = True
 # --disable=W".
 disable=too-many-function-args,  # mypy handles this with less false positives.
 no-member,  # mypy also handles this better.
+missing-docstring, # FIXME
+fixme, # FIXME
 
 [pylint.basic]
 # Good variable names which should always be accepted, separated by a comma.
-- 
2.17.1




[PATCH v4 0/7] AQMP TUI Draft

2021-08-19 Thread G S Niteesh Babu
Hello all,

Gitlab: https://gitlab.com/niteesh.gs/qemu/-/commits/aqmp-tui-prototype-v4
CI: https://gitlab.com/niteesh.gs/qemu/-/pipelines/356024270

Revision since v3:
1) Added docstrings
2) Minor changes in AQMP TUI Draft
3) Switched to constant retry delay in QMP connection manager and other
   minor changes.

G S Niteesh Babu (7):
  python: disable pylint errors for aqmp-tui
  python: Add dependencies for AQMP TUI
  python/aqmp-tui: Add AQMP TUI draft
  python: Add entry point for aqmp-tui
  python: add optional pygments dependency
  python/aqmp-tui: Add syntax highlighting
  python/aqmp-tui: Add QMP connection manager

 python/Pipfile.lock  |  20 +
 python/qemu/aqmp/aqmp_tui.py | 681 +++
 python/setup.cfg |  29 +-
 3 files changed, 729 insertions(+), 1 deletion(-)
 create mode 100644 python/qemu/aqmp/aqmp_tui.py

-- 
2.17.1




[PATCH v3 13/13] python/aqmp-tui: Allow copying message from TUI

2021-07-30 Thread G S Niteesh Babu
This commit adds a feature that enables use to copy
messages from the TUI after highlighting the message
in the history box using up/down arrow keys and pressing
alt-c.

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 9 +
 1 file changed, 9 insertions(+)

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
index 4bae0d4e89..434f431a35 100644
--- a/python/qemu/aqmp/aqmp_tui.py
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -21,6 +21,7 @@
 
 from pygments import lexers
 from pygments import token as Token
+import pyperclip
 import urwid
 import urwid_readline
 
@@ -390,6 +391,14 @@ def keypress(self, size: Tuple[int, int], key: str) -> 
Optional[str]:
 self._update_highlighting()
 self.change_focus(size, self.highlighting)
 return None
+if key == 'meta c':
+if self.highlighting == -1:
+return None
+widget = self.history[self.highlighting].original_widget
+text = widget.get_text()[0]
+LOGGER.info('Text is %s', text)
+pyperclip.copy(text)
+return None
 
 # Remove highlighting if someother key is pressed
 if self.highlighting != -1:
-- 
2.17.1




[PATCH v3 12/13] python/aqmp-tui: Add pyperclip dependency

2021-07-30 Thread G S Niteesh Babu
This dependency is required to enable copying from the TUI
using special keys to the system clipboard.

pyperclip works out of the box on windows and macos but requires
xsel/xclip to be installed on linux machines.

Signed-off-by: G S Niteesh Babu 
---
 python/Pipfile.lock | 22 ++
 python/setup.cfg|  5 +
 2 files changed, 27 insertions(+)

diff --git a/python/Pipfile.lock b/python/Pipfile.lock
index 2c6d779348..3544c8703d 100644
--- a/python/Pipfile.lock
+++ b/python/Pipfile.lock
@@ -45,6 +45,14 @@
 "index": "pypi",
 "version": "==87.0"
 },
+"backports.entry-points-selectable": {
+"hashes": [
+
"sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a",
+
"sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"
+],
+"markers": "python_version >= '2.7'",
+"version": "==1.1.0"
+},
 "distlib": {
 "hashes": [
 
"sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736",
@@ -169,6 +177,14 @@
 "markers": "python_version >= '2.7' and python_version not in 
'3.0, 3.1, 3.2, 3.3'",
 "version": "==20.9"
 },
+"platformdirs": {
+"hashes": [
+
"sha256:4666d822218db6a262bdfdc9c39d21f23b4cfdb08af331a81e92751daf6c866c",
+
"sha256:632daad3ab546bd8e6af0537d09805cec458dce201bccfe23012df73332e181e"
+],
+"markers": "python_version >= '3.6'",
+"version": "==2.2.0"
+},
 "pluggy": {
 "hashes": [
 
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
@@ -224,6 +240,12 @@
 "markers": "python_version >= '2.6' and python_version not in 
'3.0, 3.1, 3.2, 3.3'",
 "version": "==2.4.7"
 },
+"pyperclip": {
+"hashes": [
+
"sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57"
+],
+"version": "==1.8.2"
+},
 "qemu": {
 "editable": true,
 "path": "."
diff --git a/python/setup.cfg b/python/setup.cfg
index bbb7306c3d..683c0b1d00 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -47,6 +47,7 @@ devel =
 urwid >= 2.1.2
 urwid-readline >= 0.13
 Pygments >= 2.9.0
+pyperclip >= 1.8.2
 
 # Provides qom-fuse functionality
 fuse =
@@ -57,6 +58,7 @@ tui =
 urwid >= 2.1.2
 urwid-readline >= 0.13
 Pygments >= 2.9.0
+pyperclip >= 1.8.2
 
 [options.entry_points]
 console_scripts =
@@ -102,6 +104,9 @@ ignore_missing_imports = True
 [mypy-pygments]
 ignore_missing_imports = True
 
+[mypy-pyperclip]
+ignore_missing_imports = True
+
 [pylint.messages control]
 # Disable the message, report, category or checker with the given id(s). You
 # can either give multiple identifiers separated by comma (,) or put this
-- 
2.17.1




[PATCH v3 07/13] python: add optional pygments dependency

2021-07-30 Thread G S Niteesh Babu
Added pygments as optional dependency for AQMP TUI.
This is required for the upcoming syntax highlighting feature
in AQMP TUI.
The dependency has also been added in the devel optional group.

Added mypy 'ignore_missing_imports' for pygments since it does
not have any type stubs.

Signed-off-by: G S Niteesh Babu 
---
 python/Pipfile.lock | 8 
 python/setup.cfg| 5 +
 2 files changed, 13 insertions(+)

diff --git a/python/Pipfile.lock b/python/Pipfile.lock
index 76cf1e4930..2c6d779348 100644
--- a/python/Pipfile.lock
+++ b/python/Pipfile.lock
@@ -200,6 +200,14 @@
 ],
 "version": "==2.0.0"
 },
+"pygments": {
+"hashes": [
+
"sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f",
+
"sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"
+],
+"markers": "python_version >= '3.5'",
+"version": "==2.9.0"
+},
 "pylint": {
 "hashes": [
 
"sha256:082a6d461b54f90eea49ca90fff4ee8b6e45e8029e5dbd72f6107ef84f3779c0",
diff --git a/python/setup.cfg b/python/setup.cfg
index 11c6240aba..bbb7306c3d 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -46,6 +46,7 @@ devel =
 tox >= 3.18.0
 urwid >= 2.1.2
 urwid-readline >= 0.13
+Pygments >= 2.9.0
 
 # Provides qom-fuse functionality
 fuse =
@@ -55,6 +56,7 @@ fuse =
 tui =
 urwid >= 2.1.2
 urwid-readline >= 0.13
+Pygments >= 2.9.0
 
 [options.entry_points]
 console_scripts =
@@ -97,6 +99,9 @@ ignore_missing_imports = True
 [mypy-urwid_readline]
 ignore_missing_imports = True
 
+[mypy-pygments]
+ignore_missing_imports = True
+
 [pylint.messages control]
 # Disable the message, report, category or checker with the given id(s). You
 # can either give multiple identifiers separated by comma (,) or put this
-- 
2.17.1




[PATCH v3 08/13] python/aqmp-tui: add syntax highlighting

2021-07-30 Thread G S Niteesh Babu
Add syntax highlighting for the incoming and outgoing QMP messages.
This is achieved using the pygments module which was added in a
previous commit.

The current implementation is a really simple one which doesn't
allow for any configuration. In future this has to be improved
to allow for easier theme config using an external config of
some sort.

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 52 +++-
 1 file changed, 40 insertions(+), 12 deletions(-)

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
index ab9ada793a..0d5ec62cb7 100644
--- a/python/qemu/aqmp/aqmp_tui.py
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -19,6 +19,8 @@
 Union,
 )
 
+from pygments import lexers
+from pygments import token as Token
 import urwid
 import urwid_readline
 
@@ -35,6 +37,22 @@
 LOGGER = logging.getLogger()
 
 
+palette = [
+(Token.Punctuation, '', '', '', 'h15,bold', 'g7'),
+(Token.Text, '', '', '', '', 'g7'),
+(Token.Name.Tag, '', '', '', 'bold,#f88', 'g7'),
+(Token.Literal.Number.Integer, '', '', '', '#fa0', 'g7'),
+(Token.Literal.String.Double, '', '', '', '#6f6', 'g7'),
+(Token.Keyword.Constant, '', '', '', '#6af', 'g7'),
+('DEBUG', '', '', '', '#ddf', 'g7'),
+('INFO', '', '', '', 'g100', 'g7'),
+('WARNING', '', '', '', '#ff6', 'g7'),
+('ERROR', '', '', '', '#a00', 'g7'),
+('CRITICAL', '', '', '', '#a00', 'g7'),
+('background', '', 'black', '', '', 'g7'),
+]
+
+
 def format_json(msg: str) -> str:
 """
 Formats given multiline JSON message into a single line message.
@@ -57,17 +75,14 @@ def __init__(self, address: Union[str, Tuple[str, int]]) -> 
None:
 self.aloop: Optional[Any] = None  # FIXME: Use more concrete type.
 super().__init__()
 
-def add_to_history(self, msg: str) -> None:
-urwid.emit_signal(self, UPDATE_MSG, msg)
+def add_to_history(self, msg: str, level: Optional[str] = None) -> None:
+urwid.emit_signal(self, UPDATE_MSG, msg, level)
 
 def _cb_outbound(self, msg: Message) -> Message:
 # FIXME: I think the ideal way to omit these messages during in-TUI
-# logging will be to add a filter to the logger. We can use regex to
-# filter out messages starting with 'Request:' or 'Response:' but I
-# think a better approach will be encapsulate the message in an object
-# and filter based on the object. Encapsulation of the message will
-# also be necessary when we want different formatting of messages
-# inside TUI.
+# logging will be to add a filter to the logger. We can use
+# regex/startswith to filter out messages starting with 'Request:' or
+# 'Response:'. If possible please suggest other ideas.
 handler = LOGGER.handlers[0]
 if not isinstance(handler, TUILogHandler):
 LOGGER.debug('Request: %s', str(msg))
@@ -156,6 +171,9 @@ def _get_formatted_address(self) -> str:
 self._set_status('Server shutdown')
 
 def run(self, debug: bool = False) -> None:
+screen = urwid.raw_display.Screen()
+screen.set_terminal_properties(256)
+
 self.aloop = asyncio.get_event_loop()
 self.aloop.set_debug(debug)
 
@@ -167,6 +185,8 @@ def run(self, debug: bool = False) -> None:
 event_loop = urwid.AsyncioEventLoop(loop=self.aloop)
 main_loop = urwid.MainLoop(urwid.AttrMap(self.window, 'background'),
unhandled_input=self.unhandled_input,
+   screen=screen,
+   palette=palette,
handle_mouse=True,
event_loop=event_loop)
 
@@ -251,7 +271,8 @@ def __init__(self, master: App) -> None:
 self.history = urwid.SimpleFocusListWalker([])
 super().__init__(self.history)
 
-def add_to_history(self, history: str) -> None:
+def add_to_history(self,
+   history: Union[str, List[Tuple[str, str]]]) -> None:
 self.history.append(urwid.Text(history))
 if self.history:
 self.history.set_focus(len(self.history) - 1)
@@ -271,8 +292,15 @@ def __init__(self, master: App) -> None:
 super().__init__(self.body)
 urwid.connect_signal(self.master, UPDATE_MSG, self.cb_add_to_history)
 
-def cb_add_to_history(self, msg: str) -> None:
-self.history.add_to_history(msg)
+def cb_add_to_history(self, msg: str, level: Optional[str] = None) -> None:
+formatted = []
+if level:
+formatted.append((level, msg))
+else:
+lexer = lexers.JsonLexer()  # pylint: disable=no-member
+for token in lexer.get_tokens(msg):
+formatted.append(token)
+self.history.add_to_history(formatted)
 
 
 class Window(urwid.Frame):
@@ -

[PATCH v3 11/13] python/aqmp-tui: Add ability to highlight messages

2021-07-30 Thread G S Niteesh Babu
Adds ability to highlight messages in the history box. The messages
can be selected using up/down arrow keys.
This can be enhanced in the future to apply specific settings to
a particular message.

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 50 
 1 file changed, 50 insertions(+)

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
index fb828b1a27..4bae0d4e89 100644
--- a/python/qemu/aqmp/aqmp_tui.py
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -344,6 +344,7 @@ def __init__(self, master: App) -> None:
 self.master = master
 self.history = urwid.SimpleFocusListWalker([])
 super().__init__(self.history)
+self.highlighting = -1
 
 def add_to_history(self,
history: Union[str, List[Tuple[str, str]]]) -> None:
@@ -351,8 +352,57 @@ def add_to_history(self,
 if self.history:
 self.history.set_focus(len(self.history) - 1)
 
+def _remove_highlighting(self) -> None:
+assert self.highlighting != -1
+pos = self.highlighting
+widget = self.history[pos]
+widget = widget.original_widget
+self.history[pos] = widget
+
+def _update_highlighting(self) -> None:
+assert self.highlighting != -1
+pos = self.highlighting
+widget = self.history[pos]
+self.history[pos] = urwid.LineBox(widget)
+
+def keypress(self, size: Tuple[int, int], key: str) -> Optional[str]:
+if key == 'up':
+if self.highlighting != -1:
+pos = self.highlighting
+self._remove_highlighting()
+pos = max(pos - 1, 0)
+self.highlighting = pos
+else:
+self.highlighting = len(self.history) - 1
+self._update_highlighting()
+self.change_focus(size, self.highlighting)
+return None
+if key == 'down':
+pos = self.highlighting
+if pos == -1:
+return None
+
+self._remove_highlighting()
+if pos == len(self.history) - 1:
+self.highlighting = -1
+else:
+self.highlighting = pos + 1
+self._update_highlighting()
+self.change_focus(size, self.highlighting)
+return None
+
+# Remove highlighting if someother key is pressed
+if self.highlighting != -1:
+self._remove_highlighting()
+self.highlighting = -1
+return super().keypress(size, key)  # type: ignore
+
 def mouse_event(self, size: Tuple[int, int], _event: str, button: float,
 _x: int, _y: int, focus: bool) -> None:
+if self.highlighting != -1:
+self._remove_highlighting()
+self.highlighting = -1
+
 # Scroll only on focus. Therefore it is required to
 # click on the widget to enable scrolling.
 if not focus:
-- 
2.17.1




[PATCH v3 05/13] python: add entry point for aqmp-tui

2021-07-30 Thread G S Niteesh Babu
Add an entry point for aqmp-tui. This will allow it to be run from
the command line using "aqmp-tui localhost:1234"
More options available in the TUI can be found using "aqmp-tui -h"

Signed-off-by: G S Niteesh Babu 
---
 python/setup.cfg | 1 +
 1 file changed, 1 insertion(+)

diff --git a/python/setup.cfg b/python/setup.cfg
index 50f9894468..8cd9ac0d81 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -66,6 +66,7 @@ console_scripts =
 qom-fuse = qemu.qmp.qom_fuse:QOMFuse.entry_point [fuse]
 qemu-ga-client = qemu.qmp.qemu_ga_client:main
 qmp-shell = qemu.qmp.qmp_shell:main
+aqmp-tui = qemu.aqmp.aqmp_tui:main [tui]
 
 [flake8]
 extend-ignore = E722  # Prefer pylint's bare-except checks to flake8's
-- 
2.17.1




[PATCH v3 03/13] python: Add dependencies for AQMP TUI

2021-07-30 Thread G S Niteesh Babu
Added dependencies for the upcoming AQMP TUI under the optional
'tui' group.

The same dependencies have also been added under the devel group
since no work around has been found for optional groups to imply
other optional groups.

Signed-off-by: G S Niteesh Babu 
---
 python/Pipfile.lock | 12 
 python/setup.cfg|  8 
 2 files changed, 20 insertions(+)

diff --git a/python/Pipfile.lock b/python/Pipfile.lock
index 8ab41a3f60..76cf1e4930 100644
--- a/python/Pipfile.lock
+++ b/python/Pipfile.lock
@@ -289,6 +289,18 @@
 "markers": "python_version < '3.8'",
 "version": "==3.10.0.0"
 },
+"urwid": {
+"hashes": [
+
"sha256:588bee9c1cb208d0906a9f73c613d2bd32c3ed3702012f51efe318a3f2127eae"
+],
+"version": "==2.1.2"
+},
+"urwid-readline": {
+"hashes": [
+
"sha256:018020cbc864bb5ed87be17dc26b069eae2755cb29f3a9c569aac3bded1efaf4"
+],
+"version": "==0.13"
+},
 "virtualenv": {
 "hashes": [
 
"sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467",
diff --git a/python/setup.cfg b/python/setup.cfg
index 7a30dd5b09..d106a0ed7a 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -44,11 +44,18 @@ devel =
 mypy >= 0.770
 pylint >= 2.8.0
 tox >= 3.18.0
+urwid >= 2.1.2
+urwid-readline >= 0.13
 
 # Provides qom-fuse functionality
 fuse =
 fusepy >= 2.0.4
 
+# AQMP TUI dependencies
+tui =
+urwid >= 2.1.2
+urwid-readline >= 0.13
+
 [options.entry_points]
 console_scripts =
 qom = qemu.qmp.qom:main
@@ -133,5 +140,6 @@ allowlist_externals = make
 deps =
 .[devel]
 .[fuse]  # Workaround to trigger tox venv rebuild
+.[tui]   # Workaround to trigger tox venv rebuild
 commands =
 make check
-- 
2.17.1




[PATCH v3 10/13] python/aqmp-tui: Add scrolling to history box

2021-07-30 Thread G S Niteesh Babu
Adds scroll support to history box. The list can now be scrolled
using arrow keys, page up/down and the mouse.

The current implementation requires the widget to be in focus
to enable scrolling. Therefore the user has to click on the widget
before scrolling.

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 13 +
 1 file changed, 13 insertions(+)

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
index ef91883fa5..fb828b1a27 100644
--- a/python/qemu/aqmp/aqmp_tui.py
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -351,6 +351,19 @@ def add_to_history(self,
 if self.history:
 self.history.set_focus(len(self.history) - 1)
 
+def mouse_event(self, size: Tuple[int, int], _event: str, button: float,
+_x: int, _y: int, focus: bool) -> None:
+# Scroll only on focus. Therefore it is required to
+# click on the widget to enable scrolling.
+if not focus:
+return
+# button == 4 represents scroll up event
+if button == 4.0:
+super().keypress(size, 'up')
+# button == 5 represents scroll down event
+elif button == 5.0:
+super().keypress(size, 'down')
+
 
 class HistoryWindow(urwid.Frame):
 """
-- 
2.17.1




[PATCH v3 04/13] python/aqmp-tui: Add AQMP TUI draft

2021-07-30 Thread G S Niteesh Babu
Added a draft of AQMP TUI.

Implements the follwing basic features:
1) Command transmission/reception.
2) Shows events asynchronously.
3) Shows server status in the bottom status bar.

Also added necessary pylint, mypy configurations

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 333 +++
 python/setup.cfg |  16 +-
 2 files changed, 348 insertions(+), 1 deletion(-)
 create mode 100644 python/qemu/aqmp/aqmp_tui.py

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
new file mode 100644
index 00..ec9eba0aa7
--- /dev/null
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -0,0 +1,333 @@
+# Copyright (c) 2021
+#
+# Authors:
+#  Niteesh Babu G S 
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or
+# later.  See the COPYING file in the top-level directory.
+
+import argparse
+import asyncio
+import logging
+from logging import Handler
+import signal
+
+import urwid
+import urwid_readline
+
+from ..qmp import QEMUMonitorProtocol, QMPBadPortError
+from .message import DeserializationError, Message, UnexpectedTypeError
+from .protocol import ConnectError
+from .qmp_client import ExecInterruptedError, QMPClient
+from .util import create_task, pretty_traceback
+
+
+UPDATE_MSG = 'UPDATE_MSG'
+
+# Using root logger to enable all loggers under qemu and asyncio
+LOGGER = logging.getLogger()
+
+
+def format_json(msg):
+"""
+Formats given multiline JSON message into a single line message.
+Converting into single line is more asthetically pleasing when looking
+along with error messages compared to multiline JSON.
+"""
+# FIXME: Use better formatting mechanism. Might break at more complex JSON
+# data.
+msg = msg.replace('\n', '')
+words = msg.split(' ')
+words = [word for word in words if word != '']
+return ' '.join(words)
+
+
+class App(QMPClient):
+def __init__(self, address):
+urwid.register_signal(type(self), UPDATE_MSG)
+self.window = Window(self)
+self.address = address
+self.aloop = None
+super().__init__()
+
+def add_to_history(self, msg):
+urwid.emit_signal(self, UPDATE_MSG, msg)
+
+def _cb_outbound(self, msg):
+# FIXME: I think the ideal way to omit these messages during in-TUI
+# logging will be to add a filter to the logger. We can use regex to
+# filter out messages starting with 'Request:' or 'Response:' but I
+# think a better approach will be encapsulate the message in an object
+# and filter based on the object. Encapsulation of the message will
+# also be necessary when we want different formatting of messages
+# inside TUI.
+handler = LOGGER.handlers[0]
+if not isinstance(handler, TUILogHandler):
+LOGGER.debug('Request: %s', str(msg))
+self.add_to_history('<-- ' + str(msg))
+return msg
+
+def _cb_inbound(self, msg):
+handler = LOGGER.handlers[0]
+if not isinstance(handler, TUILogHandler):
+LOGGER.debug('Response: %s', str(msg))
+self.add_to_history('--> ' + str(msg))
+return msg
+
+async def wait_for_events(self):
+async for event in self.events:
+self.handle_event(event)
+
+async def _send_to_server(self, raw_msg):
+# FIXME: Format the raw_msg in history view to one line. It is not
+# pleasing to see multiple lines JSON object with an error statement.
+try:
+msg = Message(bytes(raw_msg, encoding='utf-8'))
+# Format multiline json into a single line JSON, since it is more
+# pleasing to look along with err message in TUI.
+raw_msg = self.format_json(raw_msg)
+await self._raw(msg, assign_id='id' not in msg)
+except (ValueError, TypeError) as err:
+LOGGER.info('Invalid message: %s', str(err))
+self.add_to_history(f'{raw_msg}: {err}')
+except (DeserializationError, UnexpectedTypeError) as err:
+LOGGER.info('Invalid message: %s', err.error_message)
+self.add_to_history(f'{raw_msg}: {err.error_message}')
+except ExecInterruptedError:
+LOGGER.info('Error server disconnected before reply')
+urwid.emit_signal(self, UPDATE_MSG,
+  '{"error": "Server disconnected before reply"}')
+self._set_status("Server disconnected")
+except Exception as err:
+LOGGER.error('Exception from _send_to_server: %s', str(err))
+raise err
+
+def cb_send_to_server(self, msg):
+create_task(self._send_to_server(msg))
+
+def unhandled_input(self, key):
+if key == 'esc':
+self.kill_app()
+
+def kill_app(self):
+# TODO: Work on the disconnect logic
+create_task(self._kill_app())
+
+asyn

[PATCH v3 09/13] python/aqmp-tui: Add QMP connection manager

2021-07-30 Thread G S Niteesh Babu
Instead of manually connecting and disconnecting from the
server. We now rely on the runstate to manage the QMP
connection.

Along with this the ability to reconnect on certain exceptions
has also been added.

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 109 ++-
 1 file changed, 94 insertions(+), 15 deletions(-)

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
index 0d5ec62cb7..ef91883fa5 100644
--- a/python/qemu/aqmp/aqmp_tui.py
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -25,8 +25,9 @@
 import urwid_readline
 
 from ..qmp import QEMUMonitorProtocol, QMPBadPortError
+from .error import ProtocolError
 from .message import DeserializationError, Message, UnexpectedTypeError
-from .protocol import ConnectError
+from .protocol import ConnectError, Runstate
 from .qmp_client import ExecInterruptedError, QMPClient
 from .util import create_task, pretty_traceback
 
@@ -67,12 +68,24 @@ def format_json(msg: str) -> str:
 return ' '.join(words)
 
 
+def type_name(mtype: Any) -> str:
+"""
+Returns the type name
+"""
+return type(mtype).__name__
+
+
 class App(QMPClient):
-def __init__(self, address: Union[str, Tuple[str, int]]) -> None:
+def __init__(self, address: Union[str, Tuple[str, int]], num_retries: int,
+ retry_delay: Optional[int]) -> None:
 urwid.register_signal(type(self), UPDATE_MSG)
 self.window = Window(self)
 self.address = address
 self.aloop: Optional[Any] = None  # FIXME: Use more concrete type.
+self.num_retries = num_retries
+self.retry_delay = retry_delay
+self.retry: bool = False
+self.disconnecting: bool = False
 super().__init__()
 
 def add_to_history(self, msg: str, level: Optional[str] = None) -> None:
@@ -119,7 +132,7 @@ def _cb_inbound(self, msg: Message) -> Message:
 LOGGER.info('Error server disconnected before reply')
 urwid.emit_signal(self, UPDATE_MSG,
   '{"error": "Server disconnected before reply"}')
-self._set_status("Server disconnected")
+await self.disconnect()
 except Exception as err:
 LOGGER.error('Exception from _send_to_server: %s', str(err))
 raise err
@@ -136,15 +149,29 @@ def kill_app(self) -> None:
 create_task(self._kill_app())
 
 async def _kill_app(self) -> None:
-# It is ok to call disconnect even in disconnect state
+await self.disconnect()
+LOGGER.debug('Disconnect finished. Exiting app')
+raise urwid.ExitMainLoop()
+
+async def disconnect(self) -> None:
+if self.disconnecting:
+return
 try:
-await self.disconnect()
-LOGGER.debug('Disconnect finished. Exiting app')
+self.disconnecting = True
+await super().disconnect()
+self.retry = True
+except EOFError as err:
+LOGGER.info('disconnect: %s', type_name(err))
+self.retry = True
+except ProtocolError as err:
+LOGGER.info('disconnect: %s', type_name(err))
+self.retry = False
 except Exception as err:
-LOGGER.info('_kill_app: %s', str(err))
-# Let the app crash after providing a proper stack trace
+LOGGER.error('disconnect: Unhandled exception %s', str(err))
+self.retry = False
 raise err
-raise urwid.ExitMainLoop()
+finally:
+self.disconnecting = False
 
 def handle_event(self, event: Message) -> None:
 # FIXME: Consider all states present in qapi/run-state.json
@@ -161,14 +188,61 @@ def _get_formatted_address(self) -> str:
 addr = f'{host}:{port}'
 return addr
 
-async def connect_server(self) -> None:
+async def _retry_connection(self) -> Optional[str]:
+current_retries = 0
+err = None
+# Increase in power sequence of 2 if no delay is provided
+cur_delay = 1
+inc_delay = 2
+if self.retry_delay:
+inc_delay = 1
+cur_delay = self.retry_delay
+# initial try
+await self.connect_server()
+while self.retry and current_retries < self.num_retries:
+LOGGER.info('Connection Failed, retrying in %d', cur_delay)
+status = f'[Retry #{current_retries} ({cur_delay}s)]'
+self._set_status(status)
+
+await asyncio.sleep(cur_delay)
+
+err = await self.connect_server()
+cur_delay *= inc_delay
+# Cap delay to 5mins
+cur_delay = min(cur_delay, 5 * 60)
+current_retries += 1
+# If all retries failed report the last error
+LOGGER.info('All retries failed: %s', str(err)

[PATCH v3 02/13] python: disable pylint errors for aqmp-tui

2021-07-30 Thread G S Niteesh Babu
Disable missing-docstring and fixme pylint warnings.
This is because since the AQMP is just a prototype
it is currently not documented properly and lot
of todo and fixme's are still in place.

Signed-off-by: G S Niteesh Babu 
---
 python/setup.cfg | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/python/setup.cfg b/python/setup.cfg
index 2573cd7bfb..7a30dd5b09 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -90,6 +90,8 @@ ignore_missing_imports = True
 # --disable=W".
 disable=too-many-function-args,  # mypy handles this with less false positives.
 no-member,  # mypy also handles this better.
+missing-docstring, # FIXME
+fixme, # FIXME
 
 [pylint.basic]
 # Good variable names which should always be accepted, separated by a comma.
-- 
2.17.1




[PATCH v3 06/13] python/aqmp-tui: Added type annotations for aqmp-tui

2021-07-30 Thread G S Niteesh Babu
This patch adds type annotations for aqmp-tui using
the mypy library.

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 79 
 python/setup.cfg |  3 --
 2 files changed, 43 insertions(+), 39 deletions(-)

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
index ec9eba0aa7..ab9ada793a 100644
--- a/python/qemu/aqmp/aqmp_tui.py
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -9,8 +9,15 @@
 import argparse
 import asyncio
 import logging
-from logging import Handler
+from logging import Handler, LogRecord
 import signal
+from typing import (
+Any,
+List,
+Optional,
+Tuple,
+Union,
+)
 
 import urwid
 import urwid_readline
@@ -22,13 +29,13 @@
 from .util import create_task, pretty_traceback
 
 
-UPDATE_MSG = 'UPDATE_MSG'
+UPDATE_MSG: str = 'UPDATE_MSG'
 
 # Using root logger to enable all loggers under qemu and asyncio
 LOGGER = logging.getLogger()
 
 
-def format_json(msg):
+def format_json(msg: str) -> str:
 """
 Formats given multiline JSON message into a single line message.
 Converting into single line is more asthetically pleasing when looking
@@ -43,17 +50,17 @@ def format_json(msg):
 
 
 class App(QMPClient):
-def __init__(self, address):
+def __init__(self, address: Union[str, Tuple[str, int]]) -> None:
 urwid.register_signal(type(self), UPDATE_MSG)
 self.window = Window(self)
 self.address = address
-self.aloop = None
+self.aloop: Optional[Any] = None  # FIXME: Use more concrete type.
 super().__init__()
 
-def add_to_history(self, msg):
+def add_to_history(self, msg: str) -> None:
 urwid.emit_signal(self, UPDATE_MSG, msg)
 
-def _cb_outbound(self, msg):
+def _cb_outbound(self, msg: Message) -> Message:
 # FIXME: I think the ideal way to omit these messages during in-TUI
 # logging will be to add a filter to the logger. We can use regex to
 # filter out messages starting with 'Request:' or 'Response:' but I
@@ -67,25 +74,25 @@ def _cb_outbound(self, msg):
 self.add_to_history('<-- ' + str(msg))
 return msg
 
-def _cb_inbound(self, msg):
+def _cb_inbound(self, msg: Message) -> Message:
 handler = LOGGER.handlers[0]
 if not isinstance(handler, TUILogHandler):
 LOGGER.debug('Response: %s', str(msg))
 self.add_to_history('--> ' + str(msg))
 return msg
 
-async def wait_for_events(self):
+async def wait_for_events(self) -> None:
 async for event in self.events:
 self.handle_event(event)
 
-async def _send_to_server(self, raw_msg):
+async def _send_to_server(self, raw_msg: str) -> None:
 # FIXME: Format the raw_msg in history view to one line. It is not
 # pleasing to see multiple lines JSON object with an error statement.
 try:
 msg = Message(bytes(raw_msg, encoding='utf-8'))
 # Format multiline json into a single line JSON, since it is more
 # pleasing to look along with err message in TUI.
-raw_msg = self.format_json(raw_msg)
+raw_msg = format_json(raw_msg)
 await self._raw(msg, assign_id='id' not in msg)
 except (ValueError, TypeError) as err:
 LOGGER.info('Invalid message: %s', str(err))
@@ -102,18 +109,18 @@ def _cb_inbound(self, msg):
 LOGGER.error('Exception from _send_to_server: %s', str(err))
 raise err
 
-def cb_send_to_server(self, msg):
+def cb_send_to_server(self, msg: str) -> None:
 create_task(self._send_to_server(msg))
 
-def unhandled_input(self, key):
+def unhandled_input(self, key: str) -> None:
 if key == 'esc':
 self.kill_app()
 
-def kill_app(self):
+def kill_app(self) -> None:
 # TODO: Work on the disconnect logic
 create_task(self._kill_app())
 
-async def _kill_app(self):
+async def _kill_app(self) -> None:
 # It is ok to call disconnect even in disconnect state
 try:
 await self.disconnect()
@@ -124,7 +131,7 @@ def kill_app(self):
 raise err
 raise urwid.ExitMainLoop()
 
-def handle_event(self, event):
+def handle_event(self, event: Message) -> None:
 # FIXME: Consider all states present in qapi/run-state.json
 if event['event'] == 'SHUTDOWN':
 self._set_status('Server shutdown')
@@ -139,7 +146,7 @@ def _get_formatted_address(self) -> str:
 addr = f'{host}:{port}'
 return addr
 
-async def connect_server(self):
+async def connect_server(self) -> None:
 try:
 await self.connect(self.address)
 addr = self._get_formatted_address()
@@ -148,7 +155,7 @@ def _get_formatted_address(self) -> str:
 LOGGER.info('connect_server: ConnectError %s', str

[PATCH v3 01/13] python/aqmp: Fix wait_closed work-around for python 3.6

2021-07-30 Thread G S Niteesh Babu
Before this patch the wait_closed work-around for python 3.6
fails during disconnect.
This is a temproray work around for which might be fixed in the
future or will be completely removed when the minimum python
version is raised to 3.7.

This patch was originally written by John Snow 

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/util.py | 12 +++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/python/qemu/aqmp/util.py b/python/qemu/aqmp/util.py
index de0df44cbd..eaa5fc7d5f 100644
--- a/python/qemu/aqmp/util.py
+++ b/python/qemu/aqmp/util.py
@@ -134,7 +134,17 @@ def is_closing(writer: asyncio.StreamWriter) -> bool:
 
 while not transport.is_closing():
 await asyncio.sleep(0)
-await flush(writer)
+
+# This is an ugly workaround, but it's the best I can come up with.
+sock = transport.get_extra_info('socket')
+
+if sock is None:
+# Our transport doesn't have a socket? ...
+# Nothing we can reasonably do.
+return
+
+while sock.fileno() != -1:
+await asyncio.sleep(0)
 
 
 def asyncio_run(coro: Coroutine[Any, Any, T], *, debug: bool = False) -> T:
-- 
2.17.1




[PATCH v3 00/13] AQMP TUI Draft

2021-07-30 Thread G S Niteesh Babu
Hello all,

Gitlab: https://gitlab.com/niteesh.gs/qemu/-/commits/aqmp-tui-prototype-v3
CI: https://gitlab.com/niteesh.gs/qemu/-/pipelines/345738265

Major revision since V2:
1) Refined QMP connection manager.
2) Added static typing.
3) Allow highlighting specific messages in history box.
4) Allow copying of QMP message from TUI using keyboard shortcut alt-c.

G S Niteesh Babu (13):
  python/aqmp: Fix wait_closed work-around for python 3.6
  python: disable pylint errors for aqmp-tui
  python: Add dependencies for AQMP TUI
  python/aqmp-tui: Add AQMP TUI draft
  python: add entry point for aqmp-tui
  python/aqmp-tui: Added type annotations for aqmp-tui
  python: add optional pygments dependency
  python/aqmp-tui: add syntax highlighting
  python/aqmp-tui: Add QMP connection manager
  python/aqmp-tui: Add scrolling to history box
  python/aqmp-tui: Add ability to highlight messages
  python/aqmp-tui: Add pyperclip dependency
  python/aqmp-tui: Allow copying message from TUI

 python/Pipfile.lock  |  42 +++
 python/qemu/aqmp/aqmp_tui.py | 519 +++
 python/qemu/aqmp/util.py |  12 +-
 python/setup.cfg |  34 ++-
 4 files changed, 605 insertions(+), 2 deletions(-)
 create mode 100644 python/qemu/aqmp/aqmp_tui.py

-- 
2.17.1




[PATCH v2 2/6] python: Add dependencies for AQMP TUI

2021-07-13 Thread G S Niteesh Babu
Added dependencies for the upcoming AQMP TUI under the optional
'tui' group.

The same dependencies have also been added under the devel group
since no work around has been found for optional groups to imply
other optional groups.

Signed-off-by: G S Niteesh Babu 
---
 python/Pipfile.lock | 12 
 python/setup.cfg|  7 +++
 2 files changed, 19 insertions(+)

diff --git a/python/Pipfile.lock b/python/Pipfile.lock
index 8ab41a3f60..76cf1e4930 100644
--- a/python/Pipfile.lock
+++ b/python/Pipfile.lock
@@ -289,6 +289,18 @@
 "markers": "python_version < '3.8'",
 "version": "==3.10.0.0"
 },
+"urwid": {
+"hashes": [
+
"sha256:588bee9c1cb208d0906a9f73c613d2bd32c3ed3702012f51efe318a3f2127eae"
+],
+"version": "==2.1.2"
+},
+"urwid-readline": {
+"hashes": [
+
"sha256:018020cbc864bb5ed87be17dc26b069eae2755cb29f3a9c569aac3bded1efaf4"
+],
+"version": "==0.13"
+},
 "virtualenv": {
 "hashes": [
 
"sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467",
diff --git a/python/setup.cfg b/python/setup.cfg
index 1a552d672a..c62803bffc 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -44,11 +44,18 @@ devel =
 mypy >= 0.770
 pylint >= 2.8.0
 tox >= 3.18.0
+urwid >= 2.1.2
+urwid-readline >= 0.13
 
 # Provides qom-fuse functionality
 fuse =
 fusepy >= 2.0.4
 
+# AQMP TUI dependencies
+tui =
+urwid >= 2.1.2
+urwid-readline >= 0.13
+
 [options.entry_points]
 console_scripts =
 qom = qemu.qmp.qom:main
-- 
2.17.1




[PATCH v2 0/6] python: AQMP-TUI Prototype

2021-07-13 Thread G S Niteesh Babu
GitLab: https://gitlab.com/niteesh.gs/qemu/-/commits/aqmp-tui-prototype-v1/
Based-on: <20210701041313.1696009-1-js...@redhat.com>
 [PATCH 00/20] python: introduce Asynchronous QMP package

Updates in V2:
1) Moved loop related initialization to 'run' function in 'App' class
2) Added a module logger with support in TUI log messages.
3) Corrected usage of logging.info and logging.debug
4) Added an option in setup.cfg to silent pylint regarding duplicate-code
4) Modified the arguments list to the TUI

NOTE: I am not able to get the pipelines running after the v2 changes.
I was only able to test the changes locally using *make check*.

This patch series introduces AQMP-TUI prototype. This prototype has been
helpfull in letting us try out different ideas and giving some insights
into things that we had to take care of in the upcoming TUI. It was also
helpfull in finding out bugs in the AQMP library.

The intent for this patch series is to get comments on the architectural
design of the prototype. These comments will lay down the foundation for
the upcoming TUI.

G S Niteesh Babu (6):
  python: disable pylint errors for aqmp-tui
  python: Add dependencies for AQMP TUI
  python/aqmp-tui: Add AQMP TUI draft
  python: add optional pygments dependency
  python/aqmp-tui: add syntax highlighting
  python: add entry point for aqmp-tui

 python/Pipfile.lock  |  20 ++
 python/qemu/aqmp/aqmp_tui.py | 342 +++
 python/setup.cfg |  36 +++-
 3 files changed, 397 insertions(+), 1 deletion(-)
 create mode 100644 python/qemu/aqmp/aqmp_tui.py

-- 
2.17.1




[PATCH v2 6/6] python: add entry point for aqmp-tui

2021-07-13 Thread G S Niteesh Babu
Add an entry point for aqmp-tui. This will allow it to be run from
the command line using "aqmp-tui localhost:1234"
More options available in the TUI can be found using "aqmp-tui -h"

Signed-off-by: G S Niteesh Babu 
---
 python/setup.cfg | 1 +
 1 file changed, 1 insertion(+)

diff --git a/python/setup.cfg b/python/setup.cfg
index 63f5156c03..082bb6d68b 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -68,6 +68,7 @@ console_scripts =
 qom-fuse = qemu.qmp.qom_fuse:QOMFuse.entry_point [fuse]
 qemu-ga-client = qemu.qmp.qemu_ga_client:main
 qmp-shell = qemu.qmp.qmp_shell:main
+aqmp-tui = qemu.aqmp.aqmp_tui:main
 
 [flake8]
 extend-ignore = E722  # Prefer pylint's bare-except checks to flake8's
-- 
2.17.1




[PATCH v2 5/6] python/aqmp-tui: add syntax highlighting

2021-07-13 Thread G S Niteesh Babu
Add syntax highlighting for the incoming and outgoing QMP messages.
This is achieved using the pygments module which was added in a
previous commit.

The current implementation is a really simple one which doesn't
allow for any configuration. In future this has to be improved
to allow for easier theme config using an external config of
some sort.

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 14 --
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
index f853efc1f5..9ee91f0e99 100644
--- a/python/qemu/aqmp/aqmp_tui.py
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -12,6 +12,8 @@
 from logging import Handler
 import signal
 
+from pygments import lexers
+from pygments import token as Token
 import urwid
 import urwid_readline
 
@@ -33,6 +35,11 @@
 (Token.Literal.Number.Integer, '', '', '', '#fa0', 'g7'),
 (Token.Literal.String.Double, '', '', '', '#6f6', 'g7'),
 (Token.Keyword.Constant, '', '', '', '#6af', 'g7'),
+('DEBUG', '', '', '', '#ddf', 'g7'),
+('INFO', '', '', '', 'g100', 'g7'),
+('WARNING', '', '', '', '#ff6', 'g7'),
+('ERROR', '', '', '', '#a00', 'g7'),
+('CRITICAL', '', '', '', '#a00', 'g7'),
 ('background', '', 'black', '', '', 'g7'),
 ]
 
@@ -133,7 +140,7 @@ def cb_add_to_history(self, msg, level=None):
 formatted = []
 if level:
 msg = f'[{level}]: {msg}'
-formatted.append(msg)
+formatted.append((level, msg))
 else:
 lexer = lexers.JsonLexer()  # pylint: disable=no-member
 for token in lexer.get_tokens(msg):
@@ -162,6 +169,7 @@ def __init__(self, address):
 self.address = address
 self.aloop = None
 self.loop = None
+self.screen = urwid.raw_display.Screen()
 super().__init__()
 
 def add_to_history(self, msg, level=None):
@@ -249,8 +257,10 @@ def run(self, debug=False):
 self.aloop.add_signal_handler(sig, self.kill_app)
 
 event_loop = urwid.AsyncioEventLoop(loop=self.aloop)
-self.loop = urwid.MainLoop(self.window,
+self.loop = urwid.MainLoop(urwid.AttrMap(self.window, 'background'),
unhandled_input=self.unhandled_input,
+   screen=self.screen,
+   palette=palette,
handle_mouse=True,
event_loop=event_loop)
 
-- 
2.17.1




[PATCH v2 4/6] python: add optional pygments dependency

2021-07-13 Thread G S Niteesh Babu
Added pygments as optional dependency for AQMP TUI.
This is required for the upcoming syntax highlighting feature
in AQMP TUI.
The dependency has also been added in the devel optional group.

Added mypy 'ignore_missing_imports' for pygments since it does
not have any type stubs.

Signed-off-by: G S Niteesh Babu 
---
 python/Pipfile.lock | 8 
 python/setup.cfg| 5 +
 2 files changed, 13 insertions(+)

diff --git a/python/Pipfile.lock b/python/Pipfile.lock
index 76cf1e4930..2c6d779348 100644
--- a/python/Pipfile.lock
+++ b/python/Pipfile.lock
@@ -200,6 +200,14 @@
 ],
 "version": "==2.0.0"
 },
+"pygments": {
+"hashes": [
+
"sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f",
+
"sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"
+],
+"markers": "python_version >= '3.5'",
+"version": "==2.9.0"
+},
 "pylint": {
 "hashes": [
 
"sha256:082a6d461b54f90eea49ca90fff4ee8b6e45e8029e5dbd72f6107ef84f3779c0",
diff --git a/python/setup.cfg b/python/setup.cfg
index 7e5aae66c7..63f5156c03 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -46,6 +46,7 @@ devel =
 tox >= 3.18.0
 urwid >= 2.1.2
 urwid-readline >= 0.13
+Pygments >= 2.9.0
 
 # Provides qom-fuse functionality
 fuse =
@@ -55,6 +56,7 @@ fuse =
 tui =
 urwid >= 2.1.2
 urwid-readline >= 0.13
+Pygments >= 2.9.0
 
 [options.entry_points]
 console_scripts =
@@ -99,6 +101,9 @@ ignore_missing_imports = True
 [mypy-urwid_readline]
 ignore_missing_imports = True
 
+[mypy-pygments]
+ignore_missing_imports = True
+
 [pylint.messages control]
 # Disable the message, report, category or checker with the given id(s). You
 # can either give multiple identifiers separated by comma (,) or put this
-- 
2.17.1




[PATCH v2 3/6] python/aqmp-tui: Add AQMP TUI draft

2021-07-13 Thread G S Niteesh Babu
Added a draft of AQMP TUI.

Implements the follwing basic features:
1) Command transmission/reception.
2) Shows events asynchronously.
3) Shows server status in the bottom status bar.

Also added necessary pylint, mypy configurations

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 332 +++
 python/setup.cfg |  21 ++-
 2 files changed, 352 insertions(+), 1 deletion(-)
 create mode 100644 python/qemu/aqmp/aqmp_tui.py

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
new file mode 100644
index 00..f853efc1f5
--- /dev/null
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -0,0 +1,332 @@
+# Copyright (c) 2021
+#
+# Authors:
+#  Niteesh Babu G S 
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or
+# later.  See the COPYING file in the top-level directory.
+
+import argparse
+import asyncio
+import logging
+from logging import Handler
+import signal
+
+import urwid
+import urwid_readline
+
+from .error import MultiException
+from .protocol import ConnectError
+from .qmp_protocol import QMP, ExecInterruptedError, ExecuteError
+from .util import create_task, pretty_traceback
+
+
+UPDATE_MSG = 'UPDATE_MSG'
+
+# Using root logger to enable all loggers under qemu and asyncio
+LOGGER = logging.getLogger()
+
+palette = [
+(Token.Punctuation, '', '', '', 'h15,bold', 'g7'),
+(Token.Text, '', '', '', '', 'g7'),
+(Token.Name.Tag, '', '', '', 'bold,#f88', 'g7'),
+(Token.Literal.Number.Integer, '', '', '', '#fa0', 'g7'),
+(Token.Literal.String.Double, '', '', '', '#6f6', 'g7'),
+(Token.Keyword.Constant, '', '', '', '#6af', 'g7'),
+('background', '', 'black', '', '', 'g7'),
+]
+
+
+class StatusBar(urwid.Text):
+"""
+A simple Text widget that currently only shows connection status.
+"""
+def __init__(self, text=''):
+super().__init__(text, align='right')
+
+
+class Editor(urwid_readline.ReadlineEdit):
+"""
+Support urwid_readline features along with
+history support which lacks in urwid_readline
+"""
+def __init__(self, master):
+super().__init__(caption='> ', multiline=True)
+self.master = master
+self.history = []
+self.last_index = -1
+self.show_history = False
+
+def keypress(self, size, key):
+# TODO: Add some logic for down key and clean up logic if possible.
+# Returning None means the key has been handled by this widget
+# which otherwise is propogated to the parent widget to be
+# handled
+msg = self.get_edit_text()
+if key == 'up' and not msg:
+# Show the history when 'up arrow' is pressed with no input text.
+# NOTE: The show_history logic is necessary because in 'multiline'
+# mode (which we use) 'up arrow' is used to move between lines.
+self.show_history = True
+last_msg = self.history[self.last_index] if self.history else ''
+self.set_edit_text(last_msg)
+self.edit_pos = len(last_msg)
+self.last_index += 1
+elif key == 'up' and self.show_history:
+if self.last_index < len(self.history):
+self.set_edit_text(self.history[self.last_index])
+self.edit_pos = len(self.history[self.last_index])
+self.last_index += 1
+elif key == 'meta enter':
+# When using multiline, enter inserts a new line into the editor
+# send the input to the server on alt + enter
+self.master.cb_send_to_server(msg)
+self.history.insert(0, msg)
+self.set_edit_text('')
+self.last_index = 0
+self.show_history = False
+else:
+self.show_history = False
+self.last_index = 0
+return super().keypress(size, key)
+return None
+
+
+class EditorWidget(urwid.Filler):
+"""
+Wraps CustomEdit
+"""
+def __init__(self, master):
+super().__init__(Editor(master), valign='top')
+
+
+class HistoryBox(urwid.ListBox):
+"""
+Shows all the QMP message transmitted/received
+"""
+def __init__(self, master):
+self.master = master
+self.history = urwid.SimpleFocusListWalker([])
+super().__init__(self.history)
+
+def add_to_history(self, history):
+self.history.append(urwid.Text(history))
+if self.history:
+self.history.set_focus(len(self.history) - 1)
+
+
+class HistoryWindow(urwid.Frame):
+"""
+Composes the HistoryBox and EditorWidget
+"""
+def __init__(self, master):
+self.master = master
+self.editor = EditorWidget(master)
+self.editor_widget = urwid.LineBox(self.editor)
+self.history = HistoryBox(master)
+ 

[PATCH v2 1/6] python: disable pylint errors for aqmp-tui

2021-07-13 Thread G S Niteesh Babu
Disable missing-docstring and fixme pylint warnings.
This is because since the AQMP is just a prototype
it is currently not documented properly and lot
of todo and fixme's are still in place.

Signed-off-by: G S Niteesh Babu 
---
 python/setup.cfg | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/python/setup.cfg b/python/setup.cfg
index bce8807702..1a552d672a 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -89,6 +89,8 @@ ignore_missing_imports = True
 # no Warning level messages displayed, use "--disable=all --enable=classes
 # --disable=W".
 disable=too-many-function-args,  # mypy handles this with less false positives.
+missing-docstring, # FIXME
+fixme, # FIXME
 
 [pylint.basic]
 # Good variable names which should always be accepted, separated by a comma.
-- 
2.17.1




[PATCH 6/6] python: add entry point for aqmp-tui

2021-07-02 Thread G S Niteesh Babu
Add an entry point for aqmp-tui. This will allow it to be run from
the command line using "aqmp-tui -a localhost:1234"

Signed-off-by: G S Niteesh Babu 
---
 python/setup.cfg | 1 +
 1 file changed, 1 insertion(+)

diff --git a/python/setup.cfg b/python/setup.cfg
index 4782fe5241..23e30185f4 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -68,6 +68,7 @@ console_scripts =
 qom-fuse = qemu.qmp.qom_fuse:QOMFuse.entry_point [fuse]
 qemu-ga-client = qemu.qmp.qemu_ga_client:main
 qmp-shell = qemu.qmp.qmp_shell:main
+aqmp-tui = qemu.aqmp.aqmp_tui:main
 
 [flake8]
 extend-ignore = E722  # Prefer pylint's bare-except checks to flake8's
-- 
2.17.1




[PATCH 3/6] python/aqmp-tui: Add AQMP TUI draft

2021-07-02 Thread G S Niteesh Babu
Added a draft of AQMP TUI.

Implements the follwing basic features:
1) Command transmission/reception.
2) Shows events asynchronously.
3) Shows server status in the bottom status bar.

Also added necessary pylint, mypy configurations

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 246 +++
 python/setup.cfg |  16 ++-
 2 files changed, 261 insertions(+), 1 deletion(-)
 create mode 100644 python/qemu/aqmp/aqmp_tui.py

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
new file mode 100644
index 00..8e9e8ac8ff
--- /dev/null
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -0,0 +1,246 @@
+# Copyright (c) 2021
+#
+# Authors:
+#  Niteesh Babu G S 
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or
+# later.  See the COPYING file in the top-level directory.
+
+import argparse
+import asyncio
+import logging
+import signal
+
+import urwid
+import urwid_readline
+
+from .protocol import ConnectError
+from .qmp_protocol import QMP, ExecInterruptedError, ExecuteError
+from .util import create_task, pretty_traceback
+
+
+UPDATE_MSG = 'UPDATE_MSG'
+
+
+class StatusBar(urwid.Text):
+"""
+A simple Text widget that currently only shows connection status.
+"""
+def __init__(self, text=''):
+super().__init__(text, align='right')
+
+
+class Editor(urwid_readline.ReadlineEdit):
+"""
+Support urwid_readline features along with
+history support which lacks in urwid_readline
+"""
+def __init__(self, master):
+super().__init__(caption='> ', multiline=True)
+self.master = master
+self.history = []
+self.last_index = -1
+self.show_history = False
+
+def keypress(self, size, key):
+# TODO: Add some logic for down key and clean up logic if possible.
+# Returning None means the key has been handled by this widget
+# which otherwise is propogated to the parent widget to be
+# handled
+msg = self.get_edit_text()
+if key == 'up' and not msg:
+# Show the history when 'up arrow' is pressed with no input text.
+# NOTE: The show_history logic is necessary because in 'multiline'
+# mode (which we use) 'up arrow' is used to move between lines.
+self.show_history = True
+last_msg = self.history[self.last_index] if self.history else ''
+self.set_edit_text(last_msg)
+self.edit_pos = len(last_msg)
+self.last_index += 1
+elif key == 'up' and self.show_history:
+if self.last_index < len(self.history):
+self.set_edit_text(self.history[self.last_index])
+self.edit_pos = len(self.history[self.last_index])
+self.last_index += 1
+elif key == 'meta enter':
+# When using multiline, enter inserts a new line into the editor
+# send the input to the server on alt + enter
+self.master.cb_send_to_server(msg)
+self.history.insert(0, msg)
+self.set_edit_text('')
+self.last_index = 0
+self.show_history = False
+else:
+self.show_history = False
+self.last_index = 0
+return super().keypress(size, key)
+return None
+
+
+class EditorWidget(urwid.Filler):
+"""
+Wraps CustomEdit
+"""
+def __init__(self, master):
+super().__init__(Editor(master), valign='top')
+
+
+class HistoryBox(urwid.ListBox):
+"""
+Shows all the QMP message transmitted/received
+"""
+def __init__(self, master):
+self.master = master
+self.history = urwid.SimpleFocusListWalker([])
+super().__init__(self.history)
+
+def add_to_history(self, history):
+self.history.append(urwid.Text(history))
+if self.history:
+self.history.set_focus(len(self.history) - 1)
+
+
+class HistoryWindow(urwid.Frame):
+"""
+Composes the HistoryBox and EditorWidget
+"""
+def __init__(self, master):
+self.master = master
+self.editor = EditorWidget(master)
+self.editor_widget = urwid.LineBox(self.editor)
+self.history = HistoryBox(master)
+self.body = urwid.Pile([('weight', 80, self.history),
+('weight', 10, self.editor_widget)])
+super().__init__(self.body)
+urwid.connect_signal(self.master, UPDATE_MSG, self.cb_add_to_history)
+
+def cb_add_to_history(self, msg):
+self.history.add_to_history(msg)
+
+
+class Window(urwid.Frame):
+"""
+This is going to be the main window that is going to compose other
+windows. In this stage it is unnecesssary but will be necessary in
+future when we will 

[PATCH 5/6] python/aqmp-tui: add syntax highlighting

2021-07-02 Thread G S Niteesh Babu
Add syntax highlighting for the incoming and outgoing QMP messages.
This is achieved using the pygments module which was added in a
previous commit.

The current implementation is a really simple one which doesn't
allow for any configuration. In future this has to be improved
to allow for easier theme config using an external config of
some sort.

Signed-off-by: G S Niteesh Babu 
---
 python/qemu/aqmp/aqmp_tui.py | 25 +++--
 1 file changed, 23 insertions(+), 2 deletions(-)

diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/aqmp/aqmp_tui.py
index 8e9e8ac8ff..03cb70a523 100644
--- a/python/qemu/aqmp/aqmp_tui.py
+++ b/python/qemu/aqmp/aqmp_tui.py
@@ -11,6 +11,8 @@
 import logging
 import signal
 
+from pygments import lexers
+from pygments import token as Token
 import urwid
 import urwid_readline
 
@@ -21,6 +23,16 @@
 
 UPDATE_MSG = 'UPDATE_MSG'
 
+palette = [
+(Token.Punctuation, '', '', '', 'h15,bold', 'g7'),
+(Token.Text, '', '', '', '', 'g7'),
+(Token.Name.Tag, '', '', '', 'bold,#f88', 'g7'),
+(Token.Literal.Number.Integer, '', '', '', '#fa0', 'g7'),
+(Token.Literal.String.Double, '', '', '', '#6f6', 'g7'),
+(Token.Keyword.Constant, '', '', '', '#6af', 'g7'),
+('background', '', 'black', '', '', 'g7'),
+]
+
 
 class StatusBar(urwid.Text):
 """
@@ -115,7 +127,11 @@ def __init__(self, master):
 urwid.connect_signal(self.master, UPDATE_MSG, self.cb_add_to_history)
 
 def cb_add_to_history(self, msg):
-self.history.add_to_history(msg)
+formatted = []
+lexer = lexers.JsonLexer()  # pylint: disable=no-member
+for token in lexer.get_tokens(msg):
+formatted.append(token)
+self.history.add_to_history(formatted)
 
 
 class Window(urwid.Frame):
@@ -139,6 +155,7 @@ def __init__(self, address):
 self.address = address
 self.aloop = asyncio.get_event_loop()
 self.loop = None
+self.screen = urwid.raw_display.Screen()
 super().__init__()
 
 # Gracefully handle SIGTERM and SIGINT signals
@@ -210,10 +227,14 @@ def handle_event(self, event):
 self.window.footer.set_text('Server shutdown')
 
 def run(self):
+self.screen.set_terminal_properties(256)
+
 self.aloop.set_debug(True)
 event_loop = urwid.AsyncioEventLoop(loop=self.aloop)
-self.loop = urwid.MainLoop(self.window,
+self.loop = urwid.MainLoop(urwid.AttrMap(self.window, 'background'),
unhandled_input=self.unhandled_input,
+   screen=self.screen,
+   palette=palette,
handle_mouse=True,
event_loop=event_loop)
 
-- 
2.17.1




[PATCH 2/6] python: Add dependencies for AQMP TUI

2021-07-02 Thread G S Niteesh Babu
Added dependencies for the upcoming AQMP TUI under the optional
'tui' group.

The same dependencies have also been added under the devel group
since no work around has been found for optional groups to imply
other optional groups.

Signed-off-by: G S Niteesh Babu 
---
 python/Pipfile.lock | 12 
 python/setup.cfg|  7 +++
 2 files changed, 19 insertions(+)

diff --git a/python/Pipfile.lock b/python/Pipfile.lock
index 8ab41a3f60..76cf1e4930 100644
--- a/python/Pipfile.lock
+++ b/python/Pipfile.lock
@@ -289,6 +289,18 @@
 "markers": "python_version < '3.8'",
 "version": "==3.10.0.0"
 },
+"urwid": {
+"hashes": [
+
"sha256:588bee9c1cb208d0906a9f73c613d2bd32c3ed3702012f51efe318a3f2127eae"
+],
+"version": "==2.1.2"
+},
+"urwid-readline": {
+"hashes": [
+
"sha256:018020cbc864bb5ed87be17dc26b069eae2755cb29f3a9c569aac3bded1efaf4"
+],
+"version": "==0.13"
+},
 "virtualenv": {
 "hashes": [
 
"sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467",
diff --git a/python/setup.cfg b/python/setup.cfg
index 1a552d672a..c62803bffc 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -44,11 +44,18 @@ devel =
 mypy >= 0.770
 pylint >= 2.8.0
 tox >= 3.18.0
+urwid >= 2.1.2
+urwid-readline >= 0.13
 
 # Provides qom-fuse functionality
 fuse =
 fusepy >= 2.0.4
 
+# AQMP TUI dependencies
+tui =
+urwid >= 2.1.2
+urwid-readline >= 0.13
+
 [options.entry_points]
 console_scripts =
 qom = qemu.qmp.qom:main
-- 
2.17.1




[PATCH 4/6] python: add optional pygments dependency

2021-07-02 Thread G S Niteesh Babu
Added pygments as optional dependency for AQMP TUI.
This is required for the upcoming syntax highlighting feature
in AQMP TUI.
The dependency has also been added in the devel optional group.

Added mypy 'ignore_missing_imports' for pygments since it does
not have any type stubs.

Signed-off-by: G S Niteesh Babu 
---
 python/Pipfile.lock | 8 
 python/setup.cfg| 5 +
 2 files changed, 13 insertions(+)

diff --git a/python/Pipfile.lock b/python/Pipfile.lock
index 76cf1e4930..2c6d779348 100644
--- a/python/Pipfile.lock
+++ b/python/Pipfile.lock
@@ -200,6 +200,14 @@
 ],
 "version": "==2.0.0"
 },
+"pygments": {
+"hashes": [
+
"sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f",
+
"sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"
+],
+"markers": "python_version >= '3.5'",
+"version": "==2.9.0"
+},
 "pylint": {
 "hashes": [
 
"sha256:082a6d461b54f90eea49ca90fff4ee8b6e45e8029e5dbd72f6107ef84f3779c0",
diff --git a/python/setup.cfg b/python/setup.cfg
index c6d38451eb..4782fe5241 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -46,6 +46,7 @@ devel =
 tox >= 3.18.0
 urwid >= 2.1.2
 urwid-readline >= 0.13
+Pygments >= 2.9.0
 
 # Provides qom-fuse functionality
 fuse =
@@ -55,6 +56,7 @@ fuse =
 tui =
 urwid >= 2.1.2
 urwid-readline >= 0.13
+Pygments >= 2.9.0
 
 [options.entry_points]
 console_scripts =
@@ -99,6 +101,9 @@ ignore_missing_imports = True
 [mypy-urwid_readline]
 ignore_missing_imports = True
 
+[mypy-pygments]
+ignore_missing_imports = True
+
 [pylint.messages control]
 # Disable the message, report, category or checker with the given id(s). You
 # can either give multiple identifiers separated by comma (,) or put this
-- 
2.17.1




[PATCH 0/6] python: AQMP-TUI Prototype

2021-07-02 Thread G S Niteesh Babu
GitLab: https://gitlab.com/niteesh.gs/qemu/-/commits/aqmp-tui-prototype-v1/
CI: https://gitlab.com/niteesh.gs/qemu/-/pipelines/330532044
Based-on: <20210701041313.1696009-1-js...@redhat.com>
 [PATCH 00/20] python: introduce Asynchronous QMP package

This patch series introduces AQMP-TUI prototype. This prototype has been
helpfull in letting us try out different ideas and giving some insights
into things that we had to take care of in the upcoming TUI. It was also
helpfull in finding out bugs in the AQMP library.

The intent for this patch series is to get comments on the architectural
design of the prototype. These comments will lay down the foundation for
the upcoming TUI.

G S Niteesh Babu (6):
  python: disable pylint errors for aqmp-tui
  python: Add dependencies for AQMP TUI
  python/aqmp-tui: Add AQMP TUI draft
  python: add optional pygments dependency
  python/aqmp-tui: add syntax highlighting
  python: add entry point for aqmp-tui

 python/Pipfile.lock  |  20 +++
 python/qemu/aqmp/aqmp_tui.py | 267 +++
 python/setup.cfg |  31 +++-
 3 files changed, 317 insertions(+), 1 deletion(-)
 create mode 100644 python/qemu/aqmp/aqmp_tui.py

-- 
2.17.1




[PATCH 1/6] python: disable pylint errors for aqmp-tui

2021-07-02 Thread G S Niteesh Babu
Disable missing-docstring and fixme pylint warnings.
This is because since the AQMP is just a prototype
it is currently not documented properly and lot
of todo and fixme's are still in place.

Signed-off-by: G S Niteesh Babu 
---
 python/setup.cfg | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/python/setup.cfg b/python/setup.cfg
index bce8807702..1a552d672a 100644
--- a/python/setup.cfg
+++ b/python/setup.cfg
@@ -89,6 +89,8 @@ ignore_missing_imports = True
 # no Warning level messages displayed, use "--disable=all --enable=classes
 # --disable=W".
 disable=too-many-function-args,  # mypy handles this with less false positives.
+missing-docstring, # FIXME
+fixme, # FIXME
 
 [pylint.basic]
 # Good variable names which should always be accepted, separated by a comma.
-- 
2.17.1




[PATCH 3/3] avr/arduino: Add D13 LED

2021-03-11 Thread G S Niteesh Babu
Signed-off-by: G S Niteesh Babu 
---
 hw/avr/Kconfig   |  1 +
 hw/avr/arduino.c | 15 +++
 2 files changed, 16 insertions(+)

diff --git a/hw/avr/Kconfig b/hw/avr/Kconfig
index 16a57ced11..e0d4fc5537 100644
--- a/hw/avr/Kconfig
+++ b/hw/avr/Kconfig
@@ -8,3 +8,4 @@ config AVR_ATMEGA_MCU
 config ARDUINO
 select AVR_ATMEGA_MCU
 select UNIMP
+select LED
diff --git a/hw/avr/arduino.c b/hw/avr/arduino.c
index 3c8388490d..5cdba3201c 100644
--- a/hw/avr/arduino.c
+++ b/hw/avr/arduino.c
@@ -13,6 +13,7 @@
 #include "qemu/osdep.h"
 #include "qapi/error.h"
 #include "hw/boards.h"
+#include "hw/misc/led.h"
 #include "atmega.h"
 #include "boot.h"
 #include "qom/object.h"
@@ -22,6 +23,8 @@ struct ArduinoMachineState {
 MachineState parent_obj;
 /*< public >*/
 AtmegaMcuState mcu;
+
+LEDState *onboard_led;
 };
 typedef struct ArduinoMachineState ArduinoMachineState;
 
@@ -49,6 +52,18 @@ static void arduino_machine_init(MachineState *machine)
  amc->xtal_hz, _abort);
 sysbus_realize(SYS_BUS_DEVICE(>mcu), _abort);
 
+ams->onboard_led = led_create_simple(OBJECT(ams),
+ GPIO_POLARITY_ACTIVE_HIGH,
+ LED_COLOR_BLUE,
+ "D13 LED");
+
+/* TODO: Add macro or function to map pins to ports */
+/* The onboard led is connected to PIN 13 in all boards currently supported
+ * in QEMU. And PIN 13 is mapped to PORT B BIT 5.
+ */
+qdev_connect_gpio_out(DEVICE(>mcu.gpio[1]), 5,
+  qdev_get_gpio_in(DEVICE(ams->onboard_led), 0));
+
 if (machine->firmware) {
 if (!avr_load_firmware(>mcu.cpu, machine,
>mcu.flash, machine->firmware)) {
-- 
2.17.1




[PATCH 1/3] hw/avr: Add limited support for avr gpio registers

2021-03-11 Thread G S Niteesh Babu
From: Heecheol Yang 

Add some of these features for AVR GPIO:

  - GPIO I/O : PORTx registers
  - Data Direction : DDRx registers
  - DDRx toggling : PINx registers

Following things are not supported yet:
  - MCUR registers

Signed-off-by: Heecheol Yang 
Signed-off-by: G S Niteesh Babu 
Message-id: 
dm6pr16mb2473d96c4d975de569a330f2e6...@dm6pr16mb2473.namprd16.prod.outlook.com
---
 hw/avr/Kconfig |   1 +
 hw/avr/atmega.c|   7 +-
 hw/avr/atmega.h|   2 +
 hw/gpio/Kconfig|   3 +
 hw/gpio/avr_gpio.c | 140 +
 hw/gpio/meson.build|   1 +
 include/hw/gpio/avr_gpio.h |  53 ++
 7 files changed, 205 insertions(+), 2 deletions(-)
 create mode 100644 hw/gpio/avr_gpio.c
 create mode 100644 include/hw/gpio/avr_gpio.h

diff --git a/hw/avr/Kconfig b/hw/avr/Kconfig
index d31298c3cc..16a57ced11 100644
--- a/hw/avr/Kconfig
+++ b/hw/avr/Kconfig
@@ -3,6 +3,7 @@ config AVR_ATMEGA_MCU
 select AVR_TIMER16
 select AVR_USART
 select AVR_POWER
+select AVR_GPIO
 
 config ARDUINO
 select AVR_ATMEGA_MCU
diff --git a/hw/avr/atmega.c b/hw/avr/atmega.c
index 44c6afebbb..ad942028fd 100644
--- a/hw/avr/atmega.c
+++ b/hw/avr/atmega.c
@@ -283,8 +283,11 @@ static void atmega_realize(DeviceState *dev, Error **errp)
 continue;
 }
 devname = g_strdup_printf("atmega-gpio-%c", 'a' + (char)i);
-create_unimplemented_device(devname,
-OFFSET_DATA + mc->dev[idx].addr, 3);
+object_initialize_child(OBJECT(dev), devname, >gpio[i],
+TYPE_AVR_GPIO);
+sysbus_realize(SYS_BUS_DEVICE(>gpio[i]), _abort);
+sysbus_mmio_map(SYS_BUS_DEVICE(>gpio[i]), 0,
+OFFSET_DATA + mc->dev[idx].addr);
 g_free(devname);
 }
 
diff --git a/hw/avr/atmega.h b/hw/avr/atmega.h
index a99ee15c7e..e2289d5744 100644
--- a/hw/avr/atmega.h
+++ b/hw/avr/atmega.h
@@ -13,6 +13,7 @@
 
 #include "hw/char/avr_usart.h"
 #include "hw/timer/avr_timer16.h"
+#include "hw/gpio/avr_gpio.h"
 #include "hw/misc/avr_power.h"
 #include "target/avr/cpu.h"
 #include "qom/object.h"
@@ -44,6 +45,7 @@ struct AtmegaMcuState {
 DeviceState *io;
 AVRMaskState pwr[POWER_MAX];
 AVRUsartState usart[USART_MAX];
+AVRGPIOState gpio[GPIO_MAX];
 AVRTimer16State timer[TIMER_MAX];
 uint64_t xtal_freq_hz;
 };
diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig
index f0e7405f6e..fde7019b2b 100644
--- a/hw/gpio/Kconfig
+++ b/hw/gpio/Kconfig
@@ -13,3 +13,6 @@ config GPIO_PWR
 
 config SIFIVE_GPIO
 bool
+
+config AVR_GPIO
+bool
diff --git a/hw/gpio/avr_gpio.c b/hw/gpio/avr_gpio.c
new file mode 100644
index 00..8fc192dbcb
--- /dev/null
+++ b/hw/gpio/avr_gpio.c
@@ -0,0 +1,140 @@
+/*
+ * AVR processors GPIO registers emulation.
+ *
+ * Copyright (C) 2020 Heecheol Yang 
+ * Copyright (C) 2021 Niteesh Babu G S 
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "hw/irq.h"
+#include "hw/gpio/avr_gpio.h"
+#include "hw/qdev-properties.h"
+
+static void avr_gpio_reset(DeviceState *dev)
+{
+AVRGPIOState *gpio = AVR_GPIO(dev);
+gpio->reg.pin = 0u;
+gpio->reg.ddr = 0u;
+gpio->reg.port = 0u;
+}
+
+static void avr_gpio_write_port(AVRGPIOState *s, uint64_t value)
+{
+uint8_t pin;
+uint8_t org_val = value;
+uint8_t cur_port_val = s->reg.port;
+uint8_t cur_ddr_val = s->reg.ddr;
+
+for (pin = 0u; pin < 8u ; pin++) {
+uint8_t cur_port_pin_val = cur_port_val & 0x01u;
+uint8_t cur_ddr_pin_val = cur_ddr_val & 0x01u;
+uint8_t new_port_pin_val = value & 0x01u;
+
+if (cur_ddr_pin_val && (cur_port_pin_val != new_port_pin_val)) {
+qemu_set_irq(s->out[pin], new_port_pin_val);
+}
+cur_port_val >>= 1u;
+cur_ddr_val >>= 1u;
+value >>= 1u;
+}
+s->reg.port = org_val & s->reg.ddr;
+}
+static uint64_t avr_gpio_read(void *opaque, h

[PATCH 0/3] AVR GPIO Emulation and Arduino D13 LED

2021-03-11 Thread G S Niteesh Babu
Hello,

The following series of the patches add a basic AVR GPIO emulation
to QEMU. The AVR GPIO emulation patch was originally written by
Heecheol Yang and was posted on the mailing list around 5 months ago.
I am re-publishing the patch with some bug fixes along with my own
patch, Adding the D13 onboard LED.

G S Niteesh Babu (2):
  hw/gpio/avr_gpio.c: add tracing for read and writes
  avr/arduino: Add D13 LED

Heecheol Yang (1):
  hw/avr: Add limited support for avr gpio registers

 hw/avr/Kconfig |   2 +
 hw/avr/arduino.c   |  15 
 hw/avr/atmega.c|   7 +-
 hw/avr/atmega.h|   2 +
 hw/gpio/Kconfig|   3 +
 hw/gpio/avr_gpio.c | 148 +
 hw/gpio/meson.build|   1 +
 hw/gpio/trace-events   |   6 ++
 include/hw/gpio/avr_gpio.h |  53 +
 9 files changed, 235 insertions(+), 2 deletions(-)
 create mode 100644 hw/gpio/avr_gpio.c
 create mode 100644 include/hw/gpio/avr_gpio.h

-- 
2.17.1




[PATCH 2/3] hw/gpio/avr_gpio.c: add tracing for read and writes

2021-03-11 Thread G S Niteesh Babu
Added tracing for gpio read, write, and update output irq.

1) trace_avr_gpio_update_ouput_irq
2) trace_avr_gpio_read
3) trace_avr_gpio_write

Signed-off-by: G S Niteesh Babu 
---
 hw/gpio/avr_gpio.c   | 16 
 hw/gpio/trace-events |  6 ++
 2 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/hw/gpio/avr_gpio.c b/hw/gpio/avr_gpio.c
index 8fc192dbcb..8498a99dd2 100644
--- a/hw/gpio/avr_gpio.c
+++ b/hw/gpio/avr_gpio.c
@@ -26,6 +26,7 @@
 #include "hw/irq.h"
 #include "hw/gpio/avr_gpio.h"
 #include "hw/qdev-properties.h"
+#include "trace.h"
 
 static void avr_gpio_reset(DeviceState *dev)
 {
@@ -49,6 +50,7 @@ static void avr_gpio_write_port(AVRGPIOState *s, uint64_t 
value)
 
 if (cur_ddr_pin_val && (cur_port_pin_val != new_port_pin_val)) {
 qemu_set_irq(s->out[pin], new_port_pin_val);
+trace_avr_gpio_update_output_irq(pin, new_port_pin_val);
 }
 cur_port_val >>= 1u;
 cur_ddr_val >>= 1u;
@@ -58,19 +60,25 @@ static void avr_gpio_write_port(AVRGPIOState *s, uint64_t 
value)
 }
 static uint64_t avr_gpio_read(void *opaque, hwaddr offset, unsigned int size)
 {
+uint8_t val = 0;
 AVRGPIOState *s = (AVRGPIOState *)opaque;
 switch (offset) {
 case GPIO_PIN:
-return s->reg.pin;
+val = s->reg.pin;
+break;
 case GPIO_DDR:
-return s->reg.ddr;
+val = s->reg.ddr;
+break;
 case GPIO_PORT:
-return s->reg.port;
+val = s->reg.port;
+break;
 default:
 g_assert_not_reached();
 break;
 }
-return 0;
+
+trace_avr_gpio_read(offset, val);
+return val;
 }
 
 static void avr_gpio_write(void *opaque, hwaddr offset, uint64_t value,
diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events
index 46ab9323bd..a054def07c 100644
--- a/hw/gpio/trace-events
+++ b/hw/gpio/trace-events
@@ -18,3 +18,9 @@ sifive_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" 
PRIx64 " value 0x%" P
 sifive_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 
0x%" PRIx64
 sifive_gpio_set(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
 sifive_gpio_update_output_irq(int64_t line, int64_t value) "line %" PRIi64 " 
value %" PRIi64
+
+# avr_gpio.c
+avr_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" PRIx64 " value 0x%" 
PRIx64
+avr_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 
0x%" PRIx64
+avr_gpio_set(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
+avr_gpio_update_output_irq(int64_t line, int64_t value) "pin %" PRIi64 " value 
%" PRIi64
-- 
2.17.1




[PATCH 3/3] hw/gpio/avr_gpio.c: add tracing for read and writes

2021-03-02 Thread G S Niteesh Babu
Added tracing for gpio read, write, and update output irq.

1) trace_avr_gpio_update_ouput_irq
2) trace_avr_gpio_read
3) trace_avr_gpio_write

Signed-off-by: G S Niteesh Babu 
---
 hw/gpio/avr_gpio.c   | 16 
 hw/gpio/trace-events |  6 ++
 2 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/hw/gpio/avr_gpio.c b/hw/gpio/avr_gpio.c
index 7984843841..5c0d6aa922 100644
--- a/hw/gpio/avr_gpio.c
+++ b/hw/gpio/avr_gpio.c
@@ -25,6 +25,7 @@
 #include "hw/irq.h"
 #include "hw/gpio/avr_gpio.h"
 #include "hw/qdev-properties.h"
+#include "trace.h"
 
 static void avr_gpio_reset(DeviceState *dev)
 {
@@ -48,6 +49,7 @@ static void avr_gpio_write_port(AVRGPIOState *s, uint64_t 
value)
 
 if (cur_ddr_pin_val && (cur_port_pin_val != new_port_pin_val)) {
 qemu_set_irq(s->out[pin], new_port_pin_val);
+trace_avr_gpio_update_output_irq(pin, new_port_pin_val);
 }
 cur_port_val >>= 1u;
 cur_ddr_val >>= 1u;
@@ -57,19 +59,25 @@ static void avr_gpio_write_port(AVRGPIOState *s, uint64_t 
value)
 }
 static uint64_t avr_gpio_read(void *opaque, hwaddr offset, unsigned int size)
 {
+uint8_t val = 0;
 AVRGPIOState *s = (AVRGPIOState *)opaque;
 switch (offset) {
 case GPIO_PIN:
-return s->reg.pin;
+val = s->reg.pin;
+break;
 case GPIO_DDR:
-return s->reg.ddr;
+val = s->reg.ddr;
+break;
 case GPIO_PORT:
-return s->reg.port;
+val = s->reg.port;
+break;
 default:
 g_assert_not_reached();
 break;
 }
-return 0;
+
+trace_avr_gpio_read(offset, val);
+return val;
 }
 
 static void avr_gpio_write(void *opaque, hwaddr offset, uint64_t value,
diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events
index 46ab9323bd..a054def07c 100644
--- a/hw/gpio/trace-events
+++ b/hw/gpio/trace-events
@@ -18,3 +18,9 @@ sifive_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" 
PRIx64 " value 0x%" P
 sifive_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 
0x%" PRIx64
 sifive_gpio_set(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
 sifive_gpio_update_output_irq(int64_t line, int64_t value) "line %" PRIi64 " 
value %" PRIi64
+
+# avr_gpio.c
+avr_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" PRIx64 " value 0x%" 
PRIx64
+avr_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 
0x%" PRIx64
+avr_gpio_set(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
+avr_gpio_update_output_irq(int64_t line, int64_t value) "pin %" PRIi64 " value 
%" PRIi64
-- 
2.17.1




[PATCH 2/3] avr/arduino: Add D13 LED

2021-03-02 Thread G S Niteesh Babu
Signed-off-by: G S Niteesh Babu 
---
 hw/avr/Kconfig   |  1 +
 hw/avr/arduino.c | 15 +++
 2 files changed, 16 insertions(+)

diff --git a/hw/avr/Kconfig b/hw/avr/Kconfig
index 16a57ced11..e0d4fc5537 100644
--- a/hw/avr/Kconfig
+++ b/hw/avr/Kconfig
@@ -8,3 +8,4 @@ config AVR_ATMEGA_MCU
 config ARDUINO
 select AVR_ATMEGA_MCU
 select UNIMP
+select LED
diff --git a/hw/avr/arduino.c b/hw/avr/arduino.c
index 3c8388490d..5cdba3201c 100644
--- a/hw/avr/arduino.c
+++ b/hw/avr/arduino.c
@@ -13,6 +13,7 @@
 #include "qemu/osdep.h"
 #include "qapi/error.h"
 #include "hw/boards.h"
+#include "hw/misc/led.h"
 #include "atmega.h"
 #include "boot.h"
 #include "qom/object.h"
@@ -22,6 +23,8 @@ struct ArduinoMachineState {
 MachineState parent_obj;
 /*< public >*/
 AtmegaMcuState mcu;
+
+LEDState *onboard_led;
 };
 typedef struct ArduinoMachineState ArduinoMachineState;
 
@@ -49,6 +52,18 @@ static void arduino_machine_init(MachineState *machine)
  amc->xtal_hz, _abort);
 sysbus_realize(SYS_BUS_DEVICE(>mcu), _abort);
 
+ams->onboard_led = led_create_simple(OBJECT(ams),
+ GPIO_POLARITY_ACTIVE_HIGH,
+ LED_COLOR_BLUE,
+ "D13 LED");
+
+/* TODO: Add macro or function to map pins to ports */
+/* The onboard led is connected to PIN 13 in all boards currently supported
+ * in QEMU. And PIN 13 is mapped to PORT B BIT 5.
+ */
+qdev_connect_gpio_out(DEVICE(>mcu.gpio[1]), 5,
+  qdev_get_gpio_in(DEVICE(ams->onboard_led), 0));
+
 if (machine->firmware) {
 if (!avr_load_firmware(>mcu.cpu, machine,
>mcu.flash, machine->firmware)) {
-- 
2.17.1




[PATCH 1/3] hw/avr: Add limited support for avr gpio registers

2021-03-02 Thread G S Niteesh Babu
From: Heecheol Yang 

Add some of these features for AVR GPIO:

  - GPIO I/O : PORTx registers
  - Data Direction : DDRx registers
  - DDRx toggling : PINx registers

Following things are not supported yet:
  - MCUR registers

Signed-off-by: Heecheol Yang 
---
 hw/avr/Kconfig |   1 +
 hw/avr/atmega.c|   7 +-
 hw/avr/atmega.h|   2 +
 hw/gpio/Kconfig|   3 +
 hw/gpio/avr_gpio.c | 139 +
 hw/gpio/meson.build|   1 +
 include/hw/gpio/avr_gpio.h |  53 ++
 7 files changed, 204 insertions(+), 2 deletions(-)
 create mode 100644 hw/gpio/avr_gpio.c
 create mode 100644 include/hw/gpio/avr_gpio.h

diff --git a/hw/avr/Kconfig b/hw/avr/Kconfig
index d31298c3cc..16a57ced11 100644
--- a/hw/avr/Kconfig
+++ b/hw/avr/Kconfig
@@ -3,6 +3,7 @@ config AVR_ATMEGA_MCU
 select AVR_TIMER16
 select AVR_USART
 select AVR_POWER
+select AVR_GPIO
 
 config ARDUINO
 select AVR_ATMEGA_MCU
diff --git a/hw/avr/atmega.c b/hw/avr/atmega.c
index 44c6afebbb..ad942028fd 100644
--- a/hw/avr/atmega.c
+++ b/hw/avr/atmega.c
@@ -283,8 +283,11 @@ static void atmega_realize(DeviceState *dev, Error **errp)
 continue;
 }
 devname = g_strdup_printf("atmega-gpio-%c", 'a' + (char)i);
-create_unimplemented_device(devname,
-OFFSET_DATA + mc->dev[idx].addr, 3);
+object_initialize_child(OBJECT(dev), devname, >gpio[i],
+TYPE_AVR_GPIO);
+sysbus_realize(SYS_BUS_DEVICE(>gpio[i]), _abort);
+sysbus_mmio_map(SYS_BUS_DEVICE(>gpio[i]), 0,
+OFFSET_DATA + mc->dev[idx].addr);
 g_free(devname);
 }
 
diff --git a/hw/avr/atmega.h b/hw/avr/atmega.h
index a99ee15c7e..e2289d5744 100644
--- a/hw/avr/atmega.h
+++ b/hw/avr/atmega.h
@@ -13,6 +13,7 @@
 
 #include "hw/char/avr_usart.h"
 #include "hw/timer/avr_timer16.h"
+#include "hw/gpio/avr_gpio.h"
 #include "hw/misc/avr_power.h"
 #include "target/avr/cpu.h"
 #include "qom/object.h"
@@ -44,6 +45,7 @@ struct AtmegaMcuState {
 DeviceState *io;
 AVRMaskState pwr[POWER_MAX];
 AVRUsartState usart[USART_MAX];
+AVRGPIOState gpio[GPIO_MAX];
 AVRTimer16State timer[TIMER_MAX];
 uint64_t xtal_freq_hz;
 };
diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig
index f0e7405f6e..fde7019b2b 100644
--- a/hw/gpio/Kconfig
+++ b/hw/gpio/Kconfig
@@ -13,3 +13,6 @@ config GPIO_PWR
 
 config SIFIVE_GPIO
 bool
+
+config AVR_GPIO
+bool
diff --git a/hw/gpio/avr_gpio.c b/hw/gpio/avr_gpio.c
new file mode 100644
index 00..7984843841
--- /dev/null
+++ b/hw/gpio/avr_gpio.c
@@ -0,0 +1,139 @@
+/*
+ * AVR processors GPIO registers emulation.
+ *
+ * Copyright (C) 2020 Heecheol Yang 
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see .
+ */
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "hw/irq.h"
+#include "hw/gpio/avr_gpio.h"
+#include "hw/qdev-properties.h"
+
+static void avr_gpio_reset(DeviceState *dev)
+{
+AVRGPIOState *gpio = AVR_GPIO(dev);
+gpio->reg.pin = 0u;
+gpio->reg.ddr = 0u;
+gpio->reg.port = 0u;
+}
+
+static void avr_gpio_write_port(AVRGPIOState *s, uint64_t value)
+{
+uint8_t pin;
+uint8_t org_val = value;
+uint8_t cur_port_val = s->reg.port;
+uint8_t cur_ddr_val = s->reg.ddr;
+
+for (pin = 0u; pin < 8u ; pin++) {
+uint8_t cur_port_pin_val = cur_port_val & 0x01u;
+uint8_t cur_ddr_pin_val = cur_ddr_val & 0x01u;
+uint8_t new_port_pin_val = value & 0x01u;
+
+if (cur_ddr_pin_val && (cur_port_pin_val != new_port_pin_val)) {
+qemu_set_irq(s->out[pin], new_port_pin_val);
+}
+cur_port_val >>= 1u;
+cur_ddr_val >>= 1u;
+value >>= 1u;
+}
+s->reg.port = org_val & s->reg.ddr;
+}
+static uint64_t avr_gpio_read(void *opaque, hwaddr offset, unsigned int size)
+{
+AVRGPIOState *s = (AVRGPIOState *)opaque;
+switch (offset) {
+case GPIO_PIN:
+return s->reg.pin;
+case GPIO_DDR:
+return s->reg.ddr;
+case GPIO_PORT:
+return s->reg.port;
+default:
+g_assert_not_reached();
+break;
+}
+return 0;
+}
+
+static void avr_gpio_write(void *opaque, hwaddr offset, uint64_t value,
+

[PATCH] block/iscsi: Use lock guards

2021-02-06 Thread G S Niteesh Babu
---
 block/iscsi.c | 37 -
 1 file changed, 12 insertions(+), 25 deletions(-)

diff --git a/block/iscsi.c b/block/iscsi.c
index 4d2a416ce7..4f7abc665a 100644
--- a/block/iscsi.c
+++ b/block/iscsi.c
@@ -48,6 +48,7 @@
 #include "crypto/secret.h"
 #include "scsi/utils.h"
 #include "trace.h"
+#include "qemu/lockable.h"
 
 /* Conflict between scsi/utils.h and libiscsi! :( */
 #define SCSI_XFER_NONE ISCSI_XFER_NONE
@@ -399,10 +400,9 @@ iscsi_process_read(void *arg)
 IscsiLun *iscsilun = arg;
 struct iscsi_context *iscsi = iscsilun->iscsi;
 
-qemu_mutex_lock(>mutex);
+QEMU_LOCK_GUARD(>mutex);
 iscsi_service(iscsi, POLLIN);
 iscsi_set_events(iscsilun);
-qemu_mutex_unlock(>mutex);
 }
 
 static void
@@ -411,10 +411,9 @@ iscsi_process_write(void *arg)
 IscsiLun *iscsilun = arg;
 struct iscsi_context *iscsi = iscsilun->iscsi;
 
-qemu_mutex_lock(>mutex);
+QEMU_LOCK_GUARD(>mutex);
 iscsi_service(iscsi, POLLOUT);
 iscsi_set_events(iscsilun);
-qemu_mutex_unlock(>mutex);
 }
 
 static int64_t sector_lun2qemu(int64_t sector, IscsiLun *iscsilun)
@@ -623,7 +622,7 @@ iscsi_co_writev(BlockDriverState *bs, int64_t sector_num, 
int nb_sectors,
 lba = sector_qemu2lun(sector_num, iscsilun);
 num_sectors = sector_qemu2lun(nb_sectors, iscsilun);
 iscsi_co_init_iscsitask(iscsilun, );
-qemu_mutex_lock(>mutex);
+QEMU_LOCK_GUARD(>mutex);
 retry:
 if (iscsilun->use_16_for_rw) {
 #if LIBISCSI_API_VERSION >= (20160603)
@@ -652,7 +651,6 @@ retry:
 }
 #endif
 if (iTask.task == NULL) {
-qemu_mutex_unlock(>mutex);
 return -ENOMEM;
 }
 #if LIBISCSI_API_VERSION < (20160603)
@@ -684,7 +682,6 @@ retry:
  nb_sectors * BDRV_SECTOR_SIZE);
 
 out_unlock:
-qemu_mutex_unlock(>mutex);
 g_free(iTask.err_str);
 return r;
 }
@@ -723,7 +720,7 @@ static int coroutine_fn 
iscsi_co_block_status(BlockDriverState *bs,
 lba = offset / iscsilun->block_size;
 max_bytes = (iscsilun->num_blocks - lba) * iscsilun->block_size;
 
-qemu_mutex_lock(>mutex);
+QEMU_LOCK_GUARD(>mutex);
 retry:
 if (iscsi_get_lba_status_task(iscsilun->iscsi, iscsilun->lun,
   lba, 8 + 16, iscsi_co_generic_cb,
@@ -785,7 +782,6 @@ retry:
 *pnum = bytes;
 }
 out_unlock:
-qemu_mutex_unlock(>mutex);
 g_free(iTask.err_str);
 out:
 if (iTask.task != NULL) {
@@ -858,7 +854,7 @@ static int coroutine_fn iscsi_co_readv(BlockDriverState *bs,
 num_sectors = sector_qemu2lun(nb_sectors, iscsilun);
 
 iscsi_co_init_iscsitask(iscsilun, );
-qemu_mutex_lock(>mutex);
+QEMU_LOCK_GUARD(>mutex);
 retry:
 if (iscsilun->use_16_for_rw) {
 #if LIBISCSI_API_VERSION >= (20160603)
@@ -889,7 +885,6 @@ retry:
 }
 #endif
 if (iTask.task == NULL) {
-qemu_mutex_unlock(>mutex);
 return -ENOMEM;
 }
 #if LIBISCSI_API_VERSION < (20160603)
@@ -913,7 +908,6 @@ retry:
 r = iTask.err_code;
 }
 
-qemu_mutex_unlock(>mutex);
 g_free(iTask.err_str);
 return r;
 }
@@ -925,11 +919,10 @@ static int coroutine_fn iscsi_co_flush(BlockDriverState 
*bs)
 int r = 0;
 
 iscsi_co_init_iscsitask(iscsilun, );
-qemu_mutex_lock(>mutex);
+QEMU_LOCK_GUARD(>mutex);
 retry:
 if (iscsi_synchronizecache10_task(iscsilun->iscsi, iscsilun->lun, 0, 0, 0,
   0, iscsi_co_generic_cb, ) == NULL) 
{
-qemu_mutex_unlock(>mutex);
 return -ENOMEM;
 }
 
@@ -950,7 +943,6 @@ retry:
 r = iTask.err_code;
 }
 
-qemu_mutex_unlock(>mutex);
 g_free(iTask.err_str);
 return r;
 }
@@ -1085,7 +1077,8 @@ static BlockAIOCB *iscsi_aio_ioctl(BlockDriverState *bs,
 acb->task->expxferlen = acb->ioh->dxfer_len;
 
 data.size = 0;
-qemu_mutex_lock(>mutex);
+
+QEMU_LOCK_GUARD(>mutex);
 if (acb->task->xfer_dir == SCSI_XFER_WRITE) {
 if (acb->ioh->iovec_count == 0) {
 data.data = acb->ioh->dxferp;
@@ -1101,7 +1094,6 @@ static BlockAIOCB *iscsi_aio_ioctl(BlockDriverState *bs,
  iscsi_aio_ioctl_cb,
  (data.size > 0) ?  : NULL,
  acb) != 0) {
-qemu_mutex_unlock(>mutex);
 scsi_free_scsi_task(acb->task);
 qemu_aio_unref(acb);
 return NULL;
@@ -1121,7 +1113,6 @@ static BlockAIOCB *iscsi_aio_ioctl(BlockDriverState *bs,
 }
 
 iscsi_set_events(iscsilun);
-qemu_mutex_unlock(>mutex);
 
 return >common;
 }
@@ -1161,7 +1152,7 @@ coroutine_fn iscsi_co_pdiscard(BlockDriverState *bs, 
int64_t offset, int bytes)
 list.num = bytes / iscsilun->block_size;
 
 iscsi_co_init_iscsitask(iscsilun, );
-qemu_mutex_lock(>mutex);
+QEMU_LOCK_GUARD(>mutex);
 retry:
 if (iscsi_unmap_task(iscsilun->iscsi, iscsilun->lun, 0, 0, , 1,
  iscsi_co_generic_cb, ) == NULL) {
@@ -1198,7