diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 8e21548..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.bak -out/ diff --git a/copret/project.scala b/copret/project.scala index 441be75..21b9863 100644 --- a/copret/project.scala +++ b/copret/project.scala @@ -4,8 +4,7 @@ //> using publish.organization de.qwertyuiop //> using publish.name copret //> using publish.version 0.0.2 -//> using dep org.typelevel::cats-core:2.12.0 -//> using dep org.jline:jline:3.28.0 -//> using dep com.lihaoyi::os-lib:0.11.3 +//> using dep org.typelevel::cats-core:2.10.0 +//> using dep org.jline:jline:3.26.1 +//> using dep com.lihaoyi::os-lib:0.10.1 //> using dep com.lihaoyi::fansi:0.5.0 -//> using test.dep com.lihaoyi::utest:0.8.4 diff --git a/copret/src/Presentation.scala b/copret/src/Presentation.scala index 8ea705e..a978cf5 100644 --- a/copret/src/Presentation.scala +++ b/copret/src/Presentation.scala @@ -93,10 +93,10 @@ case class Presentation( case Clear => Terminal.clear() case PauseKey => waitkey(using Keymap.empty) case Pause(msec) => Thread.sleep(msec) + case incMd @ IncludeMarkdown(_) => println(incMd.markdownBlock()) case Image(file, None) => println(KittyGraphicsProtocol.showImage(file)) case Image(file, Some(ImageSize(w, h, aspect))) => - import KittyGraphicsProtocol.Sizing.* - println(KittyGraphicsProtocol.showImage(file, Absolute(w.toInt), Absolute(h.toInt), aspect)) // TODO + println(KittyGraphicsProtocol.showImage(file)) // TODO case cmd: TypedCommand[_] => cmd.show() case Silent(actions) => actions() case Group(slides) => slides.foreach(executeSlide(pos)) @@ -116,10 +116,10 @@ case class Presentation( def executeSilent(pos: Int)( slide: Slide = slides(pos), ): Unit = slide match - case cmd: TypedCommand[_] => cmd.force() - case Group(slides) => slides.foreach(executeSilent(pos)) - case lios @ LazyIOSlide(_, display) => + case cmd: TypedCommand[_] => cmd.force() + case Group(slides) => slides.foreach(executeSilent(pos)) + case lios @ LazyIOSlide(_, display) => executeSilent(pos)(lios.genSlide()) - case Paragraph(_) | Image(_, _) | Clear | Meta(_) => () - case _ => executeQuick(pos)(slide) + case Paragraph(_) | Image(_, _) | Clear | IncludeMarkdown(_) | Meta(_) => () + case _ => executeQuick(pos)(slide) end Presentation diff --git a/copret/src/images.scala b/copret/src/images.scala index cb44aa4..15c862c 100644 --- a/copret/src/images.scala +++ b/copret/src/images.scala @@ -1,8 +1,6 @@ package de.qwertyuiop.copret import Terminal.* -import scala.util.Try -import javax.imageio.ImageIO object KittyGraphicsProtocol: val MaxID = 4294967295L // max 32-bit unsigned @@ -11,104 +9,24 @@ object KittyGraphicsProtocol: queryTerm(s"${apc}Gi=${KittyGraphicsProtocol.MaxID},s=1,v=1,a=q,t=d,f=24;AAAA${st}${csi}c") .contains(s"${apc}Gi=${KittyGraphicsProtocol.MaxID}") - def imageSize(img: os.Path) = - Try(ImageIO.read(img.toIO)).toOption.map: image => - SizePx(image.getWidth, image.getHeight) - - enum Sizing: - case Absolute(pixels: Int) - case Relative(ratio: Double) - case Infer - - case class SizePx(width: Int, height: Int) - case class SizeCells(width: Int, height: Int) - - case class ImageSize(px: SizePx, cells: SizeCells) - - def fitCellsToScreen(sizeCells: SizeCells, termSize: TermCells): SizeCells = - val widthRatio = sizeCells.width.toDouble / termSize.cols - val heightRatio = sizeCells.height.toDouble / termSize.rows - if widthRatio > 1 || heightRatio > 1 then - val ratio = widthRatio max heightRatio - SizeCells((sizeCells.width / ratio).toInt, (sizeCells.height / ratio).toInt) - else sizeCells - - def calculateSize( - size: SizePx, - requestedWidth: Sizing, - requestedHeight: Sizing, - keepAspect: Boolean, - termSize: TermSize, - ): ImageSize = - import Sizing.* - - def scale(dimensionPx: Int, requestedPx: Sizing): Int = - requestedPx match - case Absolute(pixels) => pixels - case Relative(ratio) => (ratio * dimensionPx).toInt - case Infer => dimensionPx - - val cell = SizePx(termSize.pixels.width / termSize.cells.cols, termSize.pixels.height / termSize.cells.rows) - - val scaledWidth = scale(size.width, requestedWidth) - val scaledHeight = scale(size.height, requestedHeight) - - val pixels = - if !keepAspect then SizePx(scaledWidth, scaledHeight) - else - val widthRatio = scaledWidth.toDouble / size.width - val heightRatio = scaledHeight.toDouble / size.height - if widthRatio < heightRatio then SizePx(scaledWidth, (size.height * widthRatio).toInt) - else SizePx((size.width * heightRatio).toInt, scaledHeight) - - val sizeCells = SizeCells(pixels.width / cell.width, pixels.height / cell.height) - - ImageSize(pixels, sizeCells) - end calculateSize - - def showImage(img: os.Path): String = showImage(img, Sizing.Infer, Sizing.Infer, true) - def showImage( - img: os.Path, - requestedWidth: Sizing, - requestedHeight: Sizing, - keepAspect: Boolean, - fitToScreen: Boolean = true, - ): String = + def showImage(img: os.Path) = import java.util.Base64 ( for _ <- Option.when(checkSupport())(true) termSize <- Terminal.getSize() cursorPos <- Terminal.getCursorPos() - sizeOrig <- imageSize(img) - - ImageSize(pixels, cells) = calculateSize(sizeOrig, requestedWidth, requestedHeight, keepAspect, termSize) yield - val size = - ImageSize( - pixels, - if fitToScreen then fitCellsToScreen(cells, termSize.cells) - else cells, - ) - val image = Base64.getEncoder.encodeToString(os.read.bytes(img)) - val cell = SizePx(termSize.pixels.width / termSize.cells.cols, termSize.pixels.height / termSize.cells.rows) - - val commonParams = s"s=${sizeOrig.width},v=${sizeOrig.height},c=${size.cells.width},r=${size.cells.height}" - logger.info(s"Image size (px): ${sizeOrig.width} x ${sizeOrig.height}") - logger.info(s"Cellsize (px): ${cell.width} x ${cell.height}") - logger.info(s"Preferred size (px) ${size.px}") - logger.info(s"Preferred size (Cells) ${size.cells}") - if image.length > 4096 then val chunks = image.grouped(4096).toVector + val width = termSize.cols - cursorPos.cols * 2 - s"${apc}Gf=100,t=d,m=1,a=T,${commonParams};${chunks.head}${st}" + + s"${apc}Gf=100,t=d,m=1,a=T,c=${width};${chunks.head}${st}" + chunks.tail.init.map(c => s"${apc}Gm=1;${c}${st}").mkString + s"${apc}Gm=0;${chunks.last}${st}" - else s"${apc}Gf=100,t=d,a=T,${commonParams};${image}${st}" + else s"${apc}Gf=100,t=d,a=T;${image}${st}" ).getOrElse("Could not show image") - end showImage trait Param: def code: String diff --git a/copret/src/main.scala b/copret/src/main.scala new file mode 100644 index 0000000..16f7649 --- /dev/null +++ b/copret/src/main.scala @@ -0,0 +1,16 @@ +package de.qwertyuiop.copret + +import de.qwertyuiop.copret.Terminal.* + +@main def main = + enterRawMode() + // print(queryTerm(s"${apc}Gi=31,s=10,v=2,t=s;L3NvbWUtc2hhcmVkLW1lbW9yeS1uYW1lCg==\u001b\\")) + // print(queryTerm(s"\u001b[6n")) + val canHazGraphics = queryTerm(s"${apc}Gi=${KittyGraphicsProtocol.MaxID},s=1,v=1,a=q,t=d,f=24;AAAA${st}${csi}c") + println(canHazGraphics.contains(s"${apc}Gi=${KittyGraphicsProtocol.MaxID}")) + // println( + // KittyGraphicsProtocol.showImage( + // os.Path("/home/crater2150/org/opencolloq/gittalk/img/repo-graph.png"), + // ), + // ) + // println(getSize) diff --git a/copret/src/slides.scala b/copret/src/slides.scala index 4ef5dee..6cac196 100644 --- a/copret/src/slides.scala +++ b/copret/src/slides.scala @@ -16,6 +16,15 @@ case class Paragraph(contents: String) extends Slide: object Paragraph: def apply(str: fansi.Str): Paragraph = Paragraph(str.toString) +case class IncludeMarkdown(path: Path) extends Slide: + def markdownBlock() = + %%%( + "/usr/bin/mdcat", + "--columns", + (columns * 0.8).toInt.toString, + path.toString, + )(using os.pwd).block + case class Image(path: Path, sizing: Option[ImageSize]) extends Slide object Image: def apply(path: Path) = new Image(path, None) @@ -88,11 +97,7 @@ object TypedCommand: c => runProcess(Vector(shell, "-c", c.mkString(" "))) def runInteractive(using cwd: Path): Vector[String] => String = - c => - Terminal.showCursor() - os.proc(c).call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit, cwd = cwd) - Terminal.hideCursor() - "" + c => { os.proc(c).call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit, cwd = cwd); "" } def apply(cmd: String*)(using Path): TypedCommand[Vector[String]] = TypedCommand(run, cmd.mkString(" "), cmd.toVector) @@ -109,9 +114,6 @@ object TypedCommand: def interactive(cmd: String*)(using Path): TypedCommand[Vector[String]] = TypedCommand(runInteractive, cmd.mkString(" "), cmd.toVector) - def interactiveUntyped(cmd: String*)(using Path): TypedCommand[Vector[String]] = - TypedCommand(runInteractive, cmd.mkString(" "), cmd.toVector, cmdIsHidden = true, outputIsHidden = false) - def untyped(cmd: String*)(using Path): TypedCommand[Vector[String]] = TypedCommand(exec = run, display = "", cmd.toVector, cmdIsHidden = true, outputIsHidden = false) end TypedCommand diff --git a/copret/src/templates.scala b/copret/src/templates.scala index bebd73f..39a5b0a 100644 --- a/copret/src/templates.scala +++ b/copret/src/templates.scala @@ -32,31 +32,6 @@ trait Templates: ), ) - def list(bullet: (Int, Int) => String, continued: (Int, Int) => String)(items: String*)(using Theme): Paragraph = - items.zipWithIndex - .map((str, index) => { - def formatFirst(first: String) = - s" ${bullet(index, items.size)} $first" - - str.split("\n").toList match - case first :: Nil => formatFirst(first) - case first :: cont => - formatFirst(first) - + cont.map(line => s" ${continued(index, items.size)}${line}").mkString("\n", "\n", "") - case Nil => "" - }) - .mkString("\n") - .par - - def ulist(items: String*)(using Theme) = list(bullet = (_, _) => "✦ ", continued = (_, _) => " ")(items*) - - def olist(items: String*)(using Theme) = - def digits(i: Int): Int = math.ceil(math.log10(i)).toInt - list( - bullet = (i, max) => (" " * (digits(max + 1) - digits(i + 1) - 1)) + s"${i + 1}.", - continued = (i, max) => " " * digits(max + 1), - )(items*) - lazy val --- = Paragraph("\n" + ("-" * columns).yellow.toString + "\n") lazy val === = Paragraph("\n" + ("═" * columns).yellow.toString + "\n") end Templates diff --git a/copret/src/terminal.scala b/copret/src/terminal.scala index 044dcad..75a98ba 100644 --- a/copret/src/terminal.scala +++ b/copret/src/terminal.scala @@ -37,10 +37,13 @@ object Terminal: def queryTerm(query: String): String = val ttyIn = new java.io.FileInputStream("/dev/tty") val ttyOut = new java.io.PrintStream(new java.io.FileOutputStream("/dev/tty")) + logger.info(s"Querying terminal: ${query.replaceAllLiterally("\u001b", "")}") ttyOut.print(query) var response = scala.collection.mutable.ArrayBuffer[Int]() response += ttyIn.read() + logger.info(s"Response: ${response}") while (ttyIn.available > 0 && response.last != '\u0007') + logger.info(s"Response(cont): ${response}") response += ttyIn.read() new String(response.toArray, 0, response.length) @@ -101,27 +104,20 @@ object Terminal: case CursorPosResponse(rows, cols) => Some(CursorPos(cols.toInt, rows.toInt)) case _ => None - def getSizeCells(): Option[TermCells] = + def getSize(): Option[CellsSize] = queryTerm(s"${csi}s${csi}999;999H${csi}6n${csi}u") match - case CursorPosResponse(rows, cols) => Some(TermCells(cols.toInt, rows.toInt)) + case CursorPosResponse(rows, cols) => Some(CellsSize(cols.toInt, rows.toInt)) case _ => None private val SizeResponse = """\u001b\[4;(\d+);(\d+)t""".r - case class TermPixels(width: Int, height: Int) - case class TermCells(cols: Int, rows: Int) - case class TermSize(pixels: TermPixels, cells: TermCells) + case class PixelSize(width: Int, height: Int) + case class CellsSize(cols: Int, rows: Int) case class CursorPos(cols: Int, rows: Int) - def getSizePixels(): Option[TermPixels] = queryTerm(s"${csi}14t") match - case SizeResponse(rows, cols) => Some(TermPixels(cols.toInt, rows.toInt)) + def getSizePixels: Option[PixelSize] = queryTerm(s"${csi}14t") match + case SizeResponse(rows, cols) => Some(PixelSize(cols.toInt, rows.toInt)) case _ => None - def getSize(): Option[TermSize] = - for - pixels <- getSizePixels() - cells <- getSizeCells() - yield TermSize(pixels, cells) - end Terminal private[copret] trait TerminalSyntax: diff --git a/examples/git/.gitignore b/examples/.gitignore similarity index 100% rename from examples/git/.gitignore rename to examples/.gitignore diff --git a/examples/git/git-presentation.sc b/examples/git-presentation.sc similarity index 88% rename from examples/git/git-presentation.sc rename to examples/git-presentation.sc index 0bc5f7d..5a06592 100755 --- a/examples/git/git-presentation.sc +++ b/examples/git-presentation.sc @@ -142,17 +142,9 @@ val meta = Map( val presentation = Presentation( Vector( titleSlide, - slide("Wozu braucht man das?")( - ulist( - "Versionierungssysteme speichern _Änderungsverlauf_ von Dateien", - "Lässt einen Änderungen rückgängig machen", - "Unterstützt einen bei *Zusammenführung* von mehreren Versionen", - "(z.B. stabile und Entwicklungsversion)", - "Synchronisierung zwischen mehreren Rechnern", - "Fehlersuche", - "...", - ) - ), + /* for simple slides, the `markdown` template and `IncludeMarkdown` Slide allow including plain markdown files. + * (currently requires mdcat) */ + markdown("Wozu braucht man das?", pwd / "slides" / "01wozu.md"), chapter("Basics", "Grundlegende Befehle"), /* `slide` is a template for a `Group` with an optional title, that clears the screen */ slide("Basics")( @@ -339,38 +331,9 @@ val presentation = Presentation( sh("git add hello.txt; git commit -m \"Extended greeting\""), gitStatus, ), - slide("Was bisher geschah")( - ulist( - s"${"git init [dir]".yellow}: neues Repo anlegen", - s"${"git status".yellow}: Aktuellen Zustand zeigen (geänderte, ungetrackte Dateien, Stagingbereich)", - s"${"git add ".yellow}: Datei zu Repo hinzufügen / Änderungen in Datei für Commit markieren / \"stagen\"", - s"${"git restore --staged ".yellow}: Änderungen aus Staging wieder herausnehmen", - s"${"git restore ".yellow}: Änderungen in Datei verwerfen (zurück zu Dateizustand aus letzten Commit)", - s"${"git commit [-m message]".yellow}: Neuen Commit mit Änderung im Stagingbereich erstellen", - s"${"git log".yellow}: Bisherige Commits anzeigen", - s"(${"git cat-file".yellow}: Git-Objekte im internen Format anzeigen. Braucht man im Alltag nicht)", - ) - ), + markdown("Was bisher geschah", pwd / "slides" / "02summary.md"), chapter("Branches", "Grundlegende Befehle"), - slide("Branches")( - "Branches sind mehrere parallele Entwicklungszweige. Wozu Branches?".par, - ulist( - "Hauptbranch (master, main, etc.) für stabile Version nutzen", - "neue Branches pro zusätzlichem Feature erstellen", - "Arbeit an mehreren Branches unabhängig voneinander möglich", - "Anzeigen aller Änderungen zwischen Branches möglich", - "Springen zwischen Zustand verschiedener Branches", - ), - "Verschiedene Workflows möglich, je nach Projekt unterschiedlich.".par, - "Branches sind in Git \"billig\":".par, - ulist( - "Ein Branch ist ein Pointer auf einen Commit", - "Normalerweise ist ein Branch als aktueller Branch ausgewählt", - "Neuer Commit => aktueller Branch zeigt nun auf diesen", - "Neue Branches erstellen = neue Datei mit Hash anlegen", - "Branches können auch komplett lokal bleiben (im Gegensatz zu SVN)", - ), - ), + slide("\n".par, IncludeMarkdown(pwd / "slides" / "02branches.md")), slide("Branches")( sh("git branch"), ---, diff --git a/examples/git/img/git-logo.png b/examples/img/git-logo.png similarity index 100% rename from examples/git/img/git-logo.png rename to examples/img/git-logo.png diff --git a/examples/git/img/repo-graph-i.png b/examples/img/repo-graph-i.png similarity index 100% rename from examples/git/img/repo-graph-i.png rename to examples/img/repo-graph-i.png diff --git a/examples/git/img/repo-graph.png b/examples/img/repo-graph.png similarity index 100% rename from examples/git/img/repo-graph.png rename to examples/img/repo-graph.png diff --git a/examples/git/slides/01wozu.md b/examples/slides/01wozu.md similarity index 100% rename from examples/git/slides/01wozu.md rename to examples/slides/01wozu.md diff --git a/examples/git/slides/02branches.md b/examples/slides/02branches.md similarity index 100% rename from examples/git/slides/02branches.md rename to examples/slides/02branches.md diff --git a/examples/git/slides/02summary.md b/examples/slides/02summary.md similarity index 100% rename from examples/git/slides/02summary.md rename to examples/slides/02summary.md diff --git a/examples/termescape/figlet-pagga-block b/examples/termescape/figlet-pagga-block deleted file mode 100755 index 5e8c96c..0000000 --- a/examples/termescape/figlet-pagga-block +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/zsh -zparseopts -D -E p:=padding -padding:=padding -lines=("$@") -if [[ -z $lines ]]; then - lines=() - while read line; do - lines+=($line) - done -fi - -width=0 -for line in $lines; do - if [[ ${#line} -gt $width ]]; then - width=${#line} - fi -done - -lpad_width=$((width + ${padding[2]:-0})) -rpad_width=$((lpad_width * 2)) - -pagga=( $( -for i in {1..$#lines}; do - line=$lines[$i] - padded="${(r:$(( rpad_width - ${#line})):: :)${(l:${lpad_width}:: :)line}}" - spaces=$(echo $line | tr -cd ' ' | wc -c) - if [[ $spaces -gt 0 ]]; then - padded="$padded${(l:spaces:: :)}" - fi - echo $padded -done | figlet -fpagga -t -) ) - -left_space=$(( (COLUMNS - ${#pagga[1]}) / 2 )) -for line in ${pagga[*]}; do - echo "${(l:left_space:: :)}$line" -done diff --git a/examples/termescape/img/csi.png b/examples/termescape/img/csi.png deleted file mode 100644 index 543c28a..0000000 Binary files a/examples/termescape/img/csi.png and /dev/null differ diff --git a/examples/termescape/img/csi.svg b/examples/termescape/img/csi.svg deleted file mode 100644 index 1a86b5b..0000000 --- a/examples/termescape/img/csi.svg +++ /dev/null @@ -1,105 +0,0 @@ - - - -$>_ diff --git a/examples/termescape/img/earth.gif b/examples/termescape/img/earth.gif deleted file mode 100644 index 9df6477..0000000 Binary files a/examples/termescape/img/earth.gif and /dev/null differ diff --git a/examples/termescape/terminal-presentation.sc b/examples/termescape/terminal-presentation.sc deleted file mode 100755 index 5e157aa..0000000 --- a/examples/termescape/terminal-presentation.sc +++ /dev/null @@ -1,529 +0,0 @@ -#!/usr/bin/env -S scala-cli shebang -//>using scala 3.3 -//>using dep de.qwertyuiop::copret:0.0.2 - -import de.qwertyuiop.copret.Terminal.* - -import de.qwertyuiop.copret.* -import de.qwertyuiop.copret.syntax.* -import TypedCommand.{interactive, shell => sh} -import os.Path - -given presentationRoot: Path = os.Path("/home/crater2150/org/opencolloq/terminal/termescape/") -val imgs = presentationRoot / "img" -given theme: Theme = Theme.default - -def figlet(str: String, font: String): String = String( - os.proc("figlet", "-t", "-f", font, str).call().out.bytes, -) - -def lolcat(str: String): String = String( - os.proc("lolcat", "-b", "-f", "-h", "0.7", "-r").call(stdin = str).out.bytes, -) - -def lolcatCustom(str: String, opts: String*): String = String( - os.proc("lolcat", "-f", opts).call(stdin = str).out.bytes, -) - -def lollet(str: String, font: String): String = lolcat(figlet(str, font)) - -// debug mode -val noInteractiveCommands = false - -val titleSlide = - Group( - Clear, - TypedCommand.interactiveUntyped("./title.zsh").disable("", noInteractiveCommands), - Clear, - header, - Paragraph( - lollet("Terminal", "mono12").block ++ lollet("basics", "mono12").block - ++ "\n" ++ "Was tut so eine Terminalanwendung eigentlich?".right(10), - ), - ) - -/* copret templates are simply normal methods that return slides */ -def chapter(title: String, subtitle: String): Group = - chapter(title, "", subtitle) -def chapter(title1: String, title2: String, subtitle: String): Group = { - val font = - if ((title1.length max title2.length) < columns / 10) "mono12" else "mono9" - Group( - Clear, - header, // a built in template - Paragraph( - lollet(title1, font).block ++ lollet(title2, font).block ++ "\n" ++ subtitle.right(10), - ), - ) -} - -val meta = Map( - "author" -> "Alexander Gehrke", - "title" -> "Terminals", -) - -def sgr(code: String): String = s"\u001b[${code}m" - -def sgr(code: Int*): String = sgr(code.mkString(":")) - -val reset = sgr(0) - -extension (s: String) - def style(code: String): String = sgr(code) + s + reset - def style(code: Int*): String = sgr(code*) + s + reset - def italic = s.style(3) - def bold = s.style(1) - def link(id: Int, uri: String) = s"\u001b]8;id=$id;$uri\u001b\\" + s + "\u001b]8;;\u001b\\" - def emph = sgr(3) + lolcat(s) + reset - def strong = sgr(1) + lolcat(s) + reset - - def underline(style: Int = 1, color: Int = 7) = - sgr("4:" + style) + sgr(58, 5, color) + s + reset - -val rainbow = Vector( - "\u001b[38:5:196m", - "\u001b[38:5:202m", - "\u001b[38:5:208m", - "\u001b[38:5:214m", - "\u001b[38:5:220m", - "\u001b[38:5:226m", - "\u001b[38:5:190m", - "\u001b[38:5:154m", - "\u001b[38:5:118m", - "\u001b[38:5:82m", - "\u001b[38:5:46m", - "\u001b[38:5:47m", - "\u001b[38:5:48m", - "\u001b[38:5:49m", - "\u001b[38:5:50m", - "\u001b[38:5:51m", - "\u001b[38:5:45m", - "\u001b[38:5:39m", - "\u001b[38:5:33m", -) - -def list(bullet: (Int, Int) => String, continued: (Int, Int) => String)(items: String*)(using Theme): Paragraph = - val randbow = util.Random.shuffle(rainbow) - items.zipWithIndex - .map((str, index) => { - def formatFirst(first: String) = - s" ${randbow(index % randbow.length)}${bullet(index, items.size)}\u001b[0m $first" - - str.split("\n").toList match - case first :: Nil => formatFirst(first) - case first :: cont => - formatFirst(first) - + cont.map(line => s" ${continued(index, items.size)}${line}").mkString("\n", "\n", "") - case Nil => "" - }) - .mkString("\n") - .par - -def ulist(items: String*)(using Theme) = list(bullet = (_, _) => "✦ ", continued = (_, _) => " ")(items*) - -def digits(i: Int): Int = math.ceil(math.log10(i)).toInt - -def olist(items: String*)(using Theme) = - list( - bullet = (i, max) => (" " * (digits(max + 1) - digits(i + 1) - 1)) + s"${i + 1}.", - continued = (i, max) => " " * digits(max + 1), - )(items*) - -val slides = Vector( - titleSlide, - slide("Terminal IO")( - "Eine Anwendung in einem Terminal kommuniziert (fast) nur via Standard-IO mit diesem:".par, - ulist( - "stdin, für Benutzereingaben und Nachrichten vom Terminal", - "stdout, für Textausgabe und Nachrichten an das Terminal", - "stderr, wird wie stdout behandelt", - ), - s"=> Alle Steuersignale vom Programm ans Terminal oder umgekehrt sind ${"inband".emph}".par, - ), - slide("Steuersignale")( - s"""|Wie unterscheiden Terminal und Programm normalen IO von Steuersignalen? - |Durch ${"Steuerzeichen".emph}, aufgeteilt in zwei Bereiche:""".par, - s"""|${"C0".strong}: alle Zeichen im Bereich von 0x00 bis 0x1F - |Größtenteils veraltet. Noch relevant sind u.a.:""".par, - ulist( - "Whitespaces (\\n, \\r, \\t, ...) und Backspace (0x08)", - "Bell (\\a), führt bei den meisten Desktops zu blinkendem Icon o.ä. (Urgent Flag)", - "XON/XOFF (0x11/0x13), schaltet Ausgabe an/aus (Flow Control)", - "Escape (\\e, 0x1B), das wichtigste für diesen Talk, startet Escapesequenzen", - ), - s"""|${"C1".strong}: Zeichen im Bereich von 0x80 bis 0x9F - |Wegen Kollisionen mit Unicode inkompatibel. - |Heute üblicherweise durch Escapesequenzen ersetzt""".par, - ), - chapter("Escape-", "sequenzen", "die verschiedenen Typen"), - slide("CSI")( - PauseKey, - Image(imgs / "csi.png"), - ), - slide("CSI")( - """|Control Sequence Introducer (CSI) ist der vermutlich häufigste Typ. - |Wird benutzt für:""".par, - ulist( - "Cursor-Steuerung", - "Löschen von Zeichen oder Zeilen", - s"${lolcat("Farben")} ${"und".italic} ${"andere".style(42)} ${"Formatierung".underline(style = 3, color = 1)}", - ), - "|CSI-Sequenzen haben folgendes Format:\n".par, - s"${"ESC [".style(94)} ${"".style(96)} ${"arg1 : arg2 : ... : argn".style(93)} ${"".style(92)}".block.text, - s"""| - |${"✦ ESC [".style(94)} ist der Teil, der CSI genannt wird - |${"✦ ".style(92)} gibt an, was gemacht werden soll. Immer ein Zeichen zwischen 0x40 (@) und 0x7F (~) - |${"✦ ".style(92)} darf aus dem ASCII-Bereich 0x20 - 0x3F außer Ziffern, ; und : sein - | Gibt es, weil man mehr Befehle wollte, als für Zeichen erlaubt sind. - |${"✦ arg1 : arg2 : ... : argn".style(93)} sind numerische Parameter. - | Statt : wird teilweise ; benutzt (ISO-Standard vs. Realität). - |\n""".stripMargin.block.text, - s"""\nBeispiel: ${"[31m".style(31)} schaltet auf rote Schrift um""".par, - ), - slide("OSC")( - """|Operating System Commands (OSC) haben verschiedenste Anwendungszwecke, - |z.B. Setzen des Fenstertitels, Zugriff aufs Clipboard, etc. - |Im Gegensatz zu CSI Escapes können sie die meisten Zeichen enthalten - | => komplexere Befehle möglich.""".par, - s"${"ESC ]".style(94)} ${"".style(92)} ; ${"args...".style(93)} ${"ST".style(96)}".block.text, - s"""| - |${"✦ ESC ]".style(94)} startet ein OSC - |${"✦ ".style(92)} gibt an, was gemacht werden soll, ein- bis vierstellige Zahl - |${"✦ args...".style(93)} sind je nach Befehl die Argumente. Hier ist alles außer ST erlaubt, oft Base64. - |${"✦ ST".style(96)} ist das String Terminator Zeichen. Entweder BEL (\\a) oder ESC \\ - |""".stripMargin.block.text, - s"""| - |Beispiel: ${"]52;c;SGVsbG9Xb3JsZA==\\".style(38, 5, 145)} - | OSC52 = Clipboard, "c" gibt an welches Clipboard - | base64-codierter Text entspricht "HelloWorld" - """.par, - ), - slide("APC")( - "Application Program Commands (APC) sind ähnlich zu OSC, aber größtenteils\nspezifisch für einzelne Terminals".par, - ulist( - s"fangen mit ${"ESC _".style(3, 94)} an statt mit ${"ESC ]".style(3, 94)}", - s"Format bis zum ${"ST".style(3, 96)} ist uneinheitlich", - "Benutzt z.B. bei screen und tmux zum Setzen von Statusleisten", - "Beispiel für mittlerweile verbreitetere Anwendung: Kitty Graphics Protocol", - ), - ), - chapter("Farben und ", "Formatierung", "Escapes für dargestellte Zellen"), - slide("Terminaloutput")( - """|"Bevor wir uns Formatierung ansehen, erst ein kurzer Überblick darüber, - |wie die Ausgabe eines Terminals funktioniert:""".par, - ulist( - "State Machine, die Cursorposition und aktuelle Formatierung hält", - "Ausgabe aufgeteilt in Zellen, normalerweise ein Zeichen pro Zelle", - "Escapes in Ausgabe können Formatierung und Cursorposition ändern", - "druckbare Zeichen wird an Cursorposition geschrieben\nCursor wandert weiter, Formatierung bleibt", - ), - """|=> Formatierung gilt für die Zelle, in der der Cursor steht - | und alle folgenden Zellen, bis sie geändert wird""".par, - ), - slide("SGR Codes")( - """|Die meisten Formatierungen sind SGR-Sequenzen (Select Graphic Rendition). - |Das sind CSI-Sequenzen mit "m" als Endzeichen. - |Die vermutlich wichtigste ist die zum Zurücksetzen auf Default-Formatierung:""".par, - s"${"ESC [".style(94)} ${"0".style(92)} ${"m".style(96)}".block.text, - s"""| - |Alle SGR-Codes beginnen mit dem ${"CSI".style(94)} und enden mit einem "${"m".style(96)}". - |Die ${"0".style(92)} in der Mitte gibt den eigentlichen Befehl. - |Hier können je nach Code weitere Parameter folgen""".par, - ), - slide("Farben")( - """|Farben sind die wohl am häufigsten genutzte Formatierung. - |Es gibt verschiedene Arten von Farbcodes in Terminals:""".par, - ulist( - "Palettenfarben mit 8 oder 16 Farben", - "256-Farbenpalette, inzwischen auch fast überall unterstützt", - "Truecolor, 24-Bit-Farben, die in modernen Terminals unterstützt werden", - ), - s"""|Beispiel: Farbverlauf von #55ff22 ${"██".style("38:2::85:255:34")} nach #0000ff ${"██".style( - "38:2::0:0:255", - )}:""".par, - ("256 Farbpalette: " + lolcatCustom("█".repeat(60), "-h", "0.18", "-g", "55ff22:0000ff")).par, - ("24-Bit-Farben: " + lolcatCustom("█".repeat(60), "-h", "0.40", "-g", "55ff22:0000ff", "-b")).par, - ), - slide("Die Grundpalette")( - """|Wir fangen an mit den 8/16 Grundfarben. Die Codes zum Setzen bestehen aus mehreren Ziffern. - |Der Anfang gibt an, welche Farbe gesetzt werden soll:""".par, - ulist( - "Beginn mit 3: Vordergrundfarbe", - "Beginn mit 4: Hintergrundfarbe", - "Beginn mit 9: Helle Vordergrundfarbe", - "Beginn mit 10: Helle Hintergrundfarbe", - ), - "Anschließend folgt eine Ziffer für die Farbe (tatsächliche Farbe einstellungsabhängig):".par, - s"""|0 Schwarz ${"abc".style("30")} ${"abc".style("40")} hell: ${"abc".style("90")} ${"abc".style("100")} - |1 Rot ${"abc".style("31")} ${"abc".style("41")} hell: ${"abc".style("91")} ${"abc".style("101")} - |2 Grün ${"abc".style("32")} ${"abc".style("42")} hell: ${"abc".style("92")} ${"abc".style("102")} - |3 Gelb ${"abc".style("33")} ${"abc".style("43")} hell: ${"abc".style("93")} ${"abc".style("103")} - |4 Blau ${"abc".style("34")} ${"abc".style("44")} hell: ${"abc".style("94")} ${"abc".style("104")} - |5 Magenta ${"abc".style("35")} ${"abc".style("45")} hell: ${"abc".style("95")} ${"abc".style("105")} - |6 Cyan ${"abc".style("36")} ${"abc".style("46")} hell: ${"abc".style("96")} ${"abc".style("106")} - |7 Weiß ${"abc".style("37")} ${"abc".style("47")} hell: ${"abc".style("97")} ${"abc".style("107")} - """.stripMargin.block.text, - s"""\nBeispiel von vorhin: ${"[31m".style(31)} schaltet auf rote Schrift um""".par, - ), - slide("256 Farben")( - s"""|Die 256-Farbenpalette ist eine Erweiterung der 8/16-Farbenpalette. - |0-7 entsprechen denen der Terminalpalette, 8-15 den hellen Varianten. - |Dann folgen 216 Farben, die in einem 6x6x6-Würfel angeordnet sind: - | - | ${"✦ ".style(31)} Mit jedem Index steigt der Blauwert eine Stufe, nach 6 Farben wiederholt er sich - | ${"✦ ".style(32)} Mit jedem 6. Index steigt der Grünwert eine Stufe, analog. - | ${"✦ ".style(34)} Mit jedem 36. Index steigt der Rotwert eine Stufe - | - |Die restlichen 24 Farben sind Grautöne von schwarz nach weiß. - | - |Die Codes für die 256-Farbenpalette sind folgendermaßen aufgebaut:""".par, - s"Vordergrund: CSI ${"38".style(92)} : ${"5".style(93)} : ${"".style(95)} m".block.text, - s"Hintergrund: CSI ${"48".style(92)} : ${"5".style(93)} : ${"".style(95)} m".block.text, - s"""| - |${"✦ 5".style(93)} gibt an, dass die 256-Farben-Palette benutzt werden soll. - |${"✦ ".style(95)} ist der Index der Farbe in der Palette. - |""".stripMargin.block.text, - PauseKey, - TypedCommand.interactiveUntyped("sh", "-c", "colortest -w | less -R").disable("", noInteractiveCommands), - ), - slide("24 Bit \"Truecolor\"")( - s"""|Mit Truecolor gibt es keine Indices mehr. - |Es werden direkt die Werte für die Kanäle angegeben - |Der Aufbau ist ähnlich wie bei den 256 Farben (Vordergrund 38, Hintergrund 48):""".par, - s"CSI ${"38".style(92)} : ${"".style(93)} : ${"".style(95)} : ${"".style(94)} m".block.text, - s"""| - |${"✦ ".style(93)} ist entweder 2 für RGB, 3 für CMY, 4 für CMYK (RGB am besten supported) - |${"✦ ".style(95)} gibt den Farbraum an. Schwierig, Doku dazu zu finden, meistens leer. - |${"✦ ".style(94)} sind die Werte für die einzelnen Kanäle in dezimal, getrennt durch : - |✦ Nicht-standardisiertes, aber verbreitetes xterm-Encoding: - | Semikolon statt Doppelpunkt, Farbraumfeld fehlt. - |""".stripMargin.block.text, - s"""\nBeispiel: ${"[38:2::255:166:86m".style( - "38:2::255:166:86", - )} ergibt den Orangeton RGB(255, 166, 86) bzw. #FFA656""".par, - ), - slide("weitere Textformatierungen")( - "Weitere SGR-Codes zum Formatieren von Text (in Klammern: Code zum Abschalten):".par, - ulist( - s"1 (22): ${"Fett".style(1)}", - s"2 (22): ${"Faint".style(2)} (meistens nicht unterstützt oder wechselt nur zu dunklerer Farbe)", - s"3 (23): ${"Kursiv".style(3)}", - s"5 und 6 (25): ${"Blinkend".style(5)} und ${"schnell Blinkend".style(6)}, durchwachsener Support", - s"7 (27): ${"Invertiert".style(7)} (Vorder- und Hintergrundfarbe tauschen)", - s"8 (28): Versteckt (Vordergrundfarbe = Hintergrundfarbe)", - s"9 (29): ${"Durchgestrichen".style(9)}", - ), - ), - slide("weitere Textformatierungen")( - "Weitere SGR-Codes zum Formatieren von Text:".par, - ulist( - s"""|4 (24): ${"Unterstrichen".style(4)}. Moderne Terminals können zusätzlich noch: - | 4:1 ${"Normal unterstrichen".style(4, 1)} - | 4:2 ${"Doppelt unterstrichen".style(4, 2)} - | 4:3 ${"Unterringelt".style(4, 3)} - | 4:4 ${"gepunktet".style(4, 4)} - | 4:5 ${"gestrichelt".style(4, 5)} - | - | Außerdem kann in manchen Terminals die ${"Farbe".style(4).style("58:2::255:0:0")} gesetzt werden. - | 256er-Paletten- oder Truecolor-Code, einfach 38/48 durch 58 ersetzen. - |""".stripMargin, - ), - ), - slide("Bereiche formatieren")( - """Mit der folgenden Escapesequenz kann man direkt einen größeren Bereich formatieren:""".par, - s"CSI ${"Pt; Pl; Pb; Pr".style(93)} ; ${"arg1 ; ... ; argn".style(95)} ${"$"}r".block.text, - s"""| - |${"✦ Pt; Pl; Pb; Pr".style(93)} geben den Bereich an, der formatiert werden soll - | Pt,Pl geben die erste Zelle an (top/left), Pb, Pr die letzte (bottom/right). - | Die oberste linke Zelle des Terminals ist hierbei 1,1. - | Weggelassene Werte entsprechen dem jeweiligen Extremwert. - |${"✦ arg1 ; ... ; argn".style(95)} sind ein SGR-Code (ohne das "m" am Ende) - |✦ Formatiert standardmäßig zeilenweise alles von der ersten bis zur letzten Zelle. - | Mit ${"CSI 2 * x".style(3)} wechselt man stattdessen zu blockweiser Formatierung, - | mit ${"CSI * x".style(3)} wieder zu zeilenweise. - |\n""".stripMargin.block.text, - s"""|Standardisiert sind nur fett, unterstrichen, blinken und invertiert. - |Manche Terminals, z.B. kitty unterstützen alle SGRs, andere wiederum keine.""".par, - ), - chapter("Cursor", "Bewegen und verstecken"), - slide("Einfache Cursorbewegung")( - s"""|Wenn wir komplexere Anwendungen bauen wollen, insbesondere interaktive, - |wollen wir nicht immer ein Zeichen nach dem anderen schreiben. - |Daher gibt es Escape-Codes, die den Cursor bewegen. - | - |Die folgenden Befehle sind alles CSI-Sequenzen mit 1-2 numerischen Argumenten. - |Fehlende Argumente entsprechen in den meisten Fällen einer 1.""".par, - ulist( - "A, B, C, D: Cursor um arg1 Zeichen nach oben, unten, rechts, links bewegen", - "E, F: Cursor auf Anfang der Zeile arg1 Zeilen unter/über aktueller Position setzen", - "G, d: Cursor auf Spalte bzw. Zeile arg1 setzen", - "H: Cursor auf Zeile arg1, Spalte arg2 setzen", - "s: Cursorposition speichern, u: zurück zur gespeicherten Position", - ), - s"""| - |Für absolute Positionen ist die oberste linke Zelle (1, 1). - |Beispiel: - |${"[HHello[3;6HWorld".style(3)} gibt "Hello" oben links aus, - |"World" in der dritten Zeile um 6 Zellen nach rechts verschoben.""".par, - PauseKey, - TypedCommand.interactiveUntyped("./cursorpos.zsh").disable("", noInteractiveCommands), - ), - slide("Cursordarstellung")( - """Der Cursor kann auch versteckt werden (z.B. tut das diese Präsentation):""".par, - ulist( - s"${"CSI ? 25 l".style(3)} versteckt den Cursor", - s"${"CSI ? 25 h".style(3)} zeigt den Cursor wieder an", - ), - s"""|Mit ${"CSI ␠ q".style(3)} kann die Form des Cursors angepasst werden. - |Hierbei ist ␠ ein Leerzeichen und eine der folgenden Zahlen:""".par, - ulist( - "0: Default-Cursor", - "1: Blinkender Block", - "2: Block", - "3: Blinkender Unterstrich", - "4: Unterstrich", - "5: Blinkender vertikaler Strich", - "6: vertikaler Strich", - ), - ), - slide("Dinge entfernen")( - """|Oft ist es praktisch, Text auch wieder löschen zu können, z.B. um den Inhalt - |einer Zeile zu aktualisieren. Einfach überschreiben führt zu Artefakten, wenn - |der neue Text kürzer ist als der alte.""".par, - ulist( - s"${"CSI K".style(3)} oder ${"CSI 0 K".style(3)} löscht vom Cursor bis zum Ende der Zeile", - s"${"CSI 1 K".style(3)} löscht vom Anfang der Zeile bis zum Cursor", - s"${"CSI 2 K".style(3)} löscht die ganze Zeile", - s"${"CSI J".style(3)} oder ${"CSI 0 J".style(3)} löscht vom Cursor bis zum Ende des Bildschirms", - s"${"CSI 1 J".style(3)} löscht vom Anfang des Bildschirms bis zum Cursor", - s"${"CSI 2 J".style(3)} löscht den ganzen Bildschirm (was \"clear\" tut)", - s"${"CSI 3 J".style(3)} löscht den Bildschirm und Scrollback", - ), - ), - chapter("Weitere", "Features", "vor allem neuere"), - slide("Hyperlinks")( - """|Hyperlinks sind ein relativ neues Feature in Terminals. Sie funktionieren ähnlich wie - |Links in Browsern. - |""".par, - s"Start: ${"OSC 8".style(92)} ; ${"".style(93)} ; ${"".style(95)} ST".block.text, - s"Ende: ${"OSC 8".style(92)} ; ; ST".block.text, - s"""| - |${"✦ OSC 8".style(92)} gibt an, dass wir einen Hyperlink wollen (zur Erinnerung: ${"OSC = ]".style(3)}) - |${"✦ ".style(93)} sind key=value Paare, getrennt durch Doppelpunkt - | Aktuell ist nur "id" spezifiziert. - |${"✦ ".style(95)} ist das Linkziel, URI-encoded. Erlaubte Schemas terminalabhängig. - | Wegen Remoteverbindungen wie SSH müssen file:// URIs den Hostnamen enthalten. - |✦ ${"ST".style(3)} ist der String Terminator (ESC \\)""".stripMargin.block.text, - ), - slide("Hyperlinks")( - s"""|Wozu die ID? Beispiel von egmontkob [1]: - | - | ╔═ file1 ════╗ - | ║ ╔═ file2 ═══╗ - | ║${"http://exa".link(42, "http://example.com")}║Lorem ipsum║ - | ║${"le.com".link(42, "http://example.com")} ║ dolor sit ║ - | ║ ║amet, conse║ - | ╚══════════║ctetur adip║ - | ╚═══════════╝ - | - |Umgebrochener Link in der linken Box soll ein Link bleiben. Das Terminal interpretiert aber - |Attribute pro Zelle. ID ist nötig, um nicht aufeinanderfolgende Zellen zu gruppieren. - | - | - |[1]: ${"https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda".style(38, 5, 39)}""".par, - ), - slide("Clipboard-Zugriff")( - s"""|Moderne Terminals erlauben es Programmen, auf das Clipboard zuzugreifen. - |Wozu, wenn das auch über "xclip" u.ä. geht? - | => Mit Escapecode funktioniert es auch über SSH!""".par, - s"${"OSC 52".style(92)} ; ${"".style(93)} ; ${"".style(95)} ST".block.text, - s"""| - |${"✦ OSC 52".style(92)} gibt an, dass wir auf das clipboard zugreifen wollen - |${"✦ ".style(93)} wählt das Clipboard, das wir nutzen möchten. - | "c" steht hierbei für das normale Clipboard, "p" für die "Primary selection". - |${"✦ ".style(95)} ist entweder ein "?", um aus dem Clipboard zu lesen, - | oder die base64-kodierten Daten, die reingeschrieben werden sollen. - |""".stripMargin.block.text, - s"""| - |Beispiel (von vorhin): ${"]52;c;SGVsbG9Xb3JsZA==\\".style(38, 5, 145)} - | kopiert "HelloWorld" in die Zwischenablage""".par, - ), - slide("""Moment, da muss das Terminal antworten""")( - s"""|Bis eben hatten wir nur Escape-Codes, bei denen das Terminal nichts zurückgeben muss. - |Wie erhalten wir die Antworten vom Terminal? => via stdin, auch als Escapecodes. - |Allerdings arbeitet stdin standardmäßig im ${"canonical mode".emph} - | => Eingabe kommt erst nach Newline an - | - |Hier müssen wir tatsächlich via Syscalls das ${"Terminal Device".emph} umkonfigurieren. - |Unter unixoiden Betriebssystemen können wir dafür das stty-Programm benutzen:""".par, - s"${"stty".style(92)} ${"-icanon".style(93)} ${"min 1".style(95)}".block.text, - s"""| - |${"✦ stty".style(92)} ist ein Programm, das diverse Terminalsyscalls kapselt - |${"✦ -icanon".style(93)} deaktiviert den canonical mode => ungepuffertes stdin - |${"✦ min 1".style(95)} setzt die minimale Größe für reads auf ein Byte - | => einzelne Zeichen lesbar, z.B. zum lesen von Tastendrücken""".stripMargin.block.text, - s"""| - |Wer in einer kompilierten Sprache arbeitet und die Syscalls direkt benutzen will, - |Die relevanten Suchbegriffe sind tcgetattr, tcsetattr und termios.h - |Bzw. unter Windows: GetConsoleMode, SetConsoleMode und windows.h""".par, - ), - slide("Clipboard auslesen")( - s"""|Zum Auslesen des Clipboards können wir nun die OSC52-Sequenz - |mit einem "?" als Datenparameter senden. - | - |Die Antwort des Terminals hat das selbe Format wie der Escapecode zum kopieren. - |Wenn wir also mit ${"]52;c;SGVsbG9Xb3JsZA==\\".style(38, 5, 145)} "HelloWorld" - |in die Zwischenablage schreiben können, würde auch genau dieser String zurück kommen - |wenn wir das Clipboard auslesen, während "HelloWorld" darin steht.""".par, - ), - slide("Cursorposition auslesen")( - s"""|Nachdem wir jetzt wissen, wie uns das Terminal Dinge antworten kann, können wir - |auch noch einen CSI-Code anschauen, den wir vorhin ausgelassen haben:""".par, - s"CSI ${"6 n".style(92)} ".block.text, - s"""| - |Auf diese Sequenz antwortet das Terminal mit der aktuellen Cursorposition, im Format:""".par, - s"CSI ${"".style(92)} ; ${"".style(92)} ${"".style(95)} ".block.text, - s"""| - |In Kombination mit anderen Escapes, die wir schon gesehen haben, kann man auf etwas - |hackige Art die Größe des Terminals bekommen:""".par, - s"${"CSI s".style(92)} ${"CSI 999;999 H".style(93)} ${"CSI 6 n".style(94)} ${"CSI u".style(96)}".block.text, - ), - slide("Bilder im Terminal")( - s"""|Man kann mittlerweile in vielen Terminals Grafiken ausgeben. Allerdings gibt es dafür - |verschiedene Protokolle und die meisten Emulatoren können nur eines.""".par, - ulist( - s"""|${"sixel".emph}: Ein altes Protokoll, das schon von Hardwareterminals unterstützt wurde - |Eingeschränkt auf 1024 Farbwerte pro Bild""".stripMargin, - s"""|${"iTerm2 Protocol".emph}: proprietäres, relativ einfaches Protokoll von iTerm2. - |Benutzt Escapes mit unbegrenzter Länge - | => Problemen wenn nicht supported""".stripMargin, - s"""|${"Kitty Graphics Protocol".emph}: proprietäres, aber ausführlich spezifiziertes Protokoll - |von kitty. Hat sehr viele Konfigurationsmöglichkeiten, benutzt eine APC-Sequenz. - |Auch von wenigen anderen implementiert (u.a. KDE Konsole). - |Support über Query prüfbar, wird ignoriert wenn nicht implementiert""".stripMargin, - ), - ), - slide("Bilder im Terminal")( - "Mit dem Kitty-Protokoll geht das auch in bewegt:".par, - TypedCommand.interactiveUntyped("kitten", "icat", "img/earth.gif"), - ), - chapter("Ende", "... des vorbereiteten Teils ;)"), - slide("Sonstige Escapes")( - "Ein paar weitere Typen von Escapesequenzen, die wir uns nicht genauer anschauen".par, - ulist( - "DCS (Device Control String): ähnlich wie OSC, kaum noch genutzt.\nU.a. für ältere Grafikprotokolle", - "SCS, DOCS: für Zeichensatzwechsel. Wir haben jetzt Unicode.", - "DEC: Für Dinge wie doppelte Zeilenhöhe. In manchen Terminals noch supported.", - "SM, RM: Setzen von Terminalmodi, war für Hardwareterminals relevant", - "DECSET, DECRST: Noch mehr Terminalmodi, vendorspezifisch.\nWerden noch genutzt, z.B. für Maus-Support", - ), - ), -) - -val presentation = Presentation(slides, meta = meta) - -presentation.start(using - Keymap.default ++ Map( - Key('i') -> SlideAction.runForeground("tmux"), - ), -) diff --git a/examples/termescape/title.zsh b/examples/termescape/title.zsh deleted file mode 100755 index 2b74402..0000000 --- a/examples/termescape/title.zsh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/zsh -printf '\e[?25l\e[2J' -while true; do - printf "\e[$((LINES / 2 - 14));0H" - ${ZSH_SCRIPT:a:h}/figlet-pagga-block -p 8 "" Bunte Terminals "" "und sonstiger" "Spass mit" ESCAPECODES "" | lolcat --24bit -r - read -s -k -t 0.5 && break -done -printf '\e[?25h'