bedtime
Posts: 67
Joined: Thu Dec 13, 2018 6:02 pm

An incomplete mini notification system in C

Mon May 06, 2019 8:20 pm

I've been making a lightweight notification system on my Pi3b+. It is designed to be extremely efficient and lightweight. Currently, it can (for the most part):

- display a notification using Xlib and one other library (I make a lot of my own string functions)
- display using justification
- print any number of lines or paragraphs
- add any sized padding to text
- use any size font and have the window perfectly adjust to it
- line wrapping (pain in the butt to implement but works nicely :) )
- timed auto close

As usual, it's free for anyone to use and modify (but try to let me know if you make any changes; I'd like to see them!)

The one thing it lacks is the ability to manage more than one notification at a time. I've been looking into ways to do this. One option is named pipes. If anyone has any ideas, please let me know.

note.c:

Code: Select all

/*
      gcc -Wall -O2 note.c -o note -lX11
*/

#include<X11/Xlib.h>
#include<stdio.h>     // needed only for puts()

__inline int fast_memcmp(char *s, const char *s2)
{   
    for(; *s2; ) 
    {
        if(*s != *s2)
            return 1;

        ++s;
        ++s2;
    }

    return 0;
}

void fast_strcpy(char *s, const char *s2)
{  

    // Skip past spaces
    for(; (*s2 == ' '); )
        ++s2;
 
    for(; *s2; )
    {   
        *s = *s2;
        ++s;
        ++s2;
    }
    
    *s = '\0';
}

__inline size_t fast_strlen(const char *s)
{
    const char *p = s;
    while(*s) ++s;

    return s - p;
}

/* The window which contains the text. */
struct {
    int width;
    int height;
    int y;
    int x;
    int padding;
    int maxW;
    char * txt[128];
    int txt_len[128];
    char txtAlt[128][128];
    int txtAlt_len[128];
    int doNotWrap;
    int isParagraph[128];
    int useHints;
    int border;
    int fontSize;
    int charFontSize;
    int autowidth;
    int timer;
    char justify[16];
    int lines;
    int altLines;
    
    /* X Windows related variables. */
    Display * display;
    int screen;
    Window root;
    Window window;
    GC gc;
    unsigned long black_pixel;    
    XFontStruct * font;
} box;

/* Connect to the display, set up the basic variables. */
static void x_connect ()
{
    box.display = XOpenDisplay (NULL);
    box.screen = DefaultScreen (box.display);
    box.root = RootWindow (box.display, box.screen);
}

__inline void fast_strcat(char *s, const char *s1)
{
    while (*s) ++s;

    do{
        *s = *s1;
        ++s;
    }while (*s1++); // Concatenate & check for end of s
}

unsigned int fast_strtoul(const char *c)
{
    int x = 0;
    for(; (*c < 48 || *c > 57); )
        c++;

    for(; *c > 47 && *c < 58;)
    {
        x = (x << 1) + (x << 3) + *c - 48;
        c++;
    }

    return x;
}

void fast_memcpyS(char *str, const char *str2, int start, int length)
{
    --length;

    // Skip past spaces
    while(str2[start] == ' ' && --length)
        ++start;

    // Remove spaces at end of text
    while(str2[start + length ] == ' ' && --length);
    

/*    for(; length > -1; )
    {
        str[length] = str2[start + length ];
        --length;
    }*/

    int i = 0;

    for(; i < length + 1;)
    { 
        str[i] = str2[start + i]; 
        ++i;
    }
    str[i] = '\0';
    //str[i - 1] = '-';
    printf("string: \'%s\'\n", str);
}

void fast_tostring(unsigned int num, char *str)
{
    int i, rem, len = 0, n = num;

    while (n != 0)
    {
        ++len;
        n /= 10;
    }

    for (i = 0; i < len; ++i)
    {
        rem = num % 10;
        num = num / 10;
        str[len - (i + 1)] = rem + '0';
    }

    str[len] = '\0';
}

/* Create the window. */
static void create_window ()
{
    char font[64], strSize[12];
    font[0] = '\0';

    fast_strcat(font, "-*-terminus-medium-r-*-*-");
    fast_tostring(box.fontSize, strSize);
    fast_strcat(font, strSize);
    fast_strcat(font, "-*-*-*-*-*-*-*");

    int direction, ascent = 0, descent = 0, extraLine = 0, 
         numChars, textW = 0, longestString = 0, altTxtTotalLen = 0, altTxtLen;

    XCharStruct overall;

    box.font = XLoadQueryFont (box.display, font);
    box.charFontSize = XTextWidth(box.font, box.txt[0], 
                         box.txt_len[0]) / box.txt_len[0];

    box.padding = (box.charFontSize * box.padding) / 100;
    box.height = box.padding;
    box.altLines = 0;
    box.border = 2;

    for(int i = 0; i < box.lines; )
    {

        XTextExtents (box.font, box.txt[i], box.txt_len[i],
         &direction, &ascent, &descent, &overall);

        for(numChars = box.txt_len[i]; numChars; )
        {
            textW = XTextWidth(box.font, box.txt[i], numChars);
        
            if(textW < (box.maxW - (box.padding * 2)) || box.autowidth )
            {        
                extraLine = 0;

                for( ; (numChars * extraLine) < box.txt_len[i] || box.doNotWrap;)
                {

                    if(box.doNotWrap)
                        fast_memcpyS(box.txtAlt[box.altLines], box.txt[i], 0, numChars);
                    else
                        fast_memcpyS(box.txtAlt[box.altLines], box.txt[i], numChars * extraLine, numChars);

                    altTxtLen = fast_strlen(box.txtAlt[box.altLines]);
                    box.txtAlt_len[box.altLines] = altTxtLen;

printf("txtlen: %i\n", altTxtLen);

                    if(longestString < XTextWidth(box.font, box.txtAlt[box.altLines], box.txtAlt_len[box.altLines])) 
                        longestString = XTextWidth(box.font, box.txtAlt[box.altLines], box.txtAlt_len[box.altLines]);

                    if(!extraLine && box.altLines)
                    {
                        box.isParagraph[box.altLines] = 1;
                        box.height += (ascent * .75);
                    }else
                        box.isParagraph[box.altLines] = 0;

                    if(box.txtAlt_len[box.altLines])
                    {
                        altTxtTotalLen += altTxtLen;
                        box.altLines += 1;
                        box.height += (ascent * 1.5);
                    }

                    ++extraLine;

                    if(box.doNotWrap) 
                    {
                        for(int k = 0; XTextWidth(box.font, box.txtAlt[box.altLines] - 1, box.txtAlt_len[box.altLines - 1]) - k && k < 3; k++)
                            box.txtAlt[box.altLines - 1][altTxtTotalLen - k - 1] = '.';
                            //box.txtAlt[box.altLines - 1][numChars - k - 1] = '.';
  
                        break;
                    }
                }

                break;
           }

           --numChars;
        }
        
        ++i;
    }

    box.height += box.padding - descent;

    if(box.useHints)
        box.width = longestString + (box.padding * 2);
    else
        box.width = box.maxW; 

   if(!fast_memcmp(box.justify, "c") || !fast_memcmp(box.justify, "center"))
   { 
                  box.x = 400 - (box.width / 2);
                  box.y = 220 - (box.height / 2);
   }else if(!fast_memcmp(box.justify, "r") || !fast_memcmp(box.justify, "right"))
   {
                  box.x -= box.width + (box.border * 2);
   }

    box.window = 
        XCreateSimpleWindow (box.display,
                             box.root,
                             box.x,
                             box.y,
                             box.width,
                             box.height,
                             0,
                             BlackPixel(box.display, 0),
                             BlackPixel(box.display, 0)
                            );

    XSelectInput (box.display, box.window, ExposureMask|KeyPressMask|ButtonPressMask);
    XMapWindow (box.display, box.window);
}

/* Set up the GC (Graphics Context). */
static void set_up_gc ()
{
    XColor color;
    Colormap colormap = DefaultColormap(box.display, 0);

    XParseColor(box.display, colormap, "#00FFFF", &color);
    XAllocColor(box.display, colormap, &color);

    box.screen = DefaultScreen (box.display);
    box.gc = XCreateGC (box.display, box.window, 0, 0);

    XSetBackground (box.display, box.gc, box.black_pixel);
    XSetForeground (box.display, box.gc, color.pixel);
    XSetFont (box.display, box.gc, box.font->fid);
}

/* Draw the window. */
static void draw_screen ()
{
    int x, y, direction, ascent, descent;
    XCharStruct overall;

    XTextExtents (box.font, box.txtAlt[0],
     box.txtAlt_len[0], &direction, &ascent, &descent, &overall);

    XClearWindow (box.display, box.window);

    x = box.padding; 
    y = box.padding + ascent;

    for(int i = 0; i < box.altLines; )
    {
        
        if(box.isParagraph[i])
           y += (ascent * .75);

        XDrawString (box.display, box.window, box.gc,
                 x, y, box.txtAlt[i], box.txtAlt_len[i]);

        y += (ascent * 1.5);
        
        ++i;
    }
}

/* Loop over events. */
static void event_loop ()
{

    struct timeval tv;
    XEvent e;
 
    Atom wmDelete=XInternAtom(box.display, "WM_DELETE_WINDOW", True);
    //XSelectInput (box.display, box.window, ExposureMask|KeyPressMask|ButtonPressMask);
    XSelectInput (box.display, box.window, ExposureMask|ButtonPressMask);

    int running = 1, num_ready_fds, x11_fd = ConnectionNumber(box.display);

    fd_set in_fds;

    // Main loop
    while(running)
    {
        // Create a File Description Set containing x11_fd
        FD_ZERO(&in_fds);
        FD_SET(x11_fd, &in_fds);

        tv.tv_usec = 0;

        if(box.timer)
            tv.tv_sec = box.timer;
        else
            tv.tv_sec = 1;

        while(XPending(box.display))
        {
            XNextEvent (box.display, &e);
            switch(e.type)
            {
                case (KeyPress):
                case (ButtonPress):
                //case (ClientMessage):
                          XSetWMProtocols(box.display, 0, &wmDelete, 1);
                          running = 0;  
                          break;

                case (Expose):
                          draw_screen ();
                          break;
            }
        }

        // Wait for X Event or a Timer
        num_ready_fds = select(x11_fd + 2, &in_fds, NULL, NULL, &tv);
    
        if (!num_ready_fds && box.timer)
        {
                XSetWMProtocols(box.display, 0, &wmDelete, 1);
                running = 0;      // Exit gracefully
        }
    }
}

void printArgs()
{
            puts("note v.1 (alpha 1)- released 2019-04-25");
            puts("Usage: [options] [text]");
            puts("\nOptions:");
            puts("\n\t-x\t\t\tx cooridinate");
            puts("\n\t-y\t\t\ty cooridinate");
            puts("\n\t-fs, --fontsize \tFont size");
            puts("\n\t-w,  --maxwidth\t\tMaximum width of text");
            puts("\n\t-nh, --nohints \t\tDisable hinting. Forces fixed window\n\t\t\t\tsize (retains size of --maxwidth).");
            puts("\n\t-t,  --time\t\tDuration notification is displayed\n\t\t\t\t(in seconds). Arg '0' is indefinite.");
            puts("\n\t-j,  --justify\t\tText justification. Takes args:\n\t\t\t\t[-l|--left|-c|--center|-r|--right]");
            puts("\n\t-nw, --nowrap\t\tDisable text wrapping.");
            puts("\n\t-p,  --padding\t\tPadding (% of char size).\n\t\t\t\tE.g., for 150% padding, use: -p 150");
            puts("\n\t-a,  --autowidth\tWidth adjusts to text size.");

}

int main (int argc, char ** argv)
{

    // Set defaults for text to be display in top right corner
    box.useHints  = 1;
    box.doNotWrap = 0;
    fast_strcpy(box.justify, "right");
    box.fontSize  = 18;
    box.timer     = 3;
    box.x         = 780;
    box.y         = 25;
    box.maxW      = 300;
    box.autowidth = 0;
    box.padding   = 100;
    box.lines     = 0;
    int i = 1;

    for (; i < argc ; )
    {
        if(!fast_memcmp(argv[i], "-nh") ||
           !fast_memcmp(argv[i], "--nohints"))
        {
            box.useHints  = 0; 

        } else if(!fast_memcmp(argv[i], "-nw") ||
                  !fast_memcmp(argv[i], "--nowrap"))
        {
            box.doNotWrap = 1;

        } else if(!fast_memcmp(argv[i], "-j") ||
                  !fast_memcmp(argv[i], "--justify"))
        {
            fast_strcpy(box.justify, argv[++i]);

        } else if(!fast_memcmp(argv[i], "-fs") ||
                  !fast_memcmp(argv[i], "--fontsize"))
        {
            box.fontSize = fast_strtoul(argv[++i]);

        } else if(!fast_memcmp(argv[i], "-w") ||
                  !fast_memcmp(argv[i], "--maxwidth"))
        {
            box.maxW = fast_strtoul(argv[++i]);

        } else if(!fast_memcmp(argv[i], "-t") ||
                  !fast_memcmp(argv[i], "--time"))
        {
            box.timer = fast_strtoul(argv[++i]);

      } else if(!fast_memcmp(argv[i], "-a") ||
                  !fast_memcmp(argv[i], "--autowidth"))
        {
            box.autowidth = 1;

        } else if(!fast_memcmp(argv[i], "-x"))
        {
            box.x = fast_strtoul(argv[++i]);

        } else if(!fast_memcmp(argv[i], "-p" ) ||
                  !fast_memcmp(argv[i], "--padding"))
        {
            box.padding = fast_strtoul(argv[++i]);

        } else if(!fast_memcmp(argv[i], "-y"))
        {
            box.y = fast_strtoul(argv[++i]);

        } else if(!fast_memcmp(argv[i], "-h") ||
                  !fast_memcmp(argv[i], "--help"))
        {
            printArgs();

            return 0;
        } else 
           break;

        ++i;
    }

    for(; box.lines + i < argc; )
    {
       box.txt_len[box.lines] = fast_strlen(argv[box.lines + i]);
       box.txt[box.lines]     = argv[box.lines + i]; 

       ++box.lines;
    }

    // No text was given, so just print the filename arg as text
    if (!box.lines)
    {
       ++box.lines;

       box.txt[0] = argv[0];
       box.txt_len[0] = fast_strlen(box.txt[0]);
    }

    x_connect ();
    create_window ();
    set_up_gc ();
    event_loop ();

    return 0;
}
View github for updated versions: https://github.com/bathtime/all/blob/master/note.c#L2

Also, looking for suggestions and any input-big or small! :)

User avatar
PeterO
Posts: 4857
Joined: Sun Jul 22, 2012 4:14 pm

Re: An incomplete mini notification system in C

Mon May 06, 2019 9:19 pm

How do you use this because

Code: Select all

./note "hello world"
Segmentation fault (core dumped)
PeterO
Discoverer of the PI2 XENON DEATH FLASH!
Interests: C,Python,PIC,Electronics,Ham Radio (G0DZB),1960s British Computers.
"The primary requirement (as we've always seen in your examples) is that the code is readable. " Dougie Lawson

bedtime
Posts: 67
Joined: Thu Dec 13, 2018 6:02 pm

Re: An incomplete mini notification system in C

Mon May 06, 2019 10:45 pm

PeterO wrote:
Mon May 06, 2019 9:19 pm
How do you use this because

Code: Select all

./note "hello world"
Segmentation fault (core dumped)
PeterO
i'm very sorry about this.

You see, I'm in Canada, and the weather has been most beautiful, so there is a slight chance that I may have partaken in some ungava (43.1% alc.), Premium Canadian Gin, and thus I may have overlooked that I had entered 'terminus' as the font on line 170.

