ToC DocOverview CGDoc RelNotes FAQ Index PermutedIndex
Allegro CL version 11.0

Common Graphics on HTML5/JavaScript (CG/JS)


1.0 About Common Graphics on HTML5/JavaScript (CG/JS)

Common Graphics (CG) has been ported to HTML5/JavaScript. This means that CG applications can now be displayed in web browsers rather than as desktop Microsoft Windows apps. Only the web browser version is supported on Linux and the Mac. A short name for this platform is CG/JS.

CG/JS has several advantages:


2.0 Running the Lisp IDE in CG/JS Mode

The executables allegro and allegro-ansi, when run, start Allegro CL and the IDE and display the IDE in a browser. The browser is whichever web browser is currently registered as the default web browser on that machine. Any browsers that may already be running do not affect that.

On Windows, the Start menu item for running Allegro has separate child items for running the IDE either in a web browser or as a desktop Windows app.

The IDE starts things up in the web browser by calling invoke-private-html-browser to tell the default browser to connect to the already-running CG/JS server in Lisp. invoke-private-html-browser always uses run-shell-command to run the default web browser, though if the default web browser is already running then that will simply add another tab to that browser. Thereafter the browser and Lisp send messages to each other via the Websocket protocol.


2.1 The IDE working in a web browser on a different machine than the Lisp

You may want the IDE to run in a browser that is running on a different machine than the one running Allegro CL. This can be easily done. First, start allegro/allegro-ansi with the arguments -- -l no (that is -[lowercase L]) or, equivalently -- --start-local-client no (the initial -- suppresses a spurious warning but can be left out). Allegro CL will start up, print the header and other messages, and then print the port it is listening on:

% allegro -- -l no
;; ACLSSL: openssl command reports version (1 0)
International Allegro CL Enterprise Edition
11.0 [64-bit Linux (x86-64)]
Copyright (C) 1985-2023, Franz Inc., Lafayette, CA, USA.  All Rights Reserved.

;; Optimization settings: safety 1, space 1, speed 1, debug 2,
;; compilation-speed 1.
;; For a complete description of all compiler switches given the
;; current optimization settings evaluate (explain-compiler-settings).
;;---
;; Current reader case mode: :case-sensitive-lower
port 34767

Now go to the desired browser, open a new tab, and enter

http://<host running Allegro CL>:34767

The IDE will then appear in the new browser tab. (The port number will usually be different every invocation.) This same technique works with any generated CG application.

SECURITY NOTE: there are security implications to the -- -l no/-- --start-local-client no command line arguments: the Lisp server created will listen on interfaces that include connections from hosts other than just localhost. If you are running Allegro CL in an environment where this is not advised, then please do not run the server in this mode. It is recommended that you discuss this with your IT department if you are unsure whether this is a good idea or not.

Microsoft Windows note: Probably nothing will be printed (as shown above) when you run Allegro CL in this way on Windows. And so it is not obvious that anything is happening, except that there is no next prompt because the Lisp is running and waiting for a connection from a web browser. The simple solution for the port not being printed is to also specify a particular port with the -o / --browser-server-port command line option, though that could be a problem if another running program is already using that port. The less convenient (but foolproof) solution would be to also pass the -i / --port-file option that writes the free port that the OS selected into the specified file, where you would then need to read the port to use from that file.


3.0 CG/JS Quick Start

To try out an existing CG project in web browser mode, first run the IDE as usual and open the project. Then go to the CG/JS tab of the Project Manager dialog and turn on Run as Web Browser Server at the upper left. Then Run | Run Project will run the app in a web browser, and generating the standalone app will run it in a web browser by default.

The IDE itself runs in a web browser on the Mac and on Linux. On Windows, it can run as a desktop app or in a browser, where the Start menu has separate commands for running in either mode.


4.0 Overview of CG/JS Features

The officially supported web browsers (which we test) are Chrome, Firefox, Safari, Edge, Opera, Brave, and Vivaldi, though any other Chromium-based browsers are expected to work. Internet Explorer is not supportable because its JavaScript support was not kept up to date, though it is now history.

In Safari only, save-pixmap is not working.

4.2 Any CG App Can Run in Desktop Mode or Web Browser Mode

On Windows, when you develop a CG application as a project in the IDE and then generate a standalone executable for it, that application can be run in either desktop mode or web browser mode. (Desktop mode is often also called Windows mode, and web browser mode is often called CG/JS mode.) You can specify the default mode when you generate the app, and then the user can use a command line argument to run in the other mode if needed.

Allegro's IDE (where you can develop CG applications) is itself a CG application, and it can, on Windows, run in either desktop mode or web browser mode as well, with separate commands on the Start menu.

4.3 The Project Manager Has a Tab for CG/JS Options

The Project Manager dialog in the IDE has a CG/JS tab, where you can specify the default startup mode for an application, along with a variety of options that apply when running it in web browser mode. The options affect running the project in the IDE and running the standalone application that's generated from the project.

4.4 The Web Browser's Full-Screen Mode Can Be Used As Usual

The web browser's own title bar and toolbars reduce the amount of screen space that's left for your CG application. But the browser's usual command for toggling full-screen mode can be used to give your application as much screen space as in desktop mode.

The usual keystrokes for this are Control+Command+F on a Mac or F11 elsewhere, so those keystrokes by default are in a project's Browser Keychords option on the CG/JS tab of the Project Manager dialog to let the browser handle them as it normally does.

Toggling full-screen mode will change the logical screen size in CG, resulting in a call to screen-resolution-changed. An application could add methods to that generic function for side effects, such as resizing windows to fit the new screen size.

4.5 The User Can Zoom the Entire CG App

The user can use the web browser's standard commands for zooming (or scaling) the entire application, without any changes to application code. These commands usually use the keystrokes Control+Plus, Control+Minus, and Control+Zero (or the Command key instead of Control on a Mac) to zoom in or out or return to the standard zoom level. These keystrokes are included by default in a project's Browser Keychords option to let the browser handle them itself, though they could be removed to disable zooming or if the application needs to use these keystrokes for its own commands.

When the user zooms the application, the generic function screen-resolution-changed will be called, and you can add methods that respond to the change. To the CG application it looks like the screen size has changed, and it can be handled in the same way. Typically an application might resize some windows to fit ideally into the new screen size.

If zooming is not working in Firefox, it may be that you need to turn off the "Zoom Text Only" option on the General tab of Firefox options.

It takes a bit of time for the web browser to download the file cgjs.html from the Lisp server machine and then to tell Lisp to create a process for the client and so on. To tell the user that the app really is starting up, a standard "Loading ..." message will appear in the browser tab where the app is about to appear, followed by a "Starting ..." message when the communication has been established and the browser has told Lisp to call the application's startup function. (You could edit the copy of cgjs.html that gets distributed with your application to customize those messages if desired.)

If the app has apparently failed to finish starting up, as judged by a browser-side error or the browser not receiving a reply from Lisp when expected, then the initial "Loading ..." or "Starting ..." message will change to a failure message to make it clear to the user that the app is probably not simply taking a long time to start up. The message will be a general one because the communication between the browser and Lisp is not in place for determining the specific problem, but it may still help us debug the problem if you send us the message that appears. You could copy the message by typing Control+A followed by Control+C, except using Command instead of Control on a Mac.


5.0 Changes You Might Need to Make in Your Application for CG/JS Mode

The goal is for existing CG apps to run in web browsers with no changes, though there are some known exceptions.

5.1 The Variables for Special Icons Are Now Functions

The five icon variables error-icon, warning-icon, question-icon, information-icon, and application-icon are now functions (which take no arguments). The variables still work in desktop mode, but code that may sometimes be run in CG/JS mode needs to use the new functions instead. So each occurrence of error-icon in your code would need to become (error-icon) to call the error-icon function. These values are often passed to pop-up-message-dialog, for example.

5.2 Additional CG Processes Need to Be Started in a Certain Way

If your application creates additional processes that call CG functions, then they need to be started either by using in-cg-process or by adding a binding for *system* to the usual *default-cg-bindings* to pass as the :initial-bindings argument to process-run-function. It's OK to do this for both desktop mode and web browser mode. Here's the needed value of the :initial-bindings argument:

