summaryrefslogtreecommitdiff
path: root/content
diff options
context:
space:
mode:
Diffstat (limited to 'content')
-rw-r--r--content/clopher/03-TUI.md207
-rw-r--r--content/clopher/04-Native_interface.md273
-rw-r--r--content/hiding_the_complexity.md133
-rw-r--r--content/screencast.md109
4 files changed, 722 insertions, 0 deletions
diff --git a/content/clopher/03-TUI.md b/content/clopher/03-TUI.md
new file mode 100644
index 0000000..fb5ad02
--- /dev/null
+++ b/content/clopher/03-TUI.md
@@ -0,0 +1,207 @@
+Title: TUI: A look to the deep
+Date: 2019-05-30
+Categories: Series
+Tags: Clopher - TUI Gopher client
+Slug: clopher03
+Lang: en
+Summary: Research and work on the Terminal based User Interface of Clopher, the
+ gopher client.
+
+> Read all the posts in [Clopher series here]({tag}clopher-tui-gopher-client).
+
+This software have been introduced as a Gopher client but, as you can probably
+deduce from the previous post, the Gopher part is probably the simplest one.
+The complexity comes with user interaction. *People are hard*. That's why we
+are going to delay that as much as possible, trying to cover all the points in
+the middle before we *jump to the unknown*.
+
+Just joking. In fact, we have to shave many yaks before thinking about user
+interaction anyway. This text talks about them.
+
+### Are you talking to me?
+
+Let's remember we can classify programs by two different categories like this:
+
+- **Non-interactive programs**, often called *scripts*, are programs that take
+ an input and return an output. There's no interaction with the user in
+ between. An example of this could be the command `ls`.
+
+- **Interactive programs** receive user input while they run and respond to the
+ user while they are running. An example of this could be the machine that
+ sells you the tickets for the subway, it asks you where are you going, then
+ tells you the price, take your money and so on. All of this with the program
+ constantly running.
+
+Remembering what we talked about Gopher: it's a *stateless* protocol. There's
+no *state* stored in the server so all the queries *must* contain all the info
+related to them. *Queries are independent*.
+
+This, somehow leaves the door open to two possible implementations of Clopher.
+The *non-interactive* one would work like `curl`. Getting the IP, port,
+selector string and an optional search string as input it would open the
+connection retrieve the result and return it.[^1]
+
+But Clopher is designed as an **interactive program**. More like `lynx`, where
+you interactively ask for the pages and have a *local state* that records your
+history and other things. This is a decision, it's not imposed by the protocol.
+
+### Shell*f boycott*
+
+There are some different ways to handle user interaction in TUI based programs.
+The simplest one is to read by line, waiting until the user hits `ENTER` to
+read the result. That's the behaviour of the classic `scanf` function of C and
+many others like `input` in Python, etc.
+
+In programs like Clopher, where the design is similar to `lynx` or `vi`, this
+kind of input makes no sense at all. The program needs to be able to capture
+every key pressed by the user and perform action in response to them. For
+instance, in `vi` when the user hits `i` in normal mode it needs to change to
+insert mode and when the user presses `i` in insert mode it needs to change the
+contents of the buffer.
+
+The design of these kind of programs is simple to understand, it's an infinite
+loop[^2] where key presses are captured and they change the *state* of the
+program. When the user hits the key combination that halts the program the
+loop is broken.
+
+In simple C code the program would look like this:
+
+ ::clike
+ #include<stdio.h>
+
+ int
+ main(int argc, char * argv[]){
+ char c;
+ // Create some state
+
+ while(1){
+ c = getchar();
+ if( c == 'q'){ // Exit if user pressed `q`
+ return 0;
+ }
+ // Update state here
+ putchar(c); // Show the character for debugging
+ }
+ }
+
+Or the simplified Clojure equivalent:
+
+ ::clojure
+ (loop [c (char (.read *in*))
+ state (->state)] ; Create some state
+
+ (when (not= c \q) ; Exit if user pressed `q`
+ (print c) ; Show the character for debugging
+ (recur (char (.read *in*))
+ (update-state c state))) ; Update state
+
+
+Looks simple, right?
+
+Wait a second, there's a lot of stuff going on under the hood here. If you run
+the code in any POSIX compatible operating system (I didn't test on others,
+and I won't) you'll find the code might not be doing what we expected it to:
+The `getchar` (or `.read`) calls will wait until `ENTER` is pressed in the
+input buffer and then they'll get the characters one by one. But we want to get
+them as they come!
+
+#### Saints and demons — canonical mode
+
+In POSIX operating systems, the input is buffered by default. But that behavior
+can be configured following the POSIX terminal interface under the name
+**canonical** mode or **non-canonical** mode. The mode we are looking for is
+the non canonical mode. You can read more about it in [the Wikipedia][canon].
+
+Choosing the non-canonical mode has some extra options: one controls the number
+of minimum characters to have in the buffer to perform a `read` operation and
+the other defines the amount of tenths of second to wait for that input[^3].
+Choosing the right value for those fields (`c_cc[MIN]` and `c_cc[TIME]`)
+depends on the kind of interaction we are looking for.
+
+#### Make Dikembe smile — blocking
+
+Setting `c_cc[TIME]` field to `0` means the `read` operation will wait
+indefinitely until the minimum amount of characters defined with `c_cc[MIN]`
+are waiting in the buffer. Together with that, the `c_cc[MIN]` can be `0` that
+means the read operations will wait until there are `0` characters in the
+buffer, or, in other words, they won't wait.
+
+Be aware that both fields can provoke the read operations in the input buffer
+be non-blocking operations and that will cause the read operation to return
+with no value.
+
+In the case of Clopher, I decided to set the `c_cc[MIN]` to `1` so the read
+operations block until there's at least one character in the buffer (that means
+they will always return something) and the `c_cc[TIME]` to `0` so the read
+operations have no timeout and will block until a character arrives.
+
+Depending on the application you are developing, you might choose other kind of
+blocking configuration. For instance, setting a timeout can let you process
+other parts of the system and wait for the input in the same thread.
+
+#### We're talking about practice? — termios
+
+So now we know where to find this theoretical configuration it's time to put it
+in practice. In POSIX the standard way to access this is via `termios`[^4].
+It has some details that are not specified and depend on the implementation, so
+it might have some differences from Linux to BSD or whatever.
+
+`tcsetattr` and `tcgetattr` calls can be used to set and read the terminal
+configuration via termios. Check this example, compile it and compare it with
+the C code of the previous example:
+
+ ::clike
+ #include<stdio.h>
+ #include<termios.h>
+
+ int
+ main(int argc, char* argv[]){
+ // Get interface configuration to reset it later
+ struct termios term_old;
+ tcgetattr(0, &term_old);
+
+ // Get interface configuration to edit
+ struct termios term;
+ tcgetattr(0, &term);
+
+ // Set the new configuration
+ term.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
+ term.c_cc[VMIN] = 1; // Wait until 1 character is in buffer
+ term.c_cc[VTIME] = 0; // Wait indifinitely
+ //TCSANOW makes the change occur immediately
+ tcsetattr(0, TCSANOW, &term);
+
+ char ch;
+ while(1){
+ if(ch == 'q'){
+ // Set old configuration again and exit.
+ // If it's not set back the normal configuration of the
+ // terminal will be broken later!
+ tcsetattr(0, TCSANOW, &term_old);
+ return 0;
+ }
+ ch = getchar();
+ putchar(ch);
+ }
+ }
+
+All the code has enough comments to be understood but there are some weird
+flags it's better to check in termios documentation.[^4]
+
+### But this is C code and Clopher is written in Clojure!
+
+I know but this is becoming long and boring. Why not wait until I get some
+spare time and write the next chapter? You have tons of information to check
+until I write it so you won't be bored if you don't want to.
+
+See you next.
+
+
+
+[^1]: In fact, you can navigate the Gopherverse like this with `curl`.
+[^2]: Unsurprisingly called *main loop*. Programmers are very creative.
+[^3]: That `read` operation is what `getchar` is doing under the hood.
+[^4]: `man termios` or visit [online `man`
+ pages](https://linux.die.net/man/3/termios)
+
+[canon]: https://en.wikipedia.org/wiki/POSIX_terminal_interface#Input_processing
diff --git a/content/clopher/04-Native_interface.md b/content/clopher/04-Native_interface.md
new file mode 100644
index 0000000..08a8e52
--- /dev/null
+++ b/content/clopher/04-Native_interface.md
@@ -0,0 +1,273 @@
+Title: TUI Slang: Speak like the natives
+Date: 2019-06-15
+Categories: Series
+Tags: Clopher - TUI Gopher client
+Slug: clopher04
+Lang: en
+Summary: Interfacing between Clojure, Java and Native code.
+
+
+> Read all the posts in [Clopher series here]({tag}clopher-tui-gopher-client).
+
+The previous post introduced `termios` as a native interface to configure the
+terminal input processing. With termios we managed to make our C programs get
+input character by character processing them as they came with no buffering but
+we didn't integrate that with our Clojure code. Now it's time to make it.
+
+
+### Run before it's too late
+
+Before we dig in the unknown, I have to tell you there are other alternatives
+for the terminal configuration. The simplest one I can imagine is using
+`stty`[^1] as an external command. I learned this from [Liquid][liquid], a
+really interesting project I had as a reference. If you want to see this work
+check the `adapters/tty.clj` file in the `src` directory of the project.[^2]
+
+Of course, it has some drawbacks. `stty` is part of the GNU-Coreutils project
+and you have to be sure your target has it installed if you want to rely on
+that. I'm not sure about if it's supported in non-GNU operating systems[^3].
+
+In my case, I decided to stay with `termios` interface to deal with all this
+because I didn't really want to rely on external commands and it's supposed to
+be implemented in any POSIX OS. The good (bad?) thing is it made me deal with
+native libraries from Clojure and had to learn how to do it.
+
+
+### The floor is Java
+
+When dealing with low-level stuff we have to remember Clojure is just Java, and
+most of the utilities we need to use come from it. This means the question we
+have to answer is not really *"how to call native code from Clojure?"* because
+if we are able to call native code from Java, we will be able to do it from
+Clojure too (if we spread some magic on top).
+
+#### So, how to call native code from Java?
+
+First I checked the [Java Native Interface (aka JNI)][jni], but I thought it
+was too much for me and I decided to check further. Remember there are only a
+couple of calls to make to `termios` from our code, so we don't really want to
+mess with a lot of boilerplate code, compilations and so on.
+
+My research made me find [Java Native Access (aka JNA)][jna] library. If you
+check the link there you'll find that the Wikipedia[^4] describes it as:
+
+> JNA's design aims to provide native access in a natural way with a minimum of
+> effort. No boilerplate or generated glue code is required.
+
+Sounds like right for me. Doesn't it?
+
+I encourage you to check the full Wikipedia entry and, if you have some free
+time at the office or something, to check the implementation because it's
+really interesting. But I'll leave that for you.
+
+##### A lantern in the dark
+
+JNA is quite easy to use for the case of Clopher, even easier if you realize
+there is [lanterna][lanterna], the TUI library, out there, using it internally
+so you can *steal*[^5] the implementation from it. Lanterna is a great piece
+of software I took as a reference for many parts of the project. Digging in the
+internals of large libraries is a great exercise and you can learn a lot from
+it.
+
+First of all, like many Java projects, the amount of abstractions it has is
+crazy. It takes some time to find the actual implementation of what we want.
+This isn't like this for no reason, the reality is they need to create this
+amount of abstractions because the part of the library that handles the widgets
+can work on top of many different terminal implementations, including a
+[Swing][swing] based one that comes with Lanterna itself.
+
+Clopher only targets POSIX compatible operating systems so we can go directly
+to what we want and read the termios part directly discarding all the other
+compatibility code. This code is quite easy to find if you see the directory
+tree of Lanterna: there's a `native-integration` folder in the root directory.
+If you follow that you'll arrive to [`PosixLibC.java`][posix-lanterna] that
+uses JNA to interact with termios.
+
+The implementation provided by Lanterna is quite complete, they declare a
+library with the functions they need and the data structure introduced in the
+previous chapter. Once the library interface and the necessary data structures
+are defined from Java they can be called with JNA, like they do in the file:
+[`NativeGNULinuxTerminal.java`][nativegnu-lanterna].
+
+#### How to call JNA from Clojure, then?
+
+Calling Java code from Clojure is quite simple because Clojure have been
+designed with that in mind, but this is not only that. Thanks to the Internet,
+there's a great [blogpost by Nurullah Akkaya][nakkaya] describing a simple way
+to use JNA from Clojure. From that, we can move to our specific case.
+
+`termios` has its own data structure so we need to define it so the JNA knows
+how to interact with it. The problem is that Clojure doesn't have enough OOP
+tools to do it directly so we need to make it in plain Java. The good thing is
+that we don't really need to create anything else.
+
+If we remove some unneeded code from Lanterna's termios structure
+implementation it will look like the implementation I made at
+`src/java/clopher/Termios.java`:
+
+ ::clike
+ package clopher.termios;
+ import com.sun.jna.Structure;
+
+ import java.util.Arrays;
+ import java.util.List;
+
+
+ /**
+ * Interface to Posix libc
+ */
+ public class Termios extends Structure {
+ private int NCCS = 32;
+
+ public int c_iflag; // input mode flags
+ public int c_oflag; // output mode flags
+ public int c_cflag; // control mode flags
+ public int c_lflag; // local mode flags
+ public byte c_line; // line discipline
+ public byte c_cc[]; // control characters
+ public int c_ispeed; // input speed
+ public int c_ospeed; // output speed
+
+ public Termios() {
+ c_cc = new byte[NCCS];
+ }
+
+ // This function is important for JNA, because it needs to know the
+ // order of the fields of the struct in order to make a correct Java
+ // class to C struct translation
+ protected List<String> getFieldOrder() {
+ return Arrays.asList(
+ "c_iflag",
+ "c_oflag",
+ "c_cflag",
+ "c_lflag",
+ "c_line",
+ "c_cc",
+ "c_ispeed",
+ "c_ospeed"
+ );
+ }
+ }
+
+Once the struct is defined, it's time to use it from Clojure. `clopher.term`
+namespace has the code to solve this. Summarized here:
+
+ ::clojure
+ (ns clopher.term
+ (:import [clopher.termios Termios]
+ [com.sun.jna Function]))
+
+ (def ^:private ICANON 02)
+ (def ^:private ECHO 010)
+ (def ^:private ISIG 01)
+ (def ^:private ECHONL 0100)
+ (def ^:private IEXTEN 0100000)
+
+ (def ^:private VTIME 5)
+ (def ^:private VMIN 6)
+
+ ; The macro we saw at the blogpost by Nurulla Akkaya
+ (defmacro jna-call [lib func ret & args]
+ `(let [library# (name ~lib)
+ function# (Function/getFunction library# ~func)]
+ (.invoke function# ~ret (to-array [~@args]))))
+
+ ; Wrapper for the tcgetattr function
+ (defn get-config!
+ []
+ (let [term-conf (Termios.)]
+ (if (= 0 (jna-call :c "tcgetattr" Integer 0 term-conf))
+ term-conf
+ (throw (UnsupportedOperationException.
+ "Impossible to get terminal configuration")))))
+
+ ; Wrapper for the tcsetattr function
+ (defn set-config!
+ [term-conf]
+ (when (not= 0 (jna-call :c "tcsetattr" Integer 0 0 term-conf))
+ (throw (UnsupportedOperationException.
+ "Impossible to set terminal configuration"))))
+
+ ; Example to set the non-canonical mode using the flags at the top of the
+ ; file
+ ; Yeah, binary operations.
+ (defn set-non-canonical!
+ ([]
+ (set-non-canonical! true))
+ ([blocking]
+ (let [term-conf (get-config!)]
+ (set! (.-c_lflag term-conf)
+ (bit-and (.-c_lflag term-conf)
+ (bit-not (bit-or ICANON ECHO ISIG ECHONL IEXTEN))))
+ (aset-byte (.-c_cc term-conf) VMIN (if blocking 1 0))
+ (aset-byte (.-c_cc term-conf) VTIME 0)
+ (set-config! term-conf))))
+
+
+Pay attention to all the mutable code here!
+`aset-byte` function helps a lot when dealing with all that.
+
+Be also sure to check termios' documentation because the calls act in a very
+C-like way, returning a non-zero answer when they fail.
+
+We need an extra point in our code to solve the Java-Clojure interoperability:
+we have to tell our project manager that we included some Java code in there.
+If our project manager is Leiningen, we can just tell it where do we store our
+Java code. Be careful because Leiningen [doesn't like if you mix Java and
+Clojure in the same folder][leiningen].
+
+ ::clojure
+ (defproject
+ ; There's more blablabla in here but these are the keys I want you to
+ ; take in account
+ :source-paths ["src/clojure"]
+ :java-source-paths ["src/java"]
+ :javac-options ["-Xlint:unchecked"])
+
+
+### Look back!
+
+Now you can configure your terminal to act non-canonically and serve you the
+characters one by one as they come. It's cool but you'll see there are some
+problems to come for the next chapters. Don't worry! They'll come.
+
+> This is like a heroic novel where the character (in this case you) fights
+> monsters one by one leaving their dead corpses in the dungeon floor. Looking
+> back will let you remember how many monsters did you slaughter in your way to
+> the deep where the treasure awaits. Remember to take rest and sharpen your
+> sword. This is a long travel.
+
+> Prepare yourself for the next monster. Let the voice of the narrator guide you
+> to the unknown.
+
+Why don't you mix what you learned on the previous chapter with what you
+learned from this one and try to make an interactive terminal program yourself?
+
+I'll solve that in the next chapter, but there's some code of that part already
+implemented in the repository. You can check it while I keep writing and
+coding. Here's the link to the project:
+
+[https://gitlab.com/ekaitz-zarraga/clopher][clopher]
+
+
+See you in the next episode!
+
+
+[^1]: Use the man pages, seriously: [`man stty`](https://linux.die.net/man/1/stty)
+[^2]: I've also been in contact with Mogens, the author of the project, who is
+ a really good guy and gave me a lot of good information.
+[^3]: But who cares about them anyway?
+[^4]: *the free encyclopedia*
+[^5]: If it's free software it's not stealing and it's exactly what you are
+ supposed to do with it.
+
+[liquid]: http://salza.dk/
+[jni]: https://en.wikipedia.org/wiki/Java_Native_Interface
+[jna]: https://en.wikipedia.org/wiki/Java_Native_Access
+[lanterna]: https://github.com/mabe02/lanterna
+[swing]: https://en.wikipedia.org/wiki/Swing_(Java)
+[posix-lanterna]: https://github.com/mabe02/lanterna/blob/master/native-integration/src/main/java/com/googlecode/lanterna/terminal/PosixLibC.java
+[nativegnu-lanterna]: https://github.com/mabe02/lanterna/blob/263f013a2ee1d522eb86b8f1d315423fb1f79711/native-integration/src/main/java/com/googlecode/lanterna/terminal/NativeGNULinuxTerminal.java#L123
+[nakkaya]: https://nakkaya.com/2009/11/16/java-native-access-from-clojure/
+[leiningen]: https://github.com/technomancy/leiningen/blob/master/sample.project.clj#L302
+[clopher]: https://gitlab.com/ekaitz-zarraga/clopher
diff --git a/content/hiding_the_complexity.md b/content/hiding_the_complexity.md
new file mode 100644
index 0000000..7dcbb43
--- /dev/null
+++ b/content/hiding_the_complexity.md
@@ -0,0 +1,133 @@
+Title: Hiding the complexity
+Date: 2019-10-27
+Category:
+Tags:
+Slug: hiding-the-complexity
+Lang: en
+Summary:
+ Inspired by scheme share my thoughts about the complexity and how it's
+ being hidden by our tools and how does that affect us as creators and
+ engineers.
+
+I've been recently playing with Scheme, reading R⁷RS and so on and I found
+something really interesting: Even with its high level of abstraction, it
+doesn't hide the complexity and makes you pay attention to it.
+
+That's extremely powerful and interesting.
+
+It's even more interesting if you think about the fact that Scheme can be
+implemented from scratch in an acceptable amount of time by a couple of hands.
+You don't need to be a big corporation or a big group of developers coding for
+years to implement it.
+
+It's **simple** but it doesn't hide the complexity of the implementation.
+That's a really powerful balance.
+
+But both points are too much to leave them here without playing with them
+separately so let's try to understand why both of the points are[^1]
+fundamental.
+
+[^1]: In my opinion, of course. This is *my* blog.
+
+
+## <del>Hidden</del> Latent complexity
+
+You can create the best programming language in the world but the
+complexity of the programming can't be eliminated because the user of the
+language will be, actually, programming. So, you can take two approaches here:
+
+- Expose the **intrinsic** complexity of programming.
+- Make it look as complexity doesn't exist. Which means hiding the complexity
+ as much as you can under layers of abstraction.
+
+Most of modern programming languages go for the second option. But not only
+programming languages, also operating systems, computers themselves and many
+areas more. Which is not specially bad in general, but it's dangerous when you
+need control.
+
+Most of the times where complexity is hidden by design, it's just *latent
+complexity*. It's harder to reach by the user, so the user scope of things they
+can do is reduced (and with it the their ability to decide with a high level
+of detail) but the complexity is still there, happening without being noticed,
+under the surface and being impossible to correct if something goes wrong.
+
+Think about your cellphone. You can't open it, change the battery, change it's
+software, change... *anything*. Because it's hard to do and "*people don't need
+to know about that*". But finally, what you have is a phone that is impossible
+to repair if something goes wrong or impossible to change if you want it to do
+something that is not the default behaviour.
+
+It is a problem (some people is fixing trying to fix, by the way) but it's not
+a problem for *everyone* because *everyone* doesn't need to have that level of
+complexity exposed. But they should have the right to see it if they wanted to.
+
+## Exposed complexity
+
+As engineers working on technology, we should be demanding for the complexity
+of things being exposed, more than running away from it.
+
+As engineers we are supposed to want to know how stuff works!
+
+In the case of programming languages, I want to control what the program does
+and I be aware of what I'm doing and which decisions I'm taking.
+
+When complexity is exposed you are reminded of the importance of every choice
+you make. It's not something that happens: you have to think about it.
+
+> In Scheme: List vs Vectors. Which one is better? Why have both? Why not use
+> use one all the time?
+
+It's reminding you what do you have under the hood, even if you aren't
+implementing it yourself. That way you don't forget about *your job*.
+
+## Simplicity
+
+Simplicity means that there's no unneeded complexity. It doesn't mean that
+complexity is hidden. We tend to confuse both terms too often.
+
+Scheme is simple, because its core is small and it's based in few concepts.
+Being simple means that concepts are clear and consistent and have few or none
+exceptions.
+
+Other programming languages use the same concepts that scheme does but they are
+not clearly stated so you can't rely on them for your understanding of a
+language. Scope in JavaScript (for instance) is often explained as a thing
+related with curly brackets' position while hiding the fact that it's a lexical
+scope. Watching engineers prefer a silly trick than an academical fact is
+unsatisfying[^2].
+
+[^2]: And insulting, I'd say.
+
+In many platforms abstraction layers are added until the internals are hidden
+or blurred. In this case, complexity is directly hidden by implementation, more
+than by users themselves running away from it.
+
+Some would ask: "Who cares about the details?" [^3] And it's perfectly fine to
+think that at some point but when it comes to choosing the right tool to the
+right problem, performance and fine tunning, you'd really like to know how they
+are implemented because implementation is what makes some operations be faster
+or more accurate than others. And, probably more important than that, being
+aware about how stuff is implemented make us independent enough to change the
+implementation if we want, **which is the base of free software**.
+
+[^3]: TLDR: *You, as an engineer, should*.
+
+When your tools hide reality from you for long enough, you start to forget that
+the reality still exists even if you are not watching it and you start
+acting like it wasn't there.
+
+
+## Assembly then... Right?
+
+Don't get me wrong. I'm not against simplification or making our job easier.
+Scheme, is a really high-level language. **Abstraction is good**.
+
+Accidental self-lobotomy is not that good.
+
+
+> NOTE:
+> This blogpost was triggered by this talk where a musician talks about
+> chiptune music and says how making chiptune music made him a better
+> guitarist. It has some good points about constraints and complexity.
+>
+> <https://youtu.be/_7k25pwNbj8>
diff --git a/content/screencast.md b/content/screencast.md
new file mode 100644
index 0000000..5ab88c8
--- /dev/null
+++ b/content/screencast.md
@@ -0,0 +1,109 @@
+Title: Screencasts: discussing with ffmpeg
+Date: 2020-01-11
+Category:
+Tags:
+Slug: ffmpeg-screencast
+Lang: en
+Summary: Screencasts in `ffmpeg`, having some fun solving issues thanks to
+ other people
+
+When you battle using your arguments that's called a *discussion*... That's
+exactly what I've been doing for a couple of days with `ffmpeg`: I've been
+using arguments trying to reach an understanding.
+
+I wanted to record my screen, and a couple of cameras, for reasons you'll know
+about later, and I didn't want to play around with new GUI programs and
+configuration so I decided to go for a simple `ffmpeg` setup.
+
+You probably had played with `ffmpeg` in the past. It has tons of different
+input arguments and options. It's crazy.
+
+Most of my previous times using it were just video conversions and it's as
+simple as choosing the right extensions for files, but when it comes to video
+and audio recording it gets complicated. I have no idea about video and audio
+encodings and I don't really have the time to dig in such an exciting topic.
+I searched on the Internet for examples and I found some: cool.
+
+I played around with multiple inputs and outputs, I changed arguments I can't
+even remember now and it kinda worked until I decided to record my voice at the
+same time. **Delay**.
+
+What to do then?
+
+Just go to the internet an keep searching.
+
+I found a project called `vokoscreen` that now is archived because it's
+migrating from `ffmpeg` to `gstreamer` (I also struggled with gstreamer in the
+past but that's another story) but it worked fine. It was in the repos of my
+distro, it only asked me to install one dependency, a couple of megs only...
+Great!
+
+I tried to make a screencast and the audio worked like a charm. I went for the
+code, read it and [realized the arguments it uses to call `ffmpeg` are easy to
+find][vokoscreen].
+
+Even better, in the program itself there's a button to show a log of what it
+does and it dumps the exact call it does.
+
+With that and some extra things I learned from the investigation in the deep
+abyss of the Internet, boom! There it goes:
+
+ ::bash
+ ffmpeg
+ -y -f x11grab -draw_mouse 1 -framerate 25 -video_size 1920x1080 -i :0+0,0 \
+ -f alsa -ac 2 -i default \
+ -pix_fmt yuv420p -c:v libx264 -preset veryfast \
+ -c:a libmp3lame -q:v 1 -s 1920x1080 -f matroska \
+ output.mkv
+
+Today in half an hour I solved the thing I've been struggling with a couple of
+days. But I think I wouldn't be able to solve it if I didn't struggle with it
+last days... I don't know.
+
+The good thing is I learned a couple of things from this I'll write down here
+to avoid forgetting them:
+
+### Multiple inputs
+
+Like the command in the example, `ffmpeg` can get multiple inputs. In the case
+of the example, they are `x11grab` (my screen) and `alsa`'s default input (the
+microphone). More inputs can be combined, like music playing in the background
+or whatever you want.
+
+### Multiple outputs
+
+There's also the chance to put multiple outputs there just like the multiple
+input thing does but in the output part of the command[^1].
+
+#### Pipe
+
+You can even pipe the command to a different one, like:
+
+```
+ffmpeg [...] - | ffplay -i -
+```
+
+In this case you can use one of the outputs to record to a file and another
+one to `ffplay` which plays the video in screen.
+
+This is useful if you want to record from a webcam and you want to see what you
+are recording.
+
+
+### Closing note
+
+So yeah, I was an ignorant about `ffmpeg` and I still am.
+
+But at least I learned a couple of the arguments and learned how to deal with
+all my cameras and screens at the same time.
+
+Good enough.
+
+I mean, it works, right? And that's the most important thing[^2].
+
+[^1]: Yes, it's hard to know where's the input and where's the output.
+[^2]: It's not. The most important thing is to be happy, do what you like,
+ enjoy your life and feel appreciated and valued. If your software works it's
+ like... Uugh... Congratulations I guess?
+
+[vokoscreen]: https://github.com/vkohaupt/vokoscreen/blob/b5865a85561baa46e627c09cf77efb7369516327/screencast.cpp#L2718