Graphic programming with Mocka

Elsewhere in this section, you can see attempts to perform graphic programming using X11 under Linux. The best documented example of X11 for Mocka was done in the XMOD project by 'Nicky' from Switzerland. Later, Dr Maurer of FU Berlin created Murus, which was a full port of X11 to Modula-2. Use the navigator frame on the right to access these sections. (No navigator? consult the heading on top and bottom of this page) Both projects are way over my head. Much too complex for me. So I decided to try another route: make a FOREIGN MODULE that relies on a very small section of X11 to do some basic tasks from Modula-2. Something similar to what XYplane is for obc. And I succeeded! Thanks to Vladimir Borisenko!

The steps performed are:

All source code can be downloaded in a tgz file in the bottom section of this page.

Create x11.def

X11 is huge. It can do everything you want. Some say, it can even flush your toilets. But all that power comes at a price. X11 is written in C and C does not support Qualified Imports. It doesn't even allow imports... So you need to include predefined libraries and to prevent naming conflicts you get thousands of symbolic names of a complexity that is almost unbelievable.
This is not Modula-2 ish. So I decided to make a bare bones interface that has a minimal subset of X11 functions, enabling me to yank a window open on screen and do some basic tasks. Here is the DEFINITION MODULE

(* This is the Modula-2 native part of a Foreign Module called x11
 * 
 * x11 will support just ONE window to do graphic things from within
 * Modula-2 and relies on x11.c
 * 
 * CopyLeft 2015 Jan Verhoeven, Tilburg
 *)

FOREIGN MODULE x11;

PROCEDURE	mSleep (n : CARDINAL);			(* Sleep 'n' milliseconds	*)

PROCEDURE	setTitle (text : ARRAY OF CHAR);	(* Set window title		*)

PROCEDURE	setBgC (colour : CARDINAL);		(* Set Background colour	*)

PROCEDURE	setFgC (colour : CARDINAL);		(* Set Foreground colour	*)

PROCEDURE	Clear ();				(* Clear screen			*)

PROCEDURE	Plot (x, y : CARDINAL);			(* Plot one pixel in Fg colour	*)

PROCEDURE	printString (x, y : CARDINAL; string : ARRAY OF CHAR);
							(* Print a string at (x,y)	*)

PROCEDURE	Line (x1, y1, x2, y2 : CARDINAL);	(* Draw a line			*)