Could you please try changing that line to:

Code: Select all

fast_strcat(font, "-*-*-medium-r-*-*-");
... and recompiling?

What happens?

You can type './note -h' for a rude explanation of the parameters.

Again, I'm sorry about this. The app really wasn't designed for the public, and I just wanted to share.

Please let me know.

Thanks. :)

jahboater
Posts: 4585
Joined: Wed Feb 04, 2015 6:38 pm

Re: An incomplete mini notification system in C

Mon May 06, 2019 10:56 pm

bedtime wrote:
Mon May 06, 2019 8:20 pm
Also, looking for suggestions and any input-big or small! :)
Here are couple of small things.

All the cases of "for(; *s2; )" could be replaced with "while( *s2 )", its just a little easier to read.

You have produced many "fast" replacements for the standard library functions. The compiler knows all about these functions (they are part of the C standard) and will replace them with inline code itself - if it helps performance.

Make use of things like "*s2++" on the Pi which has auto increment addressing (though the compiler will likely do it for you).

For example, you could replace:-

Code: Select all

    for(; *s2; )
    {   
        *s = *s2;
        ++s;
        ++s2;
    }
    
with

Code: Select all

while( *s2 )
   *s++ = *s2++;

User avatar
PeterO
Posts: 4857
Joined: Sun Jul 22, 2012 4:14 pm

Re: An incomplete mini notification system in C

Tue May 07, 2019 6:08 am

Yes font selection was the problem.

Your interest in "old school" code is admirable, but I have to say, that an equivalent solution produced with modern tools is much easier.

PeterO
Discoverer of the PI2 XENON DEATH FLASH!
Interests: C,Python,PIC,Electronics,Ham Radio (G0DZB),1960s British Computers.
"The primary requirement (as we've always seen in your examples) is that the code is readable. " Dougie Lawson

bedtime
Posts: 67
Joined: Thu Dec 13, 2018 6:02 pm

Re: An incomplete mini notification system in C

Tue May 07, 2019 4:59 pm

PeterO wrote: Yes font selection was the problem.

Your interest in "old school" code is admirable, but I have to say, that an equivalent solution produced with modern tools is much easier.

PeterO
jahboater wrote:
Mon May 06, 2019 10:56 pm
Though, in the world of computers, you often end up paying for that convenience. Most of my programs have a near-instant startup, and I'd like to keep it that way.
bedtime wrote:
Mon May 06, 2019 8:20 pm
Also, looking for suggestions and any input-big or small! :)
Here are couple of small things.

All the cases of "for(; *s2; )" could be replaced with "while( *s2 )", its just a little easier to read.
Agreed and done.
You have produced many "fast" replacements for the standard library functions. The compiler knows all about these functions (they are part of the C standard) and will replace them with inline code itself - if it helps performance.
I took them out. Thanks.
Make use of things like "*s2++" on the Pi which has auto increment addressing (though the compiler will likely do it for you).

For example, you could replace:-

Code: Select all

    for(; *s2; )
    {   
        *s = *s2;
        ++s;
        ++s2;
    }
    
with

Code: Select all

while( *s2 )
   *s++ = *s2++;
I agree with replacing the 'for' with 'while', but I just did a benchmark on the very functions below and found my code to be significantly faster:

#1.

Code: Select all

int fast_memcmp(char *s, const char *s2)
{
    while(*s2)
    {
        if(*s != *s2)
            return 1;

        ++s;
        ++s2;
    }

    return 0;
}
#2.

Code: Select all

int fast_memcmp2(char *s, const char *s2)
{
    while(*s2)
        if(*s++ != *s2++)
            return 1;

    return 0;
}
Results when ran 10000000x:

1. 0m05.35s real 0m05.35s user 0m00.00s system
2. 0m05.43s real 0m05.41s user 0m00.00s system

These results were consistent and varied by only +.01s. That's over 1% performance difference; I'll take it.

Return to “C/C++”