(mp:process-run-function
  (list :name "Second CG Process"
        :initial-bindings
        (acons '*system* *system* *default-cg-bindings*))
  'second-process-startup-function)

5.3 You May Need to Call cg-process-wait Rather than process-wait

If you have calls to process-wait where the wait test depends on events getting handled, then you will need to convert those to calls to cg-process-wait. The reason is that events can be handled during a process-wait only on the Windows platform.

5.4 You May Need to Filter Events If Dragging Operations Are Sluggish

Due to the communication between Lisp and the web browser, certain dragging operations may be more sluggish in web browser mode. To alleviate that, you can call the setf functions of mouse-move-filter, scroll-filter, and wheel-filter to tell the browser to send an event of that type only whenever a specified number of milliseconds have passed. The browser will still send the final event of a series even when less time passed before that one. For example:

(setf (mouse-move-filter my-window) 100) ;; ten times a second

5.5 You May Want to Use an Alternate Drawing Mode for Efficiency

A JavaScript window that can be drawn on arbitrarily always has backing store that holds what has been drawn on the entire scrolling canvas. CG still draws only what has been scrolled into view by default, but if it is more efficient for a particular kind of window to draw everything initially rather than if and when it gets scrolled into view, then you can define a draws-the-entire-scrolling-page method that returns true to tell CG that your redisplay-window method does that. (This is more in line with the JavaScript approach.)

5.6 Always Do Exit Cleanup in User-Close Methods

It's already highly recommended that you do any cleanup that's needed at exit time in a user-close method for a top-level window, because if you do it only in a File | Exit command (for example), then the user could bypass that by closing the top-level window directly. Any interactive gesture to close a top-level window will call user-close on that window, and so a user-close method can ensure that your cleanup code is run.

Bypassing a File | Exit command may be even more common in CG/JS, because if the user closes your app with its official exit command, then they would be left with a blank browser tab that they still need to close. So most users will likely close the browser tab directly, meaning that your cleanup code really needs to be in a user-close method (which will still get called).

5.7 Avoid Prompting the User in a User-Close Method

There's no way to prevent a user from exiting your application by closing the browser tab (or visiting some other web page), without first closing your main application window or invoking your official exit command. (confirm-web-browser-exit can be used to tell the browser to ask the user whether to really exit, but the user may proceed to exit that way anyway.)

So your user-close method should avoid showing a modal dialog or other prompt if calling web-browser-has-disconnected returns true (meaning that the app was running in a web browser but the connection is now gone). If the user has closed the browser tab, then there is no longer an environment in which to show a modal dialog that CG would wait on, and the app could hang while waiting for the browser to reply.

5.8 Dragging Loops Might Need to Call process-pending-events

Code that has run only on Windows and which has busy loops (such as for dragging operations) that exit based on calls to functions like key-is-down-p and mouse-button-state may now need to call process-pending-events inside the loop in order for the loop to exit when running in CG/JS mode. Otherwise events would not get handled during the busy loop as needed for the exit test to pass.

5.9 Setting the Default Web Browser for CG/JS in Windows 11

An obscure but possibly confusing thing in Windows 11 is that a CG/JS app will invoke the web browser that's associated with the HTTP link type (or URL scheme) rather than the one that's associated with the .html file type. So it's not sufficient to use the Settings app in Windows 11 and for example enter ".html" in the box at the top of the "Default apps" page and then select the desired web browser. The best way appears to be to go to the child page for the desired browser and press the button at the upper right to make it be the default browser, and then verify that it has set that browser not only for the .html and .htm file types that are listed below, but also for the HTTP and HTTPS link types that are listed farther below.


6.0 Functions that Are Specific to CG/JS

Here is a list of links to all of the CG functions, variables, and classes that are specific to CG/JS mode. You can find this same list in the Allegro Tree of Knowledge dialog by using Search | Find Forward to search for "CG/JS".


7.0 Command Line Options When Running a CG App in a Web Browser

Here are the command line arguments that can be used to specify whether to run a generated application in desktop or web browser mode, and when running in web browser mode whether to use various CG/JS options. These options apply to a CG app that was generated from a project in the IDE, and to the IDE itself. Using these options requires starting the app with a command line in a terminal or with a script, rather then running it interactively (such as from Finder on the Mac or the Start menu on Windows). The default values for most of these options can be specified on the CG/JS tab of the Project Manager dialog in the IDE, to avoid using a command line when running with the usual options. Command line arguments always override the defaults that are built into the app.

In each case, you can type either the short version of the argument name that starts with a single dash, or the long version that starts with two dashes, followed by a valid value as described.

To avoid printed warnings about unknown command line arguments, any CG and IDE arguments should be preceded by a single double-dash (to tell the base Lisp that these are arbitrary "application arguments"), as in this example:

allegro -- -l no -o 12345

An application could be written to allow its users to specify some of these startup options for subsequent runs of the app, and then write them to a file named foo-startup-options.txt in the user's home directory (or the personal Documents directory on Windows), where foo is the name of the application's executable file. The content of the file should be the actual string for the options as they would appear in a command line. So the file might contain only -b yes if the user requested that the application start up in a web browser next time. The form (utilities-directory :hidden t) returns the directory where the file should be placed. See utilities-directory.


7.1 --run-as-web-browser-server

Short version: -b

Value: yes or no

yes means to run the app (or the IDE) in a web browser and no means to run it as a desktop Windows app. See the Run as Web Browser Server option in the CG/JS tab of the Project Manager dialog.

This command line argument will be ignored if the Disallow Running In Non-Default Mode option in the CG/JS tab of the Project Manager dialog was enabled when generating the standalone application.

The rest of these options are used only when running in a web browser.


7.2 --start-local-client

Short version -l

Value: yes or no

yes means to automatically tell the default web browser on the same machine to connect to the CG server and display the application. no means to wait for someone to explicitly connect to the server from a web browser that could be on another machine. (The argument generates a spurious warning which can be suppressed by adding -- (dash dash space) before the argument.) See the Start Local Client option in the CG/JS tab of the Project Manager dialog.

When running on Linux or FreeBSD, you can use a web browser that's not the default one by setting the environment variable ACL_CG_BROWSER. The value can be either a full pathname like /usr/bin/firefox or the simple command name like firefox. If a simple name is used, then it needs to be found in your shell's PATH. If the specified browser is not found, then it will be ignored and the default browser will be used as usual.

If you run the application in a terminal and specify this command line argument as no, then it might appear that things are hung because the terminal where you ran the app does not get a fresh prompt (unless you add an ampersand to the end of the command line to run the app in the background). But actually the app is running and waiting for someone to connect from a web browser. When the user closes the browser tab, the app will exit and the terminal will get a fresh prompt.


7.3 --client-timeout

Short version -k

Value: a non-negative integer

If this is a positive integer and --start-local-client is yes, but no client connects within that many seconds, then the server will exit. If this option is not specified or its value is zero, then a default timeout of 90 seconds will still be used if the --start-local-client option is yes. This ensures that the server application will not be left running invisibly if no web browser successfully connects to the server when that was requested.

There is no corresponding project option for this command line argument.


7.4 --exit-server-on-client-exit

Short version -x

Value: yes or no

Whether the Lisp will exit automatically if a user closes the browser tab where they are using the application and there are no remaining clients connected to the server. See the Exit Server on Client Exit option in the CG/JS tab of the Project Manager dialog


7.5 --launcher

Short version: none

Value: yes, no, or only

Whether this Lisp application will listen for launch HTTP requests that a web page can send to request a separate instance of the application for use by that web page only, where typically it will embed the application in an HTML iframe or a new browser tab. This built-in feature allows web pages to remotely launch a CG/JS application without using a separate application server. This is useful when the application is not written in such a way that a single instance of the application can handle multiple clients simultaneously, and so a separate running executable must be used for each client.

If this command line option is passed as yes or only, then the Max Clients option in the CG/JS tab of the Project Manager dialog is reinterpreted to mean the maximum number of instances of the application executable that can be running at the same time. And a single instance will never serve multiple clients.

If the value is only, then this launcher instance will act only as a launcher and never serve a client itself, instead always launching a new instance (up to the Max Clients limit). When the value is yes, the launcher instance itself will be used for a requesting client whenever it is not currently serving a client. Using the launcher instance will start up the application more quickly because it doesn't need to run a new Lisp executable, though then the launcher instance perhaps might not respond as quickly to another request for an instance if the application is busy at the moment.

In the normal case, the response that is returned to the web page for its launch request will be the port at which the web page can use its private instance of the application, on the same machine as the launcher instance. Typically a launcher instance is running at a known port, and a web page will first send a launch request to that port and receive the port for its own instance, and then tell an iframe or new browser tab to vist the machine at that port to display the application. The launch HTTP command looks simply like host:launcherPort/launch, or like host:launcherPort/launch?password=secret if the --remote-password option was also used when running the launcher instance.

If an application implements its own command line arguments, then a web page could pass them as the value of a command-line-args argument to the launch HTTP command.

If the launcher refuses to serve the launch request (typically because the Max Clients are currently running, then the response to the launch request will be zero. If a --remote-password was also specified when running the launcher, but it was not passed correctly in the launch request, then the response is -1.

When an instance was launched by another one, then it will accept an exit HTTP request to exit that Lisp. (A launcher instance will also accept an exit request, but it will do nothing because that could exit a different client if the application was exited without closing the browser tab and then another client is now running in the launcher instance.) A web page might want to send this request before it sends another launch request to restart the CG application, so that it works even when the maximum number of instances are currently running. If a --remote-password was specified when running the launcher instance, then it must be passed with this request to any launcher or launched instance.

See An Example Web Page for Embedding a CG/JS Application for a complete JavaScript example of embedding a CG app in a web page.

There is no corresponding project option for this command line option.


7.6 --remote-password

Short version: none

Value: a string

When specified, this is a password that a web page must include in any launch request to remotely launch its own instance of the application, and in any exit request. See the --launcher option.

An alternative is to set the CGJS_REMOTE_PASSWORD environment variable. That may be more secure because then the password will not be shown when listing processes with the Unix ps command (though that would show it only on the server machine).

There is no corresponding project option for this command line option.


7.7 --file-to-publish

Short version: none

Value: a file path

This option can be used to specify an HTML file to publish as a web page that can visited on the machine where the CG/JS application is running. This could be used along with the --launcher option (for example) to publish a demo JS/HTML file that a user could visit from their web browser, where that demo file could have a button that sends a launch command to this application to embed its own instance of the application in an iframe.

The path of the published URL will be the name of the file, without its file type. So if the file is specified as /home/me/cool-demo.html (for example), then the path of the URL to visit will be cool-demo, and a user could visit the page by directing their web browser to host:port/cool-demo (substituting the needed values for host and port).

One warning: If you exit the CG app that has published a file, then modify the file-to-publish, and finally start the CG app again (specifying the same file-to-publish), then visiting the URL of the published page again in your browser will likely display the previous version of the file-to-publish, even if you do this in a new browser tab. The previous behavior of the file will still be in place as well. This has confused the developer to no end. 8-) You will then need to use your browser's refresh command to force it to load the modified file so that you can test your most recent changes.

There is no corresponding project option for this command line option.


7.8 --limit-connections-to-same-machine

Short version: none

Value: yes or no

Whether a web browser will be able to connect to the CG application server only if it's running on the same machine as the server, typically for security reasons. See the Limit Connections to Same Machine option in the CG/JS tab of the Project Manager dialog.


7.9 --show-cgjs-server-window

Short version -s

Value: yes or no

Whether an additional browser tab will appear on the server machine for the CG application server, where you can exit the server by closing that tab or the window that's inside it. This may be useful in server mode by an administrator. See the Show CG/JS Server Window option in the CG/JS tab of the Project Manager dialog.


7.10 --cgjs-logging

Short version -g

Value: yes, no, or a file path

If yes, then the server window (if present) will have a child window that displays logging information, such as when a user connects or disconnects or uses a menu command. The --show-cgjs-server-window option must also be yes for this to take place.

If a file path, then logging information will be incrementally appended to that file. If a relative path was specified, then it will be relative to the installation directory of the Lisp that's running. If the --launcher option was used, then any instances that are launched by this one will also append information to the same file.

See the Show CG/JS Logging Window option in the CG/JS tab of the Project Manager dialog.


7.11 --browser-server-port

Short version -o

Value: a non-negative integer

The port at which a web browser can connect to the CG application server. See the Browser Server Port option in the CG/JS tab of the Project Manager dialog.

Not specifying this option or passing it as zero will let the operating system select a port that it knows to be free.

The following example (when substituting the actual executable name for [appname]) would run the app in web browser mode, but not start a local client automatically, and use port 9009. A user could then run the app in a web browser on the same machine by telling the browser to visit localhost:9009, or on a different machine by telling the browser to visit [machine-name]:9009.

[appname] -- -b yes -l no -o 9009 

7.12 --max-clients

Short version -m

Value: a positive integer

The maximum number of clients that can connect to the running CG server at any one time. See the Max Clients option in the CG/JS tab of the Project Manager dialog.


7.13 --confirm-exit

Short version: none

Value: yes or no

Establishes whether the web browser will prompt the user for exit confirmation when they try to close the browser tab or reload the page. See the Confirm Exit option in the CG/JS tab of the Project Manager dialog.


7.14 --port-file

Short version -i

Value: a file path

A file path to which CG will write the free port that the OS selected for use as the *browser-server-port*. This file could be read to find the port to which a web browser needs to connect to display a CG app that's running in server mode. This is safer than specifying a specific --browser-server-port that might be in use by another application. There is no corresponding project option for this command line argument.

If specified, this should be either a full pathname such as /home/me/my-app-port.txt or c:\foo\free-port-for-my-app.txt, or else just a file name and type such as my-app-port.txt to indicate the path ~/.my-app.d/browser-port.txt on Linux and Mac (if my-app.d was specified as the utility-file-directory of the project) or a path like c:\Users\me\Documents\browser-port.txt on Windows. The browser server port number that was used will then be printed into the file with no other text.

This could be useful on Windows when an unknown free port was selected by the OS but it could not be written to the terminal where the app was run. An administrator who ran the app could then find the port in the file and pass it on to users, for example. It might also be useful in a custom script to find the port that was used, where the script should first delete the file if it exists, then wait until the file exists again, and finally read the port from the file.


7.15 --secure

Short version -a

Value: do not specify an argument

When specified, the app will run in a secure mode to protect the machine that the app is running on from possible nefarious or accidental actions that could remove files, for example. This is probably useful mainly when running the app in client/server mode, where an administrator runs the app on a server machine and then a user connects to it from a browser on a different client machine. There is no corresponding project option for this one.

Specifically, the file selection dialog is never shown because it would allow the user to browse the whole file system on the server machine, and write files most anywhere. All files will be read from and written to a single folder that is returned by utilities-directory, which defaults to *utilities-directory* if the value of that variable is not nil, or else to what home-directory returns. When the app calls ask-user-for-existing-pathname, a simple dialog will list the names of the files of the appropriate type that are in this directory, and when ask-user-for-new-pathname is called, the app will just ask for a file name to be typed in. See utilities-directory.

The value of the CG variable *secure-mode* will be true if and only if this command line argument was used.


7.16 --utilities-directory

Short version -y

Value: a file path

Sets the variable *utilities-directory* to the specified file path (a string), which causes utilities-directory to return it.


8.0 An Example Web Page for Embedding a CG/JS Application

Here is a complete HTML file that shows how to embed a CG/JS application in a web page, if that application is running with the --launcher option.

<!-- -*- mode: js; js-indent-level: 2; indent-tabs-mode: nil; -*- -->
<!DOCTYPE html><html lang="en"><head>
<title>Remotely Launching an Embedded Common Graphics Application</title>
<meta charset="utf-8"/></head>
<script>

// This file demonstrates launching a private copy of a remote Common
// Graphics (CG) application, and embedding that application in a web
// page.  In this example, a button widget launches the app and
// displays it in an HTML iframe element, and the JavaScript code here
// can be adapted for your own web site.

// A CG feature allows a single "launcher" instance of an app to be
// running as a server and listening for requests from web browsers.
// It will launch a separate instance of the app for each web page
// that requests one, up to a specified limit.  (It can optionally use
// the launcher instance itself for one client, to minimize the number
// of executables that are running.)

// The launcher instance of the CG app should already be running at a
// known machine and port.  The command line for running the launcher
// instance would look something like this (where the launcher will
// listen on port 9009):

// my-cg-app.exe -- -b yes -l no -x no -o 9009 --launcher yes

// -b yes           run in web browser mode rather than desktop mode
// -l no            do not start a local client in a web browser
// -x no            do not exit the server when a client exits
// -o 9009          listen for new clients on port 9009
// --launcher yes   launch another instance when remotely requested

// Something like a button widget in a web page can then run a script
// that sends an HTTP request to that machine at port 9009 to request
// an instance of the CG app.  The reply to that request will be a
// port on the same machine for a private instance of the app that was
// started up for that client.  The script can then tell an iframe or
// a new browser tab to visit the machine at that port to display its
// instance of the CG app.

// Some of the finer points below are not really necessary, such as
// making it work to directly restart the CG app when the maximum
// number of clients are currently running, but those things are
// included in case you want to use them.

// Set these variables as needed for your web page or application.

// The machine where the CG app is running as a server.
let host = "localhost";
// The port where the CG app is running as a server.
let launcherPort = 9009;
// A password can be required with a command line argument
// like "--remote-password secret".  Otherwise this can be null.
let password = "secret";
// The ID of the iframe in which to embed the CG app.
let cgFrameName = "cgFrame";
// Use null instead to show the CG app in a new browser tab.
// let cgFrameName = null;

// These will get set during the run.
let ourPort, cgFrame, messageWidget;

// The onload function that's called when this file gets loaded.
function exampleOnLoad () {
  // Cache some global variables.
  // The HTML element where this example displays status messages.
  messageWidget = document.getElementById("messageWidget");
  // The iframe (if any) where we will display the CG app.
  if (cgFrameName) {
    cgFrame = document.getElementById(cgFrameName);
    // When reloading the web page, this avoids automatically
    // reloading the iframe as well, which would leave ourPort
    // undefined (for example), and restarting the CG app at that time
    // is probably not desirable anyway.  After a reload, the user can
    // run the CG app again if desired.
    cgFrame.src = "";
  }
  // This would prompt the user when they try to close the web browser
  // tab or reload the web page, asking if they really want to exit.
  // That can be useful to avoid losing unsaved changes in the CG app.
  // window.onbeforeunload = function (event) {
  //   event.preventDefault();   // the official way
  //   return "Really exit?";    // the traditonal way
  // }
}
// Code for the "Run the App" button.  This function sends an HTTP
// request to the launcher application, asking for a personal instance
// of the application executable (or to use the launcher instance
// itself if available).
function runTheApp () {
  // If the app is already running, then first tell it to exit and
  // wait for that to complete, and then run the app again.  This is a
  // fine point that makes it work even when the maximum number of
  // clients are currently running.  If that's not important, then
  // just call runTheAppNow directly.
  if (ourPort) exitTheApp(true); // true means to run after exiting
  else runTheAppNow();
}
function runTheAppNow (restarting) {
  let args = {};
  if (password) args.password = password;
  if (restarting) args.restarting = true;
  statusMessage("Launching a copy of the CG/JS app at "
                + host + ":" + launcherPort + " ...");
  // Ask the launcher that's always running at a known port to launch
  // a copy of itself for us to use at another port. When the reply is
  // received asynchronously, displayTheApp will be called.  This
  // general sendHttpRequest utility function is down below.
  sendHttpRequest (host, launcherPort, "launch", args, displayTheApp);
}
// This function is called when a reply is received for the launch
// request above that requests an instance of the application.  The
// reply contains the port for our private application instance.
function displayTheApp (status, response) {
  // Handle various failure cases for the launch request, and
  // otherwise display the application.
  if (status === 0)
    statusMessage("There apparently is no CG application running at "
                  + host + ":" + launcherPort + ".");
  else if (status === 200) {
    if (response === "0") // CG's code for refusing a client
      statusMessage("The CG server would not launch an instance, "
                    + "probably because the maximum number of "
                    + "clients are now running.");
    else if (response === "-1") // CG's code for a bad password
      statusMessage("Bad password.");
    else {
      // In the normal case, tell the iframe to connect to the server
      // machine at our personal port, to display the app.
      ourPort = response; // cache our private port
      let url = "http://" + host + ":" + ourPort;
      if (cgFrame) cgFrame.src = url;
      // If no cgFrame was specified, then use a new browser tab.
      else open(url, "_blank");
      statusMessage("Success!  Our copy of the application "
                    + "is starting up at port " + ourPort + ".");
    }
  }
  // Possible other failures.
  else statusMessage("Launching the application failed with status "
                     + status + ".");
}
// Code for the the "Exit the App" button.  This function sends an
// HTTP message to this client's own instance of the application, to
// exit that executable (unless it's the launcher).  This is not
// really necessary because the executable will reliably exit when the
// browser tab is closed or the web page is reloaded, or the
// application is exited from within.  But calling this function
// before launching the application again avoids a problem where it
// doesn't work when the maximum number of clients are currently
// running.  And when the launcher instance is being used, doing this
// before relaunching will allow the launcher instance to be used
// once again, which is faster.
function exitTheApp (runAfterExiting) {
  if (ourPort) {
    statusMessage("Exiting the CG/JS app at "
                  + host + ":" + ourPort + "...");
    let arguments = password && { password: password };
    sendHttpRequest (host, ourPort, "exit", arguments,
                     afterExiting, runAfterExiting);
  }
  else statusMessage("There is no running application to exit.")
}
// This is called when the reply is received to the exit request
// above.  It can then restart the app when that was requested.
function afterExiting (status, response, runAfterExiting) {
  if (status === 200) {
    statusMessage("Success!  The application running at port "
                  + ourPort + " has exited.");
    // When the "Run the App" button first exits the app,
    // run the app now that the previous run has exited.
    if (runAfterExiting) runTheAppNow(true);
  }
  else statusMessage("Exiting the application failed with status "
                     + status + ".");
  ourPort = null;
}
// Sending any HTTP message and handling the reply.  This is a general
// utility function that you could use without modification.  It sends
// an HTTP request to the CG app and sets up a function to be called
// when a reply is received.  command is a command name like "launch"
// that the app published.  arguments is a JavaScript object to send
// with the request as URL query arguments.  replyFunction will get
// called when the reply is received, with the status and response
// of the reply as the first two arguments and replyFunctionArguments
// as a third argument
function sendHttpRequest (host, port, command, arguments,
                          replyFunction, replyFunctionArguments) {
  let request = new window.XMLHttpRequest();
  let url = "http://" + host + ":" + port + "/" + command;
  // Translate the "arguments" object into a URL query string.  For
  // example, { password: "secret" } would turn into ?password=secret
  // (or &password=secret for later arguments) in the URL.  Each
  // argument value will be uri-encoded (percent-encoded) to escape
  // URL syntax characers.
  if (arguments) {
    let first = true;
    for (let key in arguments) {
      url = url + (first ? "?" : "&") + key + "=" +
        encodeURIComponent(arguments[key]);
      first = false;
    }
  }
  // When the reply to the request is received asynchronously, call
  // the replyFunction on the status and the response that were
  // received, plus the arbitrary replyFunctionArguments.
  request.onreadystatechange = function () {
    if (request.readyState === XMLHttpRequest.DONE)
      (replyFunction (request.status, request.response,
                      replyFunctionArguments));
  }
  // Send the request and return immediately.
  request.open("GET", url);
  request.send();
}
function statusMessage (string) {
  messageWidget.innerText = string;
}
</script>
<body onload="exampleOnLoad()"
      style="background: #FFFCF4;
             margin-left: 1%; margin-right: 2%; margin-top: 20px;
             font-family: sans-serif; font-size: 14pt">

<h2>Remotely Launching an Embedded Common Graphics Application</h2>

<p>If you are running a CG application as a server as shown below
in web browser mode (-b yes),
without starting a local client in a web browser (-l no),
without exiting the server when a client exits (-x no),
connecting to the CG server at port 9009 (-o 9009),
and acting as a launcher to run an instance of the app for each
client (up to a limit) that requests one (--launcher yes),
then pressing the Run the App button will tell the (invisible)
iframe element to display the app in this web page.</p>

<pre><b>my-cg-app.exe -- -b yes -l no -x no -o 9009 --launcher yes</b></pre>

Using -x no allows the server instance to keep running while various
users connect to it and disconnect, though it also means that you
will need to use some operating system facility to kill the server
eventually.
</p>
<button type="button" id="runTheApp" onclick="runTheApp()"
        style="font-family: sans-serif; font-size: 14pt">
Run the App</button>
<button type="button" id="exitTheApp" onclick="exitTheApp()"
        style="font-family: sans-serif; font-size: 14pt">
Exit the App</button>
<br><br>
<div id="messageWidget">
Status messages will appear here.</div><br>
<iframe id="cgFrame"
        style="height: 700px; width: 90%; border: none">
</iframe>
</body></html>

Copyright (c) Franz Inc. Lafayette, CA., USA. All rights reserved.

ToC DocOverview CGDoc RelNotes FAQ Index PermutedIndex
Allegro CL version 11.0