!Snap

(Network Application and playing with the TaskWindow module in the Wimp)
Current release is v0.2
Author: Benoît GILON
Logo de HTMLEdit v3
Back to the main !Snap WWW page

Overview

!Snap is an application suite that allow to display the desktop of any TCP network reachable station on the local Acorn computer (in a window on the local desktop).

!Snap is a development from what I learnt by reading a CAUGers past article by Tom Hughes explaining how to do easy networking from a taskwindow task. This method was new to me at this time as it was not even described in the RO3.6 PRM supplement. Briefly stated, it shows that modifications to network applications source code can be very minimalist to make them run happily under the Wimp cooperative multitasking environment. As the official Acorn network library does'nt mention the option to set, I used the NetLib library which provides otherwise at least the same level of API.

!Snap as a classic client/server application is made of two components (you already guess what they are). An iterative server application started with the command below extracted from the !Run file:

TaskWindow "RMEnsure SnapServSupport 0.00 RMRun <SnapS$Dir>.SnapS" -name SnapServer -display -wimpslot 64K -quit

The server will bind a listening socket to fixed port 15208.

Once the server handle a connection request from a client, he creates a sprite containing the whole screen of the local Acorn station, then it sends the length of the "sprite" as a word and then the sprite data; he put an end to the connection thereafter.

A WIMP client application handling all user actions for connection establishment and for displaying the transfered sprite in a WIMP window while taking care of such constraints such as the number of colors and the resolution of the client station screen.

This client application communicates with taskwindows tasks it has just started via the WIMP message mechanism (already described in a previous CAUGers article named !JustAHack). The taskwindow tasks just call module code which itself deal with all network contingencies.

A word of advice

