!Snap

(Application Réseau illustrant l'exploitation du module Taskwindow sous WIMP)
La version courante est 0.2
Auteur: Benoît GILON
Logo de HTMLEdit v3

Retour à la page principale de !Snap

Présentation

!Snap est une suite d'applications qui permet l'affichage dans une fenêtre sur le bureau local d'une autre station accessible par le réseau.

!Snap est le dévelopment de ce que j'ai appris après avoir lu un article de Tom Hughes publié dans un article passé de CAUGers expliquant la facilité d'implémenter des applications réseau au sein de tâches taskwindow. Cette méthode décrite était nouvelle pour moi à ce moment là car elle n'était pas décrite dans le quatrième volume du PRM (décrivant les nouveautés liées à RO 3.6). En résumé, elle montre que les modifications à apporter au code source des applications réseau peuvent être minimales pour que ces dernières puissent tourner sous le régime du multi-tâches coopératif du WIMP. Comme la librairie officielle Acorn ne mentionnait même pas l'option à positionner (dans ses fichiers include), j'ai alors utilisé le librairie NetLib qui fournit autrement le même niveau de fonctionnalité dans son API.

!Snap, comme application client/serveur classique, comprend deux composants (vous avez déjà deviné ce qu'ils étaient). Une application serveur itératif lancé par la commande ci-dessous extraite du fichier !Run:

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

Le serveur va associer une socket en écoute au port local de numéro 15208.

Dès que le serveur traite une demande de connexion de la part d'un client, il crée un sprite contenant l'écran complet de la station Acorn locale, ensuite il envoie la longueur du sprite (sur un mot) puis les données du sprite elles-même; après quoi il ferme la connexion.

Une application cliente WIMP traitant toutes les interactions utilisateur pour l'établissement de la connexion en plus de l'affichage dans une fenêtre Wimp du sprite transféré du serveur tout en tenant compte des contraintes comme la résolution et le nombre de couleurs courant dans le bureau de la station cliente.

Cette application cliente communique avec des tâches taskwindow qu'elle a juste démarrées à travers le mécanisme d'échange de message (déjà décrit dans un article antérieur de CAUGers nommé !JustAHack). Les tâches taskwindow n'hébergent que du code implémenté au sein d'un module RISC OS en charge de la gestion de toutes les E/S réseau.

Avertissement

Aujourd'hui, !Snap n'a été testé avec succès que sur des architectures Risc PC. Hélas, les tests conduits sur des architectures pré-Risc PC ont conduit à l'apparition d'erreurs côté serveur (erreur #55 ce qui signifie que quelque chose s'est mal passé avec la gestion mémoire). Je suis en train d'étudier des architectures alternatives afin de contourner ce problème. Tout aide serait la bienvenue sur ce sujet (mon addresse email est bgilon@free.fr).

Guide de l'utilisateur

La toute première chose à faire est de démarrer l'application Serveur. Ceci est fait en doucle-cliquant sur l'icône !Server. Une fenêtre surgit alors affichant des messages de debug. Vous pouvez vérifier qu'une nouvelle tâche est démarrée en visualisant la fenêtre "Tasks" du Task Manager. Vous devriez voir une tâche nommée SnapServer dans la liste des "Application tasks". Ouvrez alors une fenêtre "taskwindow" avec la combinaison <Ctrl><F12> et tapez la commande:
*inetstat -a
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp        0      0  *.snap                 *.*                    LISTEN
La chaine *.snap peut être remplacée par la chaine *.15208 dans votre environement. Maintenant il est temps de démarrer l'application cliente. Double-cliquez sur l'icône !SnapC pour ce faire: L'icône !SnapC est maintenant visible dans la barre d'icônes. Pour initier une connexion vers le serveur, cliquez sur l'icône: la boîte de dialogue suivante surgit:

!Snap Connect window

Dans notre contexte, le serveur et le client résident sur le même noeud d'où le choix de l'adresse associée à l'interface "loopback ". Pour établir la connexion au serveur, il suffit de cliquer sur le bouton "Connect" et une fenêtre montrant la progression du transfert remplacera la fenêtre de connexion.

!Snap Progress Bar window

Lorsque le transfert est terminé, tla barre de progression disparait et est remplacée par la fenêtre résultat comprenant le sprite (ici, sa dimension est de 640 x 480 x 16 niveaux de gris).

!Snap Result window

Comment ça marche

Côté Serveur

Le serveur tourne en tant que tâche Wimp dont le rôle est de connaitre les caractéristiques du mode graphique en vigueur. À ce titre, il effectue une lecture de "variables" mode et VDU durant son 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);
}
Dès que le mode écran change, le gestionnaire de service call à l'intérieur du module est appelé (sur apparition d'un service call ModeChange).
Extrait du module cmhg:
service-call-handler: sc_handler 0x46
Extraitt du module main:
void sc_handler(int service_number, _kernel_swi_regs *r, void *pw)
{
refresh(r->r[2]);
}
Dès qu'une connexion avec un client est établie, le programme servira cette dernière avant de prendre en compte la requête suivante (d'où la qualité de "serveur itératif").
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;
}

Et maintenant voici le coeur du serveur (création d'un sprite avant son envoi). La variable booléenne reallocated sert de drapeau afin de savoir si une réallocation de la mémoire pour contenir un sprite est nécessaire ou non (positionnée à FALSE lorsque le mode d'écran change, remis à TRUE après avoir appelé la fonction de la librairie C realloc)

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;
}

Partie cliente

L'application cliente utilise le même mécanisme que !JustAHack, une application décrite dans un numéro passsé de CAUGers: Dès que l'application cliente vent établie une connection avec le serveur: Il crée une Taskwindow et lui soumet la commande SnapCGet <key> <serveraddress>
La commande SnapCGet est implémentée au sein d'un module, la tâche lancée va alors: Lorsque l'application cliente a fini d'utiliser le sprite (l'utilisateur a clos la fenêtre), alors une commande SnapCDone <key> est exécutée (cette fois ci en utilisant un appel de la kibrairie C system) afin de libérer les ressources allouées. Le module c.spriteutil est en charge de toute la gestion du dessin du sprite en fonction du mode courant et des capacités de la station cliente.

Notes finales

Au delà du problème de faire fonctionner !Snap sur toutes les architectures, les lignes d'amélioration du logiciel peuvent suivre les chemins suivant: