Tuesday Stream moved to Thanksgiving Weekend

Abner Coimbre Nov. 22, 2016, 4:01 p.m.
Hello community,

I'm currently recovering from jetlag and a bit of a cold after an amazing weekend in Seattle at Handmade Con 2016. I'll be streaming this Thanksgiving weekend instead. Details in a day or two, both here and on my twitter.

Yours truly,
Abner
1 comments

Tuesday Stream at 2:00PM PST / 5:00PM EST

Abner Coimbre Nov. 15, 2016, 1:48 p.m.
I'll be streaming today on my Twitch. Might fumble as I get a good Linux stream going (the terminal version of Ave is being developed there). Hope you may join me if you have the time.
0 comments

[Devlog #002] Introducing TMUI

Abner Coimbre Nov. 1, 2016, 4 a.m.
Handmade Reader,

Happy Halloween!

I am wrapping up the revision for Ave's terminal UI code, from which a single-header library, TMUI (Terminal Mode User Interface... because TMUX is taken), is being formed. Initially, whenever I needed to render to the terminal, I would make a number of ncurses calls and move on. I would compress them into functions, but they weren't straightforward to reuse for new UI components. For example, to enable the user to type into a text field, I would call FillEntry(...):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int FillEntry(win_t entry, string_t *buf)
{
    int ch;

    assert(entry.curses_win);
    assert(buf);

    while ((ch = wgetch(entry.curses_win)) != '\n')
    {
        switch (ch)
        {
            case '\t':
            case KEY_RESIZE:
                return ch;

            case BS:
            case DEL:
            {
                if (!buf->sz)
                    continue;
                memset(buf + buf->sz - 1, '\0', sizeof(char));
            } break;

            default:
            {
                if (buf.sz == ENTRY_LEN)
                    continue;
                memset(buf + buf->sz, ch, sizeof(char));
            } break;
        }

        UpdateEntry(entry, buf);
    }

    return ch;
}

The entry parameter simply stores a ncurses WINDOW and some properties about the window (e.g. width, height, hot, cold, etc.). Whichever char is "typed into the entry" will be stored in buf, and then I call UpdateEntry(...) to reflect that on le screen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void UpdateEntry(win_t entry, string_t *buf)
{
    assert(entry.curses_win);
    assert(buf);
    werase(entry.curses_win);

    wattron(entry.curses_win, entry.attr);

    if (entry.active)
        waddch(entry.curses_win, '[');

    if (entry.private)
    {
        char hidden_buf[ENTRY_LEN+1] = {0};
        memset(hidden_buf, '*', buf->sz * sizeof(char));
        waddstr(entry.curses_win, hidden_buf + EntryOffset(hidden_buf));
    }
    else
    {
        waddstr(entry.curses_win, buf->ptr + EntryOffset(buf->ptr));
    }

    if (entry.active)
        waddch(entry.curses_win, ']');

    wattroff(entry.curses_win, entry.attr);

    wrefresh(entry.curses_win);
}

So these two defunct functions would for example power the text fields present on the now-familiar login screen:



They're defunct because they weren't useful when considering entries that strongly deviated from the traditional entry like large text boxes or search fields. The edge cases would make the tangled mess I wanted to avoid through compression in the first place. I decided properly defining data types for each UI component and some related functions would be fine, even if some repetition were to occur. Then have the main code use this API without considering ncurses at all, and avoid the need to compress any of its routines. They follow a naming convention similar to ncurses, however, so it's not too unfamiliar. Below is the addch and delch tmui versions for the new txt_field_t types:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void tmui_txt_field_addch(txt_field_t *t, char ch)
{
    int bufsz;
    int titlesz;
    int vischrs; /* visible-characters-allowed count */

    assert(t);
    assert(t->win);
    assert(t->hot);

    bufsz = strlen(t->buf);
    titlesz = strlen(t->title);
    vischrs = TXT_FIELD_COLS - titlesz - 3;
    
    if (bufsz >= TXT_FIELD_MAX_LEN)
        return;

    memset(t->buf + bufsz++, ch, sizeof(char));

    ch = t->private ? '*' : ch;

    if (bufsz > vischrs && !t->private)
    {
        mvwaddstr(t->win, 0, titlesz+2, t->buf + ++t->offset);
        mvwaddch(t->win, 0, TXT_FIELD_COLS-2, ch);
    }
    else 
    {
        mvwaddch(t->win, 0, titlesz + bufsz + 1, ch);
        waddch(t->win, CLOSING_SYMBOLS[t->type]);
    }

    wrefresh(t->win);
}


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void tmui_txt_field_delch(txt_field_t *t)
{
    int bufsz;
    int titlesz;
    int vischrs; /* visible-characters-allowed count */

    assert(t);
    assert(t->win);
    assert(t->hot);

    bufsz = strlen(t->buf);

    if (!bufsz)
        return;

    titlesz = strlen(t->title);
    vischrs = TXT_FIELD_COLS - titlesz - 3;

    memset(t->buf+bufsz-1, 0, sizeof(char));

    if (bufsz-- > vischrs && !t->private)
    {
        mvwaddstr(t->win, 0, titlesz+2, t->buf + --t->offset);
    }
    else
    {
        mvwaddch(t->win, 0, titlesz + bufsz + 2, CLOSING_SYMBOLS[t->type]);
        mvwaddch(t->win, 0, titlesz + bufsz + 3, ' ');
    }

    wrefresh(t->win);
}

[Note: Funny how properly deleting a character is a little involved. We shouldn't dismiss the simple as trivial.]

These handle private text fields, a title, the offset when characters go over the width, and the style of the text field (curly braces, regular braces, parentheses, etc.). The text field itself now holds the contents typed in so far. There are other functions like txt_field_set_hot (whether to highlight it or not), txt_field_update (which replaced the generic FillEntry), etc. And there are similar functions for text boxes, buttons, labels, email items, etc... except when they're not similar. Then they have their own unique routines, and I call those instead :)

Of course, if these UI components ever proliferate, a less repetitive strategy would be wiser.



All of what you see are their own set of UI components now, with a proper barrier between my code and ncurses! One can introduce the notion of backends, and switch between an ncurses implementation of tmui with some future one. Of course, this isn't anything terribly new or exciting in the world of software development. However, now I may trivially add terminal theming using tmui, after which I complete Ave Terminal 1.0, and give my full attention to the graphical mode user interface (GMUI? No?).

Until November's monthly update. We'll see if I get to stream too (probably not due to Handmade Con!). Previous stream may be found here
5 comments

[Devlog #001] The Ave Mail Client

Abner Coimbre Sept. 28, 2016, 2:31 p.m.
Dear Reader,

I'm happy to talk about Ave with you.

Since I announced a devlog on late April 2016, awesome developers have supported this personal mission but have also slapped me hard for not keeping anyone posted. Months later, and with several metaphoric bruises, I'm raring to discuss Ave—reaching a reasonable balance in my life where I may post monthly. The same goes for the show, I hope. Yesterday's first programming stream gives an overview, and is on my Twitch as a highlighted feature (should you prefer to look at curly hair..)

This devlog expands on that overview, and allows you to skip the stream.

A Mail Client?

There are several reasons why I selected e-mail as my handmade project.

  • Sean Barrett is an inspiration to me, and I’ve looked up to him for as long as my brain may remember. I strive to be a C programmer of his caliber. As a thankful developer, I’d like Ave to replace his e-mail client. The terminal version would not meet his needs, but Ave in GUI mode likely will.

  • For the past two years I’ve focused on protocols and how to meet them with specific implementations, but they’ve been internal to the NASA agency. These are implementations done in large teams of very capable people. I always wanted to see what it would be like to implement an open protocol instead, on my own, such as IMAP. I’m also fascinated by protocols in general, and most people don’t realize the incredible extent in which they are essential in computer science.

  • E-mail is an old, presumably reliable technology from the 60’s and 70’s and an integral part of our digital culture. Which is all the more terrifying as to why today’s mail clients suffer from being integrated into environments that slow this tech down, such as Gmail, for a variety of complicated software-is-fat-and-bloated reasons (which we could go into in future posts? Might be counterproductive). To my knowledge, there is no modern, slim, fast, out-of-your-way mail client that is solely focused on sending and receiving e-mail in a pleasant self-contained environment. We should have no excuse of why we can't make that happen. The closest case is Thunderbird. It is close to what I envision at the surface level, but let’s go ahead and tackle precisely what I want.

  • What I Want

    1. Slim Technology Stack – Ave is being developed as a bunch of ANSI C files on Linux. I’m implementing the protocols myself (so far only IMAP) in a style similar to libcurl but just for mail protocols, making it the core technology of Ave (libave). For terminal mode I use ncurses, which is available on every Linux distribution. For gui mode, I’m using Vurtun’s nuklear, which is highly customizable and ANSI C compatible; it allows me to choose my own rendering backend (e.g. Xlib).

    Ideally, you should be able to compile the whole of Ave with any C89 compiler and without the need to install extra libraries, and that portability is what I meant by self-contained environment.

    2.Minimalism – Both terminal and gui Ave should look good and modern, but their design should only reflect the functions at hand – reading and writing mail. The less you have to look at, the more you know it’s dead simple to navigate and get your job done. I’m also focusing on the functionality needed to meet the common operations required by a typical user. Anything extra would have to be strongly justified for its inclusion, and it cannot compromise ease-of-use.

    3. Speed – So far, accessing my mail has been incredibly fast, orders of magnitude faster than any web client I’ve ever used. I do not know what happens behind the scenes when you’re logging into your e-mail anywhere else, but I can now assuredly tell you it’s not just a simple IMAP request. I’m unaware of the final speed of Ave until it’s fully developed, I’ll grant you that, but staying fast is a central metric.

    How Has It Been So Far?

    The biggest lesson is that not everyone is RFC-compliant. It was a shock seeing some companies accept ill-formed e-mail addresses, developers showing their best-but-still-inaccurate regular expressions for compliance, and security agents from company’s mail servers trumping simple IMAP requests that should have yielded a proper response, but didn’t. Look, I always knew commercial software packages don’t fully adhere to a spec—not even language compilers achieve 100% accuracy—but seeing violations led to unfortunate wrinkles and hard-coding in specific recovery points when I try to talk to some servers.

    In fact, it was so difficult trying to define a proper e-mail address, that after a week of research, the best solution ended up being:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    /*
     * It seems no e-mail company completely adheres to the spec,
     * so I just check if an address is of the form:
     *
     * [ch]@[ch]
     *
     * Where there is at least one char before and after the at-symbol.
     */
    static bool valid_email(char *addr)
    {
        int sz;
        int at_cnt;
    
        if (!addr)
            return FALSE;
    
        sz = strlen(addr);
        at_cnt = 0;
    
        if (sz < 3 || *addr++ == '@')
            return FALSE;
    
        do
        {
            if (*addr++ == '@')
                ++at_cnt;
        } while (*addr);
    
        if (!at_cnt || at_cnt > 1 || *--addr == '@')
            return FALSE;
    
        return TRUE;
    }
    

    [Note: I'm following the ncurses boolean system, where the data type is bool and the states are TRUE or FALSE.]

    When Do I Get To Use Ave?


    I have failed at giving proper deadlines time and again. I announced Ave here on April 2016, and 5 months later, I have a working prototype on terminal that is being redesigned to include the bird Ave in some unobtrusive animations (e.g. if you’re writing an e-mail, expect a small birdy flying on the bottom left to notify you have a new e-mail), and to provide a proper barrier between ncurses and application logic. I also have a very-unfinished GUI mode being worked on. It might be another 5 months before I reach a working prototype for that, and probably another two months of polish before I consider the possibility of releasing some build publicly. Thus, it’ll be a year or so into development before I think of opening it up, although having early testers may or may not be a good idea?

    What About Updates???

    I'd like to stream programming or make a devlog each month (or both), at least explaining why I can’t post an update at a given time. This month was just an overview of what I care for Ave, and your feedback means the world to me.

    Next Time

    Next time I'll attempt discussing Ave's attempts at achieving minimalism and how they're evolving, starting with the input system / navigation. If anything is more interesting that I should report though, I'll talk about that instead!
    4 comments

    Devlog #001 [Coming Soon]

    Abner Coimbre April 26, 2016, 12:50 a.m.
    If you're here, congrats! Handmade Network has launched. The first devlog, packed with details of Ave's goals and current progress will be up sometime soon.
    0 comments