!Snap currently has only been fully tested with success on Risc PC architectures. Hélas, the testing performed on pre Risc PC architectures lead to runtime errors in server code (error #55 which means something went wrong with memory management). I am investigating alternate architectures to have a work around for this issue. If a reader could give me some help on this topic, don't hesitate to contact me (my email address is bgilon@free.fr).

User Guide

The first thing to do is to start the server side !Snap application. This is done by double clicking on the !Server icon. A window pops up where the !Server debug message will be printed. You can check that a new task is started by looking at the Task manager's "Tasks" window. You should see a task named SnapServer on the task list. Open a second task window with <Ctrl><F12> and issue the command:
*inetstat -a
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp        0      0  *.snap                 *.*                    LISTEN
The pattern *.snap might be substituted by the *.15208 on your station. Now it's time to run the client application. Double click on the !SnapC icon to start the client application: the SnapClient icon now is visible on the iconbar. To connect to the server, just click on the icon: the dislog box below pops up:

!Snap Connect window

In our context, the server and the client will both be resident on the same station so the default IP address (which binds the loopback interface) will suit our needs. To connect to the server, just click on the Connect action button and a progress window will replace the "new connexion" window on the screen.

!Snap Progress Bar window

When the transfer is completed, the progress window disappears and is replaced by the sprite window below which shows the final result (640 x 480 x 16 greys).

!Snap Result window

How it works

Server Side

The server runs as a separate task which always maintains the physical characteristics of the current mode, therefore it reads the mode number at initialisation:
static size_t mget_mode_variable(const int reason)
{
_kernel_swi_regs r;
_kernel_oserror *p;
int carry;
r.r[0]= -1;
r.r[1]= reason;
p= _kernel_swi_c(OS_ReadModeVariable, &r, &r, &carry);
if(p || carry) exit(EXIT_FAILURE);
return (size_t) r.r[2];
}

#define KSCRSIZE 7
#define XEIGEN 4
#define YEIGEN 5
#define XWNDLIMIT 11
#define YWNDLIMIT 12
#define KGWLCOL 128
#define KGWBROW 129
#define KGWRCOL 130
#define KGWTROW 131
#define NCOLOUR 3

void refresh(const int mode)
{
_kernel_swi_regs r;
_kernel_oserror *p;
const int reasons[] =
        {
        KGWLCOL, KGWBROW, KGWRCOL, KGWTROW,
        -1
        };
gmode= mode;
screensize= mget_mode_variable(KSCRSIZE) + 1024;
xscrlimit= mget_mode_variable(XWNDLIMIT) + 1;
yscrlimit= mget_mode_variable(YWNDLIMIT) + 1;
xeigen= mget_mode_variable(XEIGEN);
yeigen= mget_mode_variable(YEIGEN);
ncolour= mget_mode_variable(NCOLOUR);
r.r[0]= (int) &reasons;
r.r[1]= (int) &vduvars;
p= _kernel_swi(OS_ReadVduVariables, &r, &r);
if(p) mexit(EXIT_FAILURE, p);
reallocated= FALSE;
}

void spr_init(void)
{
int mode= _kernel_osbyte(135, 0, 0);
if(mode >> 8) & 0xff);
atexit(atexit1);
}
Whenever the screen mode changes, the module service call handler is run (triggering for service call ModeChange). Extract from the cmhg module.
service-call-handler: sc_handler 0x46
Extract from the main module
void sc_handler(int service_number, _kernel_swi_regs *r, void *pw)
{
refresh(r->r[2]);
}
As soon as a connexion with a client is established, the program will serve this request up to its end before proceeding on the next connexion.
void mwrite(const int sd, const void *const pbuf, const size_t ilen)
{
const size_t mlenmax= 16384;
size_t len= ilen;
int nwritten;
const char *pb= pbuf;
do      {
        nwritten= send(sd, pb, (len > mlenmax) ? mlenmax : len, 0);
        if(nwritten > 0);
}
...
int main()
{
...
for(;;) {
        connfd= accept(listenfd, (SA *) NULL, NULL);
        if(connfd > 0)
                {
                gconnfd= connfd;
                make_tw_compat(connfd);
                bOK= process_query(&pbuf, &nbytes);
                tnbytes= (size_t) htonl(nbytes);
                mwrite(connfd, &tnbytes, sizeof(size_t));
                if(gconnfd!= -1)
                        {
                        if(bOK) mwrite(connfd, pbuf, nbytes);
                        if(gconnfd!= -1)
                                {
                                close(connfd);
                                gconnfd= -1;
                                }
                        }
                }
        }
return EXIT_SUCCESS;
}

And here is the core stuff of the server (creates a sprite). The reallocated BOOL variable serves as a flag to check whether a memory reallocation is required or not (set to FALSE as the mode changes, reset to TRUE after having called the realloc C library function)

static const char spr_name[]= "screen";
static void *pscreen= NULL;
static BOOL reallocated= FALSE;
static size_t mlen= 0;
static size_t ncolour;
static size_t screensize;
static size_t xscrlimit, yscrlimit;
static size_t xeigen, yeigen;
static int gmode= -1;
static struct
        {
        int gwlcol, gwbrow, gwrcol, gwtrow;
        } vduvars;

static void create_sprite(void)
{
_kernel_swi_regs r;
_kernel_oserror *p;
r.r[0]= 16 | 256;
r.r[1]= (int) pscreen;
r.r[2]= (int) spr_name;
r.r[3]= ncolour > 256 ? 0 : 1;
r.r[4]= vduvars.gwlcol<<xeigen;
r.r[5]= vduvars.gwbrow<<yeigen;
r.r[6]= vduvars.gwrcol<<xeigen;
r.r[7]= vduvars.gwtrow<<yeigen;
p= _kernel_swi(OS_SpriteOp, &r, &r);
if(p) mexit(EXIT_FAILURE, p);
}

BOOL process_query(void **const p2pbuf, size_t *const pnbytes)
{
_kernel_swi_regs r;
_kernel_oserror *p;
void *pstr;
int *pi;
size_t llen;
assert(pnbytes!= NULL);
*pnbytes= 0;
if(!reallocated)
        {
        pstr= realloc(pscreen, llen= screensize);
        if(!pstr) return FALSE;
        pi= (int *) (pscreen= pstr);
        mlen= llen;
        *pi= llen;
        *(pi+1)= 0;
        *(pi+2)= 16;
        r.r[0]= 9 | 256;
        r.r[1]= (int) pscreen;
        p= _kernel_swi(OS_SpriteOp, &r, &r);
        if(p) mexit(EXIT_FAILURE, p);
        reallocated= TRUE;
        }
create_sprite();
*pnbytes= (size_t) *(((int *) pscreen)+3);
*p2pbuf= pscreen;
return TRUE;
}

Client side

The client application used the same mechanism as in a previous application described in CAUGers named !JustAHack. That is: whenever a client application tries to connect to the server: It creates a taskwindow task running the command SnapCGet <key> <serveraddress>
The SnapCGet command is implemented as part of a module, the launched task willl then: When the client application has finished with playing with the sprite (the user closed the window), then a SnapCDone <key> is issued (this time by a C library system call) to free memory ressources. The module c.spriteutil is in charge of all sprite rendering on the client station whatever the current mode and palette settings.

Final notes

Beyond the current issue already mentionned before, there is space for improvement for # to be actually workable: