Quick search

Table Of Contents

Source code for kivy.modules.console

# coding=utf-8

.. versionadded:: 1.9.1

Reboot of the old inspector, designed to be modular and keep concerns
separated. It also has an addons architecture that allow you to add a button,
panel, or more in the Console itself.

.. warning::

    This module works, but might fail in some cases. Please contribute!


For normal module usage, please see the :mod:`~kivy.modules` documentation::

    python -m console

Mouse navigation

When the "Select" button is activated, you can:

- tap once on a widget to select it without leaving inspect mode
- double tap on a widget to select and leave inspect mode (then you can
  manipulate the widget again)

Keyboard navigation

- "Ctrl + e": toggle console
- "Escape": cancel widget lookup, then hide inspector view
- "Up": select the parent widget
- "Down": select the first child of the currently selected widget
- "Left": select the previous sibling
- "Right": select the next sibling

Additional information

Some properties can be edited live. However, due to the delayed usage of
some properties, it might crash if you don't handle the required cases.


Addons must be added to `Console.addons` before the first Clock tick of the
application, or before :attr:`create_console` is called. You currently cannot
add addons on the fly. Addons are quite cheap until the Console is activated.
Panels are even cheaper as nothing is done until the user selects them.

We provide multiple addons activated by default:

- ConsoleAddonFps: display the FPS at the top-right
- ConsoleAddonSelect: activate the selection mode
- ConsoleAddonBreadcrumb: display the hierarchy of the current widget at the
- ConsoleAddonWidgetTree: panel to display the widget tree of the application
- ConsoleAddonWidgetPanel: panel to display the properties of the selected

If you need to add custom widgets in the Console, please use either
:class:`ConsoleButton`, :class:`ConsoleToggleButton` or :class:`ConsoleLabel`.

An addon must inherit from the :class:`ConsoleAddon` class.

For example, here is a simple addon for displaying the FPS at the top/right
of the Console::

    from kivy.modules.console import Console, ConsoleAddon

    class ConsoleAddonFps(ConsoleAddon):
        def init(self):
            self.lbl = ConsoleLabel(text="0 Fps")
            self.console.add_toolbar_widget(self.lbl, right=True)

        def activate(self):
            self.event = Clock.schedule_interval(self.update_fps, 1 / 2.)

        def deactivated(self):

        def update_fps(self, *args):
            fps = Clock.get_fps()
            self.lbl.text = "{} Fps".format(int(fps))


You can create addons that add panels. Panel activation/deactivation is not
tied to the addon activation/deactivation, but in some cases, you can use the
same callback for deactivating the addon and the panel. Here is a simple
"About" panel addon::

    from kivy.modules.console import Console, ConsoleAddon, ConsoleLabel

    class ConsoleAddonAbout(ConsoleAddon):
        def init(self):
            self.console.add_panel("About", self.panel_activate,

        def panel_activate(self):

        def panel_deactivate(self):

        def deactivate(self):

        def update_content(self, *args):
            widget = self.console.widget
            if not widget:
            text = "Selected widget is: {!r}".format(widget)
            lbl = ConsoleLabel(text=text)



__all__ = ("start", "stop", "create_console", "Console", "ConsoleAddon",
           "ConsoleButton", "ConsoleToggleButton", "ConsoleLabel")

import kivy

import weakref
from functools import partial
from itertools import chain
from kivy.logger import Logger
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.image import Image
from kivy.uix.treeview import TreeViewNode, TreeView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.modalview import ModalView
from import Color, Rectangle, PushMatrix, PopMatrix
from import Transform
from import Matrix
from import (ObjectProperty, BooleanProperty, ListProperty,
                             NumericProperty, StringProperty, OptionProperty,
                             ReferenceListProperty, AliasProperty,
from import Texture
from kivy.clock import Clock
from kivy.lang import Builder

    size_hint: (1, None) if self.mode == "docked" else (None, None)
    height: dp(250)

            rgb: .185, .18, .18
            size: self.size
            rgb: .3, .3, .3
            pos: 0, self.height - dp(48)
            size: self.width, dp(48)

        cols: 1
        id: layout

            id: toolbar
            rows: 1
            height: "48dp"
            size_hint_y: None
            padding: "4dp"
            spacing: "4dp"

            id: content

    size_hint_x: None
    width: "10dp"

    size_hint_x: None
    width: self.texture_size[0] + dp(20)

    size_hint_y: None
    height: "48dp"
            rgb: .3, .3, .3
            size: self.size
        id: sv
        do_scroll_y: False
            id: stack
            rows: 1
            size_hint_x: None
            width: self.minimum_width
            padding: "4dp"
            spacing: "4dp"

    height: max(dp(48), max(lkey.texture_size[1], ltext.texture_size[1]))
        id: lkey
        text: root.key
        text_size: (self.width, None)
        width: 150
        size_hint_x: None
        id: ltext
        text: [repr(getattr(root.widget, root.key, '')), root.refresh][0]\
                if root.widget else ''
        text_size: (self.width, None)

        scroll_type: ['bars', 'content']
        bar_width: '10dp'

            id: widgettree
            hide_root: True
            size_hint: None, None
            height: self.minimum_height
            width: max(self.parent.width, self.minimum_width)
            selected_widget: root.widget
            on_select_widget: root.console.highlight_widget(args[1])

    height: self.texture_size[1] + sp(4)
    size_hint_x: None
    width: self.texture_size[0] + sp(4)

            rgba: self.color_selected if self.is_selected else (0, 0, 0, 0)
            pos: self.pos
            size: self.size
            rgba: 1, 1, 1, int(not self.is_leaf)
                ('atlas://data/images/defaulttheme/tree_%s' %
                ('opened' if self.is_open else 'closed'))
            size: 16, 16
            pos: self.x - 20, self.center_y - 8

                (self.disabled_color if self.disabled else
                (self.color if not self.markup else (1, 1, 1, 1)))
            texture: self.texture
            size: self.texture_size
                (int(self.center_x - self.texture_size[0] / 2.),
                int(self.center_y - self.texture_size[1] / 2.))


def ignore_exception(f):
    def f2(*args, **kwargs):
            return f(*args, **kwargs)

    return f2

class TreeViewProperty(BoxLayout, TreeViewNode):
    key = ObjectProperty(None, allownone=True)
    refresh = BooleanProperty(False)
    widget_ref = ObjectProperty(None, allownone=True)

    def _get_widget(self):
        wr = self.widget_ref
        if wr is None:
            return None
        wr = wr()
        if wr is None:
            self.widget_ref = None
            return None
        return wr

    widget = AliasProperty(_get_widget, None, bind=('widget_ref', ))

[docs]class ConsoleButton(Button): """Button specialized for the Console""" pass
[docs]class ConsoleToggleButton(ToggleButton): """ToggleButton specialized for the Console""" pass
[docs]class ConsoleLabel(Label): """LabelButton specialized for the Console""" pass
class ConsoleAddonSeparator(Widget): pass
[docs]class ConsoleAddon(object): """Base class for implementing addons""" #: Console instance console = None def __init__(self, console): super(ConsoleAddon, self).__init__() self.console = console self.init()
[docs] def init(self): """Method called when the addon is instantiated by the Console """ pass
[docs] def activate(self): """Method called when the addon is activated by the console (when the console is displayed)""" pass
[docs] def deactivate(self): """Method called when the addon is deactivated by the console (when the console is hidden) """ pass
class ConsoleAddonMode(ConsoleAddon): def init(self): btn = ConsoleToggleButton(text=u"Docked") self.console.add_toolbar_widget(btn) class ConsoleAddonSelect(ConsoleAddon): def init(self): self.btn = ConsoleToggleButton(text=u"Select") self.btn.bind(state=self.on_button_state) self.console.add_toolbar_widget(self.btn) self.console.bind(inspect_enabled=self.on_inspect_enabled) def on_inspect_enabled(self, instance, value): self.btn.state = "down" if value else "normal" def on_button_state(self, instance, value): self.console.inspect_enabled = (value == "down") class ConsoleAddonFps(ConsoleAddon): _update_ev = None def init(self): self.lbl = ConsoleLabel(text="0 Fps") self.console.add_toolbar_widget(self.lbl, right=True) def activate(self): ev = self._update_ev if ev is None: self._update_ev = Clock.schedule_interval(self.update_fps, 1 / 2.) else: ev() def deactivated(self): if self._update_ev is not None: self._update_ev.cancel() def update_fps(self, *args): fps = Clock.get_fps() self.lbl.text = "{} Fps".format(int(fps)) class ConsoleAddonBreadcrumbView(RelativeLayout): widget = ObjectProperty(None, allownone=True) parents = [] def on_widget(self, instance, value): stack = self.ids.stack # determine if we can just highlight the current one # or if we need to rebuild the breadcrumb prefs = [btn.widget_ref() for btn in self.parents] if value in prefs: # ok, so just toggle this one instead. index = prefs.index(value) for btn in self.parents: btn.state = "normal" self.parents[index].state = "down" return # we need to rebuild the breadcrumb. stack.clear_widgets() if not value: return widget = value parents = [] while True: btn = ConsoleButton(text=widget.__class__.__name__) btn.widget_ref = weakref.ref(widget) btn.bind(on_release=self.highlight_widget) parents.append(btn) if widget == widget.parent: break widget = widget.parent for btn in reversed(parents): stack.add_widget(btn) = 1 self.parents = parents btn.state = "down" def highlight_widget(self, instance): self.console.widget = instance.widget_ref() class ConsoleAddonBreadcrumb(ConsoleAddon): def init(self): self.view = ConsoleAddonBreadcrumbView() self.view.console = self.console self.console.ids.layout.add_widget(self.view) def activate(self): self.console.bind(widget=self.update_content) self.update_content() def deactivate(self): self.console.unbind(widget=self.update_content) def update_content(self, *args): self.view.widget = self.console.widget class ConsoleAddonWidgetPanel(ConsoleAddon): def init(self): self.console.add_panel("Properties", self.panel_activate, self.deactivate) def panel_activate(self): self.console.bind(widget=self.update_content) self.update_content() def deactivate(self): self.console.unbind(widget=self.update_content) def update_content(self, *args): widget = self.console.widget if not widget: return from kivy.uix.scrollview import ScrollView self.root = root = BoxLayout() = sv = ScrollView(scroll_type=["bars", "content"], bar_width='10dp') treeview = TreeView(hide_root=True, size_hint_y=None) treeview.bind(minimum_height=treeview.setter("height")) keys = list( keys.sort() node = None wk_widget = weakref.ref(widget) for key in keys: node = TreeViewProperty(key=key, widget_ref=wk_widget) node.bind(is_selected=self.show_property) try: widget.bind(**{ key: partial(self.update_node_content, weakref.ref(node)) }) except: pass treeview.add_node(node) root.add_widget(sv) sv.add_widget(treeview) self.console.set_content(root) def show_property(self, instance, value, key=None, index=-1, *l): # normal call: (tree node, focus, ) # nested call: (widget, prop value, prop key, index in dict/list) if value is False: return console = self.console content = None if key is None: # normal call nested = False widget = instance.widget key = instance.key prop = value = getattr(widget, key) else: # nested call, we might edit subvalue nested = True widget = instance prop = None dtype = None if isinstance(prop, AliasProperty) or nested: # trying to resolve type dynamically if type(value) in (str, str): dtype = 'string' elif type(value) in (int, float): dtype = 'numeric' elif type(value) in (tuple, list): dtype = 'list' if isinstance(prop, NumericProperty) or dtype == 'numeric': content = TextInput(text=str(value) or '', multiline=False) content.bind( text=partial(self.save_property_numeric, widget, key, index)) elif isinstance(prop, StringProperty) or dtype == 'string': content = TextInput(text=value or '', multiline=True) content.bind( text=partial(self.save_property_text, widget, key, index)) elif (isinstance(prop, ListProperty) or isinstance(prop, ReferenceListProperty) or isinstance(prop, VariableListProperty) or dtype == 'list'): content = GridLayout(cols=1, size_hint_y=None) content.bind(minimum_height=content.setter('height')) for i, item in enumerate(value): button = Button(text=repr(item), size_hint_y=None, height=44) if isinstance(item, Widget): button.bind(on_release=partial(console.highlight_widget, item, False)) else: button.bind(on_release=partial(self.show_property, widget, item, key, i)) content.add_widget(button) elif isinstance(prop, OptionProperty): content = GridLayout(cols=1, size_hint_y=None) content.bind(minimum_height=content.setter('height')) for option in prop.options: button = ToggleButton( text=option, state='down' if option == value else 'normal', group=repr(content.uid), size_hint_y=None, height=44) button.bind( on_press=partial(self.save_property_option, widget, key)) content.add_widget(button) elif isinstance(prop, ObjectProperty): if isinstance(value, Widget): content = Button(text=repr(value)) content.bind( on_release=partial(console.highlight_widget, value)) elif isinstance(value, Texture): content = Image(texture=value) else: content = Label(text=repr(value)) elif isinstance(prop, BooleanProperty): state = 'down' if value else 'normal' content = ToggleButton(text=key, state=state) content.bind(on_release=partial(self.save_property_boolean, widget, key, index)) self.root.clear_widgets() self.root.add_widget( if content: self.root.add_widget(content) @ignore_exception def save_property_numeric(self, widget, key, index, instance, value): if index >= 0: getattr(widget, key)[index] = float(instance.text) else: setattr(widget, key, float(instance.text)) @ignore_exception def save_property_text(self, widget, key, index, instance, value): if index >= 0: getattr(widget, key)[index] = instance.text else: setattr(widget, key, instance.text) @ignore_exception def save_property_boolean(self, widget, key, index, instance, ): value = instance.state == 'down' if index >= 0: getattr(widget, key)[index] = value else: setattr(widget, key, value) @ignore_exception def save_property_option(self, widget, key, instance, *l): setattr(widget, key, instance.text) class TreeViewWidget(Label, TreeViewNode): widget = ObjectProperty(None) class ConsoleAddonWidgetTreeImpl(TreeView): selected_widget = ObjectProperty(None, allownone=True) __events__ = ('on_select_widget', ) def __init__(self, **kwargs): super(ConsoleAddonWidgetTreeImpl, self).__init__(**kwargs) self.update_scroll = Clock.create_trigger(self._update_scroll) def find_node_by_widget(self, widget): for node in self.iterate_all_nodes(): if not node.parent_node: continue try: if node.widget == widget: return node except ReferenceError: pass return None def update_selected_widget(self, widget): if widget: node = self.find_node_by_widget(widget) if node: self.select_node(node, False) while node and isinstance(node, TreeViewWidget): if not node.is_open: self.toggle_node(node) node = node.parent_node def on_selected_widget(self, inst, widget): if widget: self.update_selected_widget(widget) self.update_scroll() def select_node(self, node, select_widget=True): super(ConsoleAddonWidgetTreeImpl, self).select_node(node) if select_widget: try: self.dispatch("on_select_widget", node.widget.__self__) except ReferenceError: pass def on_select_widget(self, widget): pass def _update_scroll(self, *args): node = self._selected_node if not node: return self.parent.scroll_to(node) class ConsoleAddonWidgetTreeView(RelativeLayout): widget = ObjectProperty(None, allownone=True) _window_node = None def _update_widget_tree_node(self, node, widget, is_open=False): tree = self.ids.widgettree update_nodes = [] nodes = {} for cnode in node.nodes[:]: try: nodes[cnode.widget] = cnode except ReferenceError: # widget no longer exists, just remove it pass tree.remove_node(cnode) for child in widget.children: if isinstance(child, Console): continue if child in nodes: cnode = tree.add_node(nodes[child], node) else: cnode = tree.add_node( TreeViewWidget(text=child.__class__.__name__, widget=child.proxy_ref, is_open=is_open), node) update_nodes.append((cnode, child)) return update_nodes def update_widget_tree(self, *args): win = if not self._window_node: self._window_node = self.ids.widgettree.add_node( TreeViewWidget(text="Window", widget=win, is_open=True)) nodes = self._update_widget_tree_node(self._window_node, win, is_open=True) while nodes: ntmp = nodes[:] nodes = [] for node in ntmp: nodes += self._update_widget_tree_node(*node) self.ids.widgettree.update_selected_widget(self.widget) class ConsoleAddonWidgetTree(ConsoleAddon): def init(self): self.content = None self.console.add_panel("Tree", self.panel_activate, self.deactivate, self.panel_refresh) def panel_activate(self): self.console.bind(widget=self.update_content) self.update_content() def deactivate(self): if self.content: self.content.widget = None self.content.console = None self.console.unbind(widget=self.update_content) def update_content(self, *args): widget = self.console.widget if not self.content: self.content = ConsoleAddonWidgetTreeView() self.content.console = self.console self.content.widget = widget self.content.update_widget_tree() self.console.set_content(self.content) def panel_refresh(self): if self.content: self.content.update_widget_tree()
[docs]class Console(RelativeLayout): """Console interface This widget is created by create_console(), when the module is loaded. During that time, you can add addons on the console to extend the functionalities, or add your own application stats / debugging module. """ #: Array of addons that will be created at Console creation addons = [ # ConsoleAddonMode, ConsoleAddonSelect, ConsoleAddonFps, ConsoleAddonWidgetPanel, ConsoleAddonWidgetTree, ConsoleAddonBreadcrumb] #: Display mode of the Console, either docked at the bottom, or as a #: floating window. mode = OptionProperty("docked", options=["docked", "floated"]) #: Current widget being selected widget = ObjectProperty(None, allownone=True) #: Indicate if the inspector inspection is enabled. If yes, the next #: touch down will select a the widget under the touch inspect_enabled = BooleanProperty(False) #: True if the Console is activated (showed) activated = BooleanProperty(False) def __init__(self, **kwargs): = kwargs.pop('win', None) super(Console, self).__init__(**kwargs) self.avoid_bring_to_top = False with self.canvas.before: self.gcolor = Color(1, 0, 0, .25) PushMatrix() self.gtransform = Transform(Matrix()) self.grect = Rectangle(size=(0, 0)) PopMatrix() Clock.schedule_interval(self.update_widget_graphics, 0) # instantiate all addons self._toolbar = {"left": [], "panels": [], "right": []} self._addons = [] self._panel = None for addon in self.addons: instance = addon(self) self._addons.append(instance) self._init_toolbar() # select the first panel self._panel = self._toolbar["panels"][0] self._panel.state = "down" self._panel.cb_activate() def _init_toolbar(self): toolbar = self.ids.toolbar for key in ("left", "panels", "right"): if key == "right": toolbar.add_widget(Widget()) for el in self._toolbar[key]: toolbar.add_widget(el) if key != "right": toolbar.add_widget(ConsoleAddonSeparator()) @classmethod def register_addon(cls, addon): cls.addons.append(addon)
[docs] def add_toolbar_widget(self, widget, right=False): """Add a widget in the top left toolbar of the Console. Use `right=True` if you wanna add the widget at the right instead. """ key = "right" if right else "left" self._toolbar[key].append(widget)
[docs] def remove_toolbar_widget(self, widget): """Remove a widget from the toolbar """ self.ids.toolbar.remove_widget(widget)
[docs] def add_panel(self, name, cb_activate, cb_deactivate, cb_refresh=None): """Add a new panel in the Console. - `cb_activate` is a callable that will be called when the panel is activated by the user. - `cb_deactivate` is a callable that will be called when the panel is deactivated or when the console will hide. - `cb_refresh` is an optional callable that is called if the user click again on the button for display the panel When activated, it's up to the panel to display a content in the Console by using :meth:`set_content`. """ btn = ConsoleToggleButton(text=name) btn.cb_activate = cb_activate btn.cb_deactivate = cb_deactivate btn.cb_refresh = cb_refresh btn.bind(on_press=self._activate_panel) self._toolbar["panels"].append(btn)
def _activate_panel(self, instance): if self._panel != instance: self._panel.cb_deactivate() self._panel.state = "normal" self.ids.content.clear_widgets() self._panel = instance self._panel.cb_activate() self._panel.state = "down" else: self._panel.state = "down" if self._panel.cb_refresh: self._panel.cb_refresh()
[docs] def set_content(self, content): """Replace the Console content with a new one. """ self.ids.content.clear_widgets() self.ids.content.add_widget(content)
[docs] def on_touch_down(self, touch): ret = super(Console, self).on_touch_down(touch) if (('button' not in touch.profile or touch.button == 'left') and not ret and self.inspect_enabled): self.highlight_at(*touch.pos) if touch.is_double_tap: self.inspect_enabled = False ret = True else: ret = self.collide_point(*touch.pos) return ret
[docs] def on_touch_move(self, touch): ret = super(Console, self).on_touch_move(touch) if not ret and self.inspect_enabled: self.highlight_at(*touch.pos) ret = True return ret
[docs] def on_touch_up(self, touch): ret = super(Console, self).on_touch_up(touch) if not ret and self.inspect_enabled: ret = True return ret
def on_window_children(self, win, children): if self.avoid_bring_to_top or not self.activated: return self.avoid_bring_to_top = True win.remove_widget(self) win.add_widget(self) self.avoid_bring_to_top = False
[docs] def highlight_at(self, x, y): """Select a widget from a x/y window coordinate. This is mostly used internally when Select mode is activated """ widget = None # reverse the loop - look at children on top first and # modalviews before others win_children = children = chain((c for c in reversed(win_children) if isinstance(c, ModalView)), (c for c in reversed(win_children) if not isinstance(c, ModalView))) for child in children: if child is self: continue widget = self.pick(child, x, y) if widget: break self.highlight_widget(widget)
def highlight_widget(self, widget, *largs): # no widget to highlight, reduce rectangle to 0, 0 self.widget = widget if not widget: self.grect.size = 0, 0 def update_widget_graphics(self, *l): if not self.activated: return if self.widget is None: self.grect.size = 0, 0 return self.grect.size = self.widget.size matrix = self.widget.get_window_matrix() if self.gtransform.matrix.get() != matrix.get(): self.gtransform.matrix = matrix
[docs] def pick(self, widget, x, y): """Pick a widget at x/y, given a root `widget` """ ret = None # try to filter widgets that are not visible (invalid inspect target) if (hasattr(widget, 'visible') and not widget.visible): return ret if widget.collide_point(x, y): ret = widget x2, y2 = widget.to_local(x, y) # reverse the loop - look at children on top first for child in reversed(widget.children): ret = self.pick(child, x2, y2) or ret return ret
def on_activated(self, instance, activated): if activated: self._activate_console() else: self._deactivate_console() def _activate_console(self): if self not in self.y = 0 for addon in self._addons: addon.activate()'Console: console activated') def _deactivate_console(self): for addon in self._addons: addon.deactivate() self.grect.size = 0, 0 self.y = -self.height self.widget = None self.inspect_enabled = False # self._window_node = None'Console: console deactivated') def keyboard_shortcut(self, win, scancode, *largs): modifiers = largs[-1] if scancode == 101 and modifiers == ['ctrl']: self.activated = not self.activated if self.activated: self.inspect_enabled = True return True elif scancode == 27: if self.inspect_enabled: self.inspect_enabled = False return True if self.activated: self.activated = False return True if not self.activated or not self.widget: return if scancode == 273: # top self.widget = self.widget.parent elif scancode == 274: # down filtered_children = [c for c in self.widget.children if not isinstance(c, Console)] if filtered_children: self.widget = filtered_children[0] elif scancode == 276: # left parent = self.widget.parent filtered_children = [c for c in parent.children if not isinstance(c, Console)] index = filtered_children.index(self.widget) index = max(0, index - 1) self.widget = filtered_children[index] elif scancode == 275: # right parent = self.widget.parent filtered_children = [c for c in parent.children if not isinstance(c, Console)] index = filtered_children.index(self.widget) index = min(len(filtered_children) - 1, index + 1) self.widget = filtered_children[index]
def create_console(win, ctx, *l): ctx.console = Console(win=win) win.bind(children=ctx.console.on_window_children, on_keyboard=ctx.console.keyboard_shortcut)
[docs]def start(win, ctx): """Create an Console instance attached to the *ctx* and bound to the Window's :meth:`~kivy.core.window.WindowBase.on_keyboard` event for capturing the keyboard shortcut. :Parameters: `win`: A :class:`Window <kivy.core.window.WindowBase>` The application Window to bind to. `ctx`: A :class:`~kivy.uix.widget.Widget` or subclass The Widget to be inspected. """ Clock.schedule_once(partial(create_console, win, ctx))
[docs]def stop(win, ctx): """Stop and unload any active Inspectors for the given *ctx*.""" if hasattr(ctx, "console"): win.unbind(children=ctx.console.on_window_children, on_keyboard=ctx.console.keyboard_shortcut) win.remove_widget(ctx.console) del ctx.console