From patchwork Mon Oct 5 08:36:22 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marcel Hellwig X-Patchwork-Id: 99983 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0E634CA5FCE for ; Mon, 5 Oct 2026 08:36:43 +0000 (UTC) Received: from mout-p-102.mailbox.org (mout-p-102.mailbox.org [80.241.56.152]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.15449.1791189398789425771 for ; Mon, 05 Oct 2026 01:36:39 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: body hash did not verify" header.i=@cookiesoft.de header.s=MBO0001 header.b=z2pcMMTZ; spf=pass (domain: cookiesoft.de, ip: 80.241.56.152, mailfrom: git@cookiesoft.de) Received: from smtp1.mailbox.org (smtp1.mailbox.org [10.196.197.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-p-102.mailbox.org (Postfix) with ESMTPS id 4hyt5w1Qg7zKp6g; Mon, 05 Oct 2026 10:36:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cookiesoft.de; s=MBO0001; t=1791189396; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding; bh=hg/6YG7hehAghfwo0ah4LKKsmaYVBxlRtBoIcUCsyns=; b=z2pcMMTZ68ZG34F4n5zLdi/fbWE8NFPCTTYPpsFNWRIUmXLnQbOOG7IxxyLDIieMzQ9Sur rnH+iw+euHIVpeZ6B3NHzkxZQLDtNV/ob6NhbG8HsZLitfOx42BVumwRrqNxiQATKhGpog AV5Sh7p4zzijB67s8aIr0r7x3/umjIH0o2M/i2nRedRb+xrBbzI0hCQLVXSSwDFMeP+xLR GF+aGUG2XtK1ShYRxyHriW683VZpYWmySBgubnpTezw71DpbKBmb+mW7oB5afDB7ezVLWF ++kXXXxxPjQB8WOtTlPddStNzMlM8MrDfZi39JIYpn8xYcW63oKGGMTmjR6m+g== From: git@cookiesoft.de To: bitbake-devel@lists.openembedded.org Cc: Marcel Hellwig Subject: [PATCH] knotty: add OSC 9;4 sequence for progress display Date: Mon, 5 Oct 2026 10:36:22 +0200 Message-ID: <20261005083628.1451356-1-git@cookiesoft.de> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Mon, 05 Oct 2026 08:36:43 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/bitbake-devel/message/20294 From: Marcel Hellwig OSC 9;4[0] allows the terminal emulator to display a progress bar. This is helpful for the long-running tasks in both Bitbake and Devtool. The most prominent supporters are Windows Terminal and KDE's Konsole. Terminals that do not support the sequence simply ignore it, so no special treament is required. [0]: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences Signed-off-by: Marcel Hellwig --- bitbake/lib/bb/ui/knotty.py | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/bitbake/lib/bb/ui/knotty.py b/bitbake/lib/bb/ui/knotty.py index 5198e93d8d..b7388f2948 100644 --- a/bitbake/lib/bb/ui/knotty.py +++ b/bitbake/lib/bb/ui/knotty.py @@ -119,6 +119,58 @@ class InteractConsoleLogFilter(logging.Filter): self.tf.clearFooter() return True +class Osc94FilterNotifier(logging.Filter): + def __init__(self, tf): + self.tf = tf + + def filter(self, record): + if record.levelno == bb.msg.BBLogFormatter.ERROR: + self.tf.osc94.error() + elif record.levelno == bb.msg.BBLogFormatter.WARNING: + self.tf.osc94.warning() + + return True + +class Osc94(object): + REMOVE = 0 + SET = 1 + ERROR = 2 + INDETERMINATE = 3 + WARNING = 4 + + def __init__(self, fd = None): + self.fd = fd + self._warning = False + self._error = False + + def _write(self, what, argument = None): + if self.fd is not None: + argument = "" if argument is None else argument + self.fd.write(f"\x1b]9;4;{what};{argument}\x07") + self.fd.flush() + + def clear(self): + self._write(self.REMOVE) + + def indeterminate(self): + self._write(self.INDETERMINATE) + + def error(self): + self._error = True + + def warning(self): + self._warning = True + + def progress(self, value): + value = int(min(max(value, 0), 100)) + if self._error: + self._write(self.ERROR, value) + elif self._warning: + self._write(self.WARNING, value) + else: + self._write(self.SET, value) + + class TerminalFilter(object): rows = 25 columns = 80 @@ -160,6 +212,7 @@ class TerminalFilter(object): self.lastpids = [] self.lasttime = None self.quiet = quiet + self.osc94 = Osc94(sys.stdout) if not self.interactive: return @@ -203,8 +256,10 @@ class TerminalFilter(object): for h in handlers: h.addFilter(InteractConsoleLogFilter(self)) + h.addFilter(Osc94FilterNotifier(self)) self.main_progress = None + self.osc94.indeterminate() def clearFooter(self): if self.footer_present: @@ -274,6 +329,7 @@ class TerminalFilter(object): tasks.append(msg) if self.main.shutdown: + self.osc94.indeterminate() content = pluralise("Waiting for %s running task to finish", "Waiting for %s running tasks to finish", len(activetasks)) if not self.quiet: @@ -303,6 +359,7 @@ class TerminalFilter(object): self.main_progress.setmessage(msg) progress = max(0, self.helper.tasknumber_current - 1) content += self.main_progress.update(progress) + self.osc94.progress(self.main_progress.percent) print('') lines = self.getlines(content) if not self.quiet: @@ -335,6 +392,7 @@ class TerminalFilter(object): return lines def finish(self): + self.osc94.clear() if self.stdinbackup: fd = sys.stdin.fileno() self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup)