Skip to content

TMS WEB Miletus

Miletus enables developers to create desktop applications with TMS WEB Core. Similaly to Electron it provides access to the local file system, shell dialogs, clipboard and much more.

Your first TMS Web Miletus Application

To create a new Miletus application, select the "TMS Web Miletus Application" from the wizard:

It generates a project similar to a TMS Web Application, with extra icon files and build configurations. For each supported platform there is a Debug-Platform and Build-Platform configuration. The difference between Debug and Build is the availability of the debugging tools. In Build mode the debugging tools are disabled.

The icon file can be changed through the project options:

You can now develop your application like you would normally do with a TMS Web Application.

Debugging and accessing the Developer Tools

Windows

Debugging on Windows is identical to a TMS Web Application, it can be done through the Developer Tools. When the application is deployed in Debug mode Miletus adds a default menubar to the application if a TMiletusMainMenu has not been added to the main form. The Developer Tools can be accessed with View > Toggle developer tools or via the F12 shortcut. To force any window to have the Developer Tools opened after the given window is shown, use the following code in the form's OnCreate event:

procedure TForm1.MiletusFormCreate(Sender: TObject);
begin
 OpenDevTools;
end;

Linux

To be able to debug on Linux, select the Debug-Linux64 build configuration, build your application and copy the resulting application to your target machine.

After these steps, you can debug your application in a similar way as on Windows: Either through the View > Toggle developer tools menu item or by calling the OpenDevTools method.

macOS

To be able to debug on MacOS on the target machine, open up a Safari instance and if you haven't already, enable the Develop menu item: https://support.apple.com/guide/safari/use-thedeveloper-tools-in-the-develop-menu-sfri20948

Select the Debug-MacOS64 build configuration, build your application and copy the resulting application to your target machine.

Sign your application along with the provided .entitlements file, otherwise the necessary key for debugging won't be picked up by the binary.

After that you'll be able to debug your running Miletus application by selecting Develop > Your machine's name > main.html from your running Safari instance.

Deployment

Set the configuration to the correct Build-Platform target and Build the application. After that copy the resulting application to the target machine if that differs from the development machine. It's always recommended to sign the application afterwards.

macOS

After deployment if at application launch the message "“YourApp” cannot be opened because the developer cannot be verified." is displayed, try Right click > Open which gives the option to open the application despite the lack of application signatures.

Depending on how the application is copied to the target machine the necessary read, write and execute permissions might be removed. If the error message "You do not have permission to open the application" is shown try setting the correct permissions:

sudo chmod -R /path/to/YourApp.app

Starting from Big Sur, on macOS ARM targets it is a requirement to sign the application. Miletus applications come unsigned on all platforms, so on a macOS ARM target they always need to be signed first:

codesign --force --deep --entitlements YourApp.entitlements --sign -
YourApp.app

If the code signing fails with the message "resource fork, Finder information, or similar detritus not allowed", remove the extended attributes by running the following command and sign the application afterwards:

xattr -cr ./path/to/YourApp.app

Before distribution sign your application with your Developer ID certificate.

Linux or Raspberry Pi with Raspberry Pi OS

Depending on how the application is copied to the target machine the necessary read, write and execute permissions might be removed. If the application cannot be run due to missing permissions try setting them:

sudo chmod -R 755 /path/to/YourApp
On Linux or Raspberry Pi, Miletus is using GTK3 and the WebKitGTK browser engine. If your Linux system or Raspberry Pi does not have WebKitGTK installed, run the following command:
sudo apt install libwebkit2gtk-4.0-dev

Custom extensibility

It's possible to extend a Miletus application with custom native functionality through shared libraries.

Loading and unloading a library

The LoadLibrary(ALibraryPath) and UnloadLibrary(ALibraryPath) methods can be used to load and unload a library. LoadLibrary is a TJSPromise, and its return value can determine if the library could be loaded:

//Mark as async
[asnyc]
procedure WebButton1Click;

//Implementation
procedure TForm1.WebButton1Click(Sender: TObject);
var
  b: Boolean;
const
  LIBPATH = 'path\to\MyLibrary.dll';
begin
  b := Await(Boolean, LoadLibrary(LIBPATH));
  if b then
  begin
    //The library could be loaded, call ProcNoParam procedure
    Await(JSValue, ExecProc(LIBPATH, 'ProcNoParam'));

    //And finally, unload the library
    UnloadLibrary(LIBPATH);
  end;
end;

Example of Miletus compatible library from Delphi

unit UMyLibrary;

interface

uses
 Classes;

procedure ProcNoParam; cdecl;
procedure ProcParam(AData: PChar); cdecl;
function FuncNoParam: PChar; cdecl;
function FuncParam(AData: PChar): PChar; cdecl;

exports
  ProcNoParam,
  ProcParam,
  FuncNoParam,
  FuncParam;

implementation

procedure ProcNoParam; cdecl;
begin
  //
end;

procedure ProcParam(AData: PChar); cdecl;
begin
  //
end;

function FuncNoParam: PChar; cdecl;
begin
  Result := '';
end;

function FuncParam(AData: PChar): PChar; cdecl;
begin
  Result := '';
end;

end.

Note: For Raspberry the shared library needs to be created from Lazarus.

Sending custom messages to a Miletus application

It's possible to send custom messages by implementing a RegisterCallback procedure in the library.

type
 TCallback = procedure(AMessageID: Integer; AData: PChar); cdecl;

var
 MyCallback: TCallBack;

const
 MYID = 123;

procedure RegisterCallback(AFunction: Pointer); cdecl;
begin
  @MyCallback := AFunction;

  //For Lazarus:
  //MyCallback := TCallback(AFunction);
end;

procedure MyProcedure; cdecl;
begin
  //Do something and call MyCallback
  MyCallback(MYID, '{"Name": "My data", "Value": "This is my JSON formatted
data"}');
end;

To capture these messages, in the Miletus application use the MiletusCommunication.OnCustomMessage event:

const
  MYID = 123;

procedure TForm1.MiletusFormCreate(Sender: TObject);
begin
  MiletusCommunication.OnCustomMessage := CustomTextMessage;
end;

procedure TForm1.CustomTextMessage(AMessageID: Integer; AMessage: string);
begin
  if AMessageID = MYID then
  begin
    //Do something with AMessageText
    //e.g. Create JSON object
  end;
end;

Drag and drop

Miletus provides support for drag and drop functionality. There's a difference between dragging into and dragging out of an application. In both cases the dragging needs be detected by an event.

From desktop to Miletus

Dragging something into the application is a feature that is supported by HTML5.

procedure TForm1.WebMemo1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  f: TJSHTMLFile;
begin
  f := TJSDragEvent(TDragSourceObject(Source).Event).dataTransfer.files[0];
  //process the TJSHMTLFile futher...
end;

From Miletus to desktop

Dragging something out of an Miletus application is supported, but the file must already exist on the local file system. If the file does not exist, it is up to the developer to create it on the fly based on the contents from the application. If the file is present, then only the following code needs to be called with the path to the existing file:

procedure TForm1.WebMemo1StartDrag(Sender: TObject;
  var DragObject: TDragObject);
begin
  StartFileDrag('path\to\file');
end;

Helper methods

The following helper methods are available.

Keep in mind

The synchronous methods below only work on Windows. For other platforms or a cross-platform application it's better to use the async promise-based versions.

Property Description
GetCursorPos: TPoint Returns the cursor position.
GetCursorPosP: TJSPromise Promise based equivalent of GetCursorPos for cross-platform support. The return value of the TJSPromise is a TPoint that contains the on-screen mouse position.
GetMiletusPath(APathType: Integer; var APath: string) Retrieves the requested path. Accepted APathType values are: NP_APPDATA, NP_APPPATH, NP_DESKTOP, NP_DOCUMENTS, NP_DOWNLOADS, NP_EXE, NP_HOME, NP_MUSIC, NP_USERDATA, NP_PICTURES, NP_TEMP and NP_VIDEOS
GetMiletusPathP(APathType: Integer) Promise based equivalent of GetMiletusPath for cross-platform support. The return value of the TJSPromise is a string that contains the path.
GetMiletusFilesP(ADirectory: string; AFilter: string) Promise based function to return the files in a given directory. The AFilter parameter is optional.
GetMiletusDirectoriesP(ADirectory: string; AFilter: string) Promise based function to return the subdirectories in a given directory. The AFilter parameter is optional.
GetOSVersionP: TJSPromise Promise based function to return the OS information. The return value of the TJSPromise is a TMiletusOSVersion record, which contains the Platform, Architecture, Name, Build, Major and Minor properties of the OS. TMiletusOSVersion.ToString contains the OS version as a formatted string.
MiletusTerminate Method to terminate the application from any Miletus window.
OpenDevTools Method to open the developer tools. Only works on Windows and Linux. For macOS please refer to the Debugging and accessing the Developer Tools part of the documentation.
StartFileDrag(APath: string) Method to start the file dragging at the given APath.
LoadLibrary(ALibraryPath: string): TJSPromise Dynamically loads a library by AName. This TJSPromise returns with a Boolean value which indicates if the loading was successful.
UnloadLibrary(ALibraryPath: string) Dynamically unloads a library by AName.
ExecProc(ALibraryPath: string; AProc: string): TJSPromise Call AProc procedure from ALibraryPath loaded library.
ExecProc(ALibraryPath: string; AProc: string; AData: string): TJSPromise Overload of ExecProc(ALibraryPath, AProc) with AData parameter which can be used to send some data over to the library.
ExecFunc(ALibraryPath: string; AFunc: string): TJSPromise Call AFunc function from ALibraryPath loaded library. This TJSPromise returns with a string value.
ExecFunc(ALibraryPath: string; AFunc: string; AData: string): TJSPromise Overload of ExecFunc(ALibraryPath, AFunc) with AData parameter which can be used to send some data over to the library. This TJSPromise returns with a string value.