PROCEDURE	Arc (x, y, width, height, arc_start, arc_stop : CARDINAL);
							(* Draw an arc (see man page	*)

PROCEDURE	Rect (x, y, width, height : CARDINAL);	(* Draw an empty box		*)

PROCEDURE	FillArc (x, y, width, height, arc_start, arc_stop : CARDINAL);
							(* Draw a filled circle segment	*)

PROCEDURE	FillRect (x, y, width, height : CARDINAL);
							(* Draw a solid box in Fg color	*)

PROCEDURE	Xinit (W, H : CARDINAL);		(* Initialize X and open a window	*)

PROCEDURE	NextEvent () : INTEGER;			(* X11 main loop component	*)

PROCEDURE	Xclose ();				(* Shut down the window		*)

END x11.
   
My work heavily relies on a very well written page on the net by some russian guy (I think).. I had great fun reading his stuff at http://math.msu.su/~vvb/2course/Borisenko/CppProjects/GWindow/xintro.html.

Create x11.c

Now that we have defined what we want to be done in the DEF module, it is time to create the working horse module x11.c:

/* This is the foreign part of a Modula-2 Foreign Module called x11
 * 
 * x11 will support just ONE window to do graphic things from within
 * Modula-2.
 * 
 * CopyLeft 2015 Jan Verhoeven, Tilburg
 */

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <time.h>

/* * Global variables * */

Display 	*dis;
Window 		win;
GC 		gc;
XEvent 		event;
KeySym 		key;

int		screen;
int		maxX = 640, maxY = 480;
unsigned long 	black, white;
char		windowTitle [65];
char 		text [255];

/* * Functions * */


void mSleep (unsigned int nap)
{
      usleep (1000 * nap);
}


void	Xinit (int mx, int my) 
{
   dis = XOpenDisplay ( (char *) 0);
   screen = DefaultScreen (dis);
   black = BlackPixel (dis, screen);	/* get color black */
   white = WhitePixel (dis, screen);  	/* get color white */
   win = XCreateSimpleWindow (dis, DefaultRootWindow (dis), 0, 0, mx, my, 5, white, black);
   XSetStandardProperties (dis, win, windowTitle, "MockaX11", None, NULL, 0, NULL);
   XSelectInput (dis, win, ExposureMask|ButtonPressMask|KeyPressMask);
   
   gc = XCreateGC (dis, win, 0, 0);	/* create the Graphics Context */
   
   /* here is another routine to set the foreground and background
    *    colors _currently_ in use in the window.
    * */
   XSetBackground (dis, gc, black);
   XSetForeground (dis, gc, white);
   
   /* clear the window and bring it on top of the other windows */
   XClearWindow (dis, win);
   XMapRaised (dis, win);
}


void Xclose ()
{
   XFreeGC (dis, gc);
   XDestroyWindow (dis, win);
   XCloseDisplay (dis);
   exit (1);
}


int	NextEvent (void)
{
   XNextEvent(dis, &event);
   if (event.type == Expose && event.xexpose.count == 0) 
     {
	/* the window was exposed redraw it! */
	return -1000;
     }
   if (event.type == KeyPress && XLookupString (&event.xkey,text, 255, &key, 0) == 1) 
     {
	/* use the XLookupString routine to convert the invent
	 * KeyPress data into regular text.  Weird but necessary...
	 */
	return (text [0]) * -1;
     }
   if (event.type == ButtonPress) 
     {
	/* tell where the mouse Button was Pressed */
	int x = event.xbutton.x, y = event.xbutton.y;
	return x<<16 + y;
     }
}


void	setTitle (char * text)
{
   strcpy (windowTitle, text);
}


void	setBgC (unsigned long colour)	/* set background colour */
{
   XSetBackground (dis, gc, colour);
}


void	setFgC (unsigned long colour)	/* set foreground colour */
{
   XSetForeground (dis, gc, colour);
}


void	Plot (int x, int y)
{
   XDrawPoint (dis, win, gc, x, y);
}


void	printString (int x, int y, char * string)
{ 
   XDrawString (dis, win, gc, x, y, string, strlen(string));
}


void	Clear (void)
{
   XClearWindow(dis, win);
}


void	Line (int x1, int y1, int x2, int y2)
{
   XDrawLine (dis, win, gc, x1, y1, x2, y2);
}


void	Arc (int x, int y, int width, int height, int arc_start, int arc_stop)
{
   XDrawArc (dis, win, gc, x, y, width, height, arc_start, arc_stop);
}


void	Rect (int x, int y, int width, int height)
{
   XDrawRectangle (dis, win, gc, x, y, width, height);
}


void	FillArc (int x, int y, int width, int height, int arc_start, int arc_stop)
{ 
   XFillArc (dis, win, gc, x, y, width, height, arc_start, arc_stop);
}


void	FillRect (int x, int y, int width, int height)
{
   XFillRectangle (dis, win, gc, x, y, width, height);
}

/* * * * - - - - - - * * * */
   
Experience, so far, with but it was enough to create a graphic screen that was in control!

Compiling the modules

First we need to compile the C module to an object file with

gcc -lX11 -c x11.c
This creates x11.o, which we need to manually copy to ./m2bin/ in order for mocka to find it.

Next we need to connect the DEF module to the Object module: Done. Now we can use it. So I wrote a minimal program to test things:
MODULE xt;

IMPORT x11;

VAR	task	: INTEGER;

BEGIN
  x11.Xinit (640, 480);
  LOOP
    task := x11.NextEvent ();
    IF  task < 0  THEN
      task := -task;
      IF  task = ORD ('q')  THEN  EXIT  END
    END
  END;
  x11.Xclose ()
END xt.
   
Restart the mocka IDE script and issue the commands And now you're in for a surprise. Your screen fills with error messages which are hard to comprehend. You can follow the hints supplied by the linker but you will not resolve the problems. The problem is: mocka needs to link the executable 'xt' against the X11 library.... Now this could cause a lot of problems when it would have required us to rewrite the link script but lucky for us, when Dr Maurer (of FU Berlin) created the 0608m release, he rewrote the link script into:
#!/bin/sh
#
# Christian Maurer   v. 14. August 2006

case $1 in
'-elf' ) shift ;;
*      ) ;;
esac

case $1 in
'-g' ) shift ;;
*    ) ;;
esac

Programm=${1##*/}; Verzeichnis=${1%/*}; shift

gcc -D_REENTRANT -o $Programm $MOCKA/sys/M2RTS.o $* $MOCKALINK

strip $Programm

ln -sf ../$Programm $Verzeichnis/$Programm
   
If you do not understand some of the words: they're german. Look at the line in green. Look at the last word: '$MOCKALINK'. Dr Maurer enabled us to add linker commands through the (new) environment variable 'MOCKALINK'.

OK, let's see if this works. First I define the variable with
export MOCKALINK=-lX11
Now restart the mocka script and issue the command 'p xt'. It compiles.... Thanks to Dr Maurer.

See it work

On the right we see the result of running the 'xt' command. It's a no frills window. It proves only one things: IT WORKS! In order to prevent a nonsense window pushing the text too far to the left I changed the source of xt.mod so it would create a 400 x 400 window.

Here's the source of xt.mod once more:

MODULE xt;

IMPORT x11;

VAR	task	: INTEGER;

BEGIN
  x11.Xinit (400, 400);
  LOOP
    task := x11.NextEvent ();
    IF  task < 0  THEN
      task := -task;
      IF  task = ORD ('q')  THEN  EXIT  END
    END
  END;
  x11.Xclose ()
END xt.
   
My first attempt looked like this:
MODULE xt;

IMPORT x11;

VAR	task	: INTEGER;

BEGIN
  x11.Xinit (400, 400);
  x11.mSleep (3000); 
  x11.Xclose ()
END xt.
   
but that did not produce a window. The only noticeable thing was the 3 second delay.... X11 needs the NextEvent function for creating the window.

I mentioned that the X11 window is in control. Run the program and press the 'q' key. The window is closed and control is returned ot the command interpreter.

Printing and plotting

A clean window is nice, but I need to do something sensible in the window... So I changed the source accordingly:

MODULE xt;

IMPORT x11;

VAR	task	: INTEGER;

BEGIN
  x11.setTitle ("Mocka XT test");
  x11.Xinit (400, 400);
  x11.setFgC (0FFFF00H);
  x11.setBgC (0FFH);
  x11.Line (10, 10, 400, 240);
  task := x11.NextEvent ();
  x11.printString (100, 100, "Mocka Rulez");
  LOOP
    task := x11.NextEvent ();
    IF  task < 0  THEN
      task := -task;
      IF  task = ORD ('q')  THEN  EXIT  END
    END
  END;
  x11.Xclose ()
END xt.
   
As you can see: it works. The picture shows some yellow text (apparently the setFgC works as expected) and the position may well be (100, 100) relative to the topleft corner. This may need some more attention in order to make the origin work off the bottomleft corner, just like any XY coordinate system in math. There's only one oddity now: If I leave out the line in green, the screen remains blank. If I put it in, the line following it will be processed, not the line preceding it.... People warned me that learning X11 programming can be a nightmare.

Doing the unlogical

I changed the source to:

MODULE xt;

IMPORT x11;

VAR	task	: INTEGER;

BEGIN
  x11.setTitle ("Mocka XT test");
  x11.Xinit (400, 400);
  x11.setFgC (0FFFF00H);
  x11.setBgC (0FFH);
  task := x11.NextEvent ();
  x11.Line (10, 10, 400, 240);
  x11.printString (100, 100, "Mocka Rulez");
  LOOP
    task := x11.NextEvent ();
    IF  task < 0  THEN
      task := -task;
      IF  task = ORD ('q')  THEN  EXIT  END
    END
  END;
  x11.Xclose ()
END xt.
   
The call to NextEvent is now preceeding the actions.... not following it. OK, it seems to do the trick, but WHY? Tcl/Tk is known for 'quoting hell' since the quoting characters show unpredictable interactions. Does X11 have a Flushing Hell?

Redrawing the screen

I still don't fully understand why and how plotting data are sent to the screen. The call to NextEvent plays a major role, that's for sure. Still, by fooling around with the NextEvent line I can get some work done. On the right is the result of the current xt.mod, shown below:

MODULE xt;

IMPORT x11;

VAR	task	: INTEGER;


PROCEDURE build;

BEGIN
  x11.setFgC (0FFFF00H);
  x11.setBgC (0FFH);
  x11.Line (10, 10, 400, 240);
  x11.FillRect (20, 135, 100, 200);
  x11.printString (100, 100, "Mocka Rulez");
END build;


BEGIN
  x11.setTitle ("Mocka XT test");
  x11.Xinit (400, 400);
  task := x11.NextEvent ();
  build;
  LOOP
    task := x11.NextEvent ();
    IF  task = -1000  THEN
      task := x11.NextEvent ();
      build
    ELSIF  task < 0  THEN
      task := -task;
      IF  task = ORD ('q')  THEN  EXIT  END
    END
  END;
  x11.Xclose ()
END xt.
   
The actual printing is now done in a procedure and when the screen has been obscured by another window and now is moved on top again, NextEvent signals it and the build procedure is called to rebuild the screen. This works in 90% of the cases.... But it works. Just try it out... Here is the executable: xt3.exe Download it, make it executable (chmod 755 xt3.exe) and run it. Play hide and seek with the window and see for yourself. One word: I called this version of xt 'xt3.exe' but this is a LINUX executable, not a Win$low$ file.

Select another font

This has been a hard to find topic.. For some reason font selection always seems to be related to word processors or command line terminals. Not for programs that require a bigger font. By sheer coinsidence I eventually ran into the XLoadFont function call and then things got easier. A quick test in the hi.c program confirmed the ideas I extracted from the man pages.
So now x11 has a SetFont command. Here's the new x11.def file:

(* This is the Modula-2 native part of a Foreign Module called x11
 * 
 * x11 will support just ONE window to do graphic things from within
 * Modula-2 and relies on x11.c
 * 
 * CopyLeft 2015 Jan Verhoeven, Tilburg
 *)

FOREIGN MODULE x11;

PROCEDURE	mSleep (n : CARDINAL);

PROCEDURE	setTitle (text : ARRAY OF CHAR);

PROCEDURE	setBgC (colour : CARDINAL);

PROCEDURE	setFgC (colour : CARDINAL);

PROCEDURE	SetFont (fontname : ARRAY OF CHAR);

PROCEDURE	Clear ();

PROCEDURE	Flush ();

PROCEDURE	Plot (x, y : CARDINAL);

PROCEDURE	printString (x, y : CARDINAL; string : ARRAY OF CHAR);

PROCEDURE	Line (x1, y1, x2, y2 : CARDINAL);

PROCEDURE	Arc (x, y, width, height, arc_start, arc_stop : CARDINAL);

PROCEDURE	Rect (x, y, width, height : CARDINAL);

PROCEDURE	FillArc (x, y, width, height, arc_start, arc_stop : CARDINAL);

PROCEDURE	FillRect (x, y, width, height : CARDINAL);

PROCEDURE	Xinit (W, H : CARDINAL);

(*	
	Init initializes X11, the variables and opens the window on screen	
*)

PROCEDURE	NextEvent () : INTEGER;

PROCEDURE	Xclose ();

END x11.
   
And here is the associated x11.c file:
/* This is the foreign part of a Modula-2 Foreign Module called x11
 * 
 * x11 will support just ONE window to do graphic things from within
 * Modula-2.
 * 
 * CopyLeft 2015 Jan Verhoeven, Tilburg
 */

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <time.h>

/* * Global variables * */

Display 	*dis;
Window 		win;
GC 		gc;
XEvent 		event;
KeySym 		key;
Font		font;

int		screen;
int		maxX = 640, maxY = 480;
unsigned long 	black, white;
char		windowTitle [65];
char 		text [255];

/* * Functions * */


void 	mSleep (unsigned int nap)
{
      usleep (1000 * nap);
}


void	SetFont (char * fontname)
{
   font = XLoadFont (dis, fontname);
   XSetFont (dis, gc, font);
}


void	Xinit (int mx, int my) 
{
   dis = XOpenDisplay ( (char *) 0);
   screen = DefaultScreen (dis);
   black = BlackPixel (dis, screen);	/* get color black */
   white = WhitePixel (dis, screen);  	/* get color white */
   win = XCreateSimpleWindow (dis, DefaultRootWindow (dis), 0, 0, mx, my, 5, white, black);
   XSetStandardProperties (dis, win, windowTitle, "MockaX11", None, NULL, 0, NULL);
   XSelectInput (dis, win, ExposureMask|ButtonPressMask|KeyPressMask);
   
   gc = XCreateGC (dis, win, 0, 0);	/* create the Graphics Context */
   
   /* here is another routine to set the foreground and background
    *    colors _currently_ in use in the window.
    * */
   XSetBackground (dis, gc, black);
   XSetForeground (dis, gc, white);
   
   /* clear the window and bring it on top of the other windows */
   XClearWindow (dis, win);
   XMapRaised (dis, win);
}


void	Xclose ()
{
   XFreeGC (dis, gc);
   XDestroyWindow (dis, win);
   XCloseDisplay (dis);
   exit (1);
}


void	Flush (void)
{
   XFlush (dis);
}


int	NextEvent (void)
{
   XNextEvent(dis, &event);
   if (event.type == Expose && event.xexpose.count == 0) 
     {
	/* the window was exposed redraw it! */
	return -1000;
     }
   if (event.type == KeyPress && XLookupString (&event.xkey,text, 255, &key, 0) == 1) 
     {
	/* use the XLookupString routine to convert the invent
	 * KeyPress data into regular text.  Weird but necessary...
	 */
	return (text [0]) * -1;
     }
   if (event.type == ButtonPress) 
     {
	/* tell where the mouse Button was Pressed */
	int x = event.xbutton.x, y = event.xbutton.y;
	return x<<16 + y;
     }
}


void	setTitle (char * text)
{
   strcpy (windowTitle, text);
}


void	setBgC (unsigned long colour)	/* set background colour */
{
   XSetBackground (dis, gc, colour);
}


void	setFgC (unsigned long colour)	/* set foreground colour */
{
   XSetForeground (dis, gc, colour);
}


void	Plot (int x, int y)
{
   XDrawPoint (dis, win, gc, x, y);
}


void	printString (int x, int y, char * string)
{ 
   XDrawString (dis, win, gc, x, y, string, strlen(string));
}


void	Clear (void)
{
   XClearWindow(dis, win);
}


void	Line (int x1, int y1, int x2, int y2)
{
   XDrawLine (dis, win, gc, x1, y1, x2, y2);
}


void	Arc (int x, int y, int width, int height, int arc_start, int arc_stop)
{
   XDrawArc (dis, win, gc, x, y, width, height, arc_start, arc_stop);
}


void	Rect (int x, int y, int width, int height)
{
   XDrawRectangle (dis, win, gc, x, y, width, height);
}


void	FillArc (int x, int y, int width, int height, int arc_start, int arc_stop)
{ 
   XFillArc (dis, win, gc, x, y, width, height, arc_start, arc_stop);
}


void	FillRect (int x, int y, int width, int height)
{
   XFillRectangle (dis, win, gc, x, y, width, height);
}

/* * * * - - - - - - * * * */
   

Compile it, run it

On the right you see the spoiler graph... The font is now much bigger. Below is the source for xy.mod:

MODULE xy;

IMPORT x11;

VAR	task	: INTEGER;


PROCEDURE build;

BEGIN
  x11.setFgC (0FFFF00H);
  x11.setBgC (0FFH);
  x11.Line (10, 10, 400, 240);
  x11.FillRect (20, 135, 100, 200);
  x11.Arc (200, 200, 100, 100, 0, 5000);
  x11.FillArc (250, 250, 100, 100, 0, 5000);
  x11.setFgC (0FF00FFH);
  x11.printString (100, 200, "Mocka Rulez");
END build;


BEGIN
  x11.setTitle ("Mocka XT test");
  x11.Xinit (400, 400);
  x11.SetFont ("10x20");
  task := x11.NextEvent ();
  build;
  LOOP
    task := x11.NextEvent ();
    IF  task = -1000  THEN
(*      task := x11.NextEvent (); *)
      build
    ELSIF  task < 0  THEN
      task := -task;
      IF  task = ORD ('q')  THEN  EXIT  END
    END
  END;
  x11.Xclose ()
END xy.
   

Compiling

All files are in mockax11-02.tgz which can be downloaded below.

Reading back the mouse coordinates

For some silly reason NextEvent did not return correct mouser data. So I changed x11.c from

return x<<16 + y;
into
return 10000 * x + y;
and recompiled to a new object module and later recompiled the foreign module. Then I changed xy.mod to test the new module. Here's the spoiler picture:

I must admit that I felt kind of happy. It just works.... If I click on a certain point, the 'x' and 'y' coordinates are printed in the terminal screen. And 'Click' is printed in the GUI window. Here's the new source of xy.mod. No need to retype or cut/paste it. All files are in mockax11-03.tgz which can be downloaded below:

MODULE xy;

IMPORT x11, InOut;

VAR	x, y,
	task	: INTEGER;


PROCEDURE build;

BEGIN
  x11.setFgC (0FFFF00H);
  x11.setBgC (0FFH);
  x11.Line (10, 10, 400, 240);
  x11.FillRect (20, 135, 100, 200);
  x11.setFgC (0FFH);
  x11.Arc (100, 100, 200, 200, 0, 64*360);
  x11.setFgC (0FF0000H);
  x11.FillArc (50, 50, 250, 50, 0, 270*64);
  x11.setFgC (0FF00FFH);
  x11.printString (100, 200, "Mocka Rulez");
END build;


BEGIN
  x11.setTitle ("Mocka XT test");
  x11.Xinit (400, 400);
  x11.SetFont ("10x20");
  task := x11.NextEvent ();
  build;
  LOOP
    task := x11.NextEvent ();
    IF  task = -1000  THEN
      build
    ELSIF  task < 0  THEN
      task := -task;
      IF  task = ORD ('q')  THEN  EXIT  END
    ELSIF  task > 0  THEN	(* Mouse click!	*)
      x := task DIV 10000;
      y := task MOD 10000;
      InOut.WriteInt (x, 5);
      InOut.WriteInt (y, 6);
      InOut.WriteLn;
      x11.printString (x, y, "Click!");
    END
  END;
  x11.Xclose ()
END xy.
   
At this point it is obvious that x11 will only support a 10.000 x 10.000 pixel screen. But it also means that the detour with the negative numbers for relaying status information, is not needed anymore. If we say that all numbers between 100.000.000 and 101.000.000 are for character passing and all numbers above 101.000.000 are for status information, it suffices to declare the required constants in x11.def and recompile.


Downloads

Page created June 25, 2015 and