Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change emscripten browser input method from window.prompt to something more sensible?

I have a C++ function which once called consumes input from stdin. Exporting this function to javascript using emscripten causes calls to window.prompt.

Interacting with browser prompt is really tedious task. First of all you can paste only one line at time. Secondly the only way to indicate EOF is by pressing 'cancel'. Last but not least the only way (in case of my function) to make it stop asking user for input by window.prompt is by checking the checkbox preventing more prompts to pop up.

For me the best input method would be reading some blob. I know I can hack library.js but I see some problems:

  1. Reading blob is asynchronous.
  2. To read a blob, first you have to open a file user has to select first.
  3. I don't really know how to prevent my function from reading this blob forever - there is no checkbox like with window.prompt and I'm not sure if spotting EOF will stop it if it didn't in window.prompt case (only checking a checkbox helped).

The best solution would be some kind of callback but I would like to see sime hints from more experienced users.

like image 703
mnowotka Avatar asked Apr 12 '13 20:04

mnowotka


2 Answers

A way would be to use the Emscripten Filesystem API, for example by calling FS.init in the Module preRun function, passing a custom function as the standard input.

var Module = {
  preRun: function() {
    function stdin() {
      // Return ASCII code of character, or null if no input
    }

    var stdout = null; // Keep as default
    var stderr = null;  // Keep as default
    FS.init(stdin, stdout, stderr);
  }
};

The function is quite low-level: is must deal with one character at a time. To read some data from a blob, you could do something like:

var data = new Int8Array([1,2,3,4,5]);
var blob = new Blob([array], {type: 'application/octet-binary'});
var reader = new FileReader();
var result;
reader.addEventListener("loadend", function() {
  result = new Int8Array(reader.result);
});
var i = 0;
var Module = {
  preRun: function() {
    function stdin() {
      if (if < result.byteLength {
        var code = result[i];
        ++i;
        return code;
      } else {
        return null;
      }
    }

    var stdout = null; // Keep as default
    var stderr = null; // Keep as default
    FS.init(stdin, stdout, stderr);
  }
};

Note (as you have hinted), due to the asynchronous nature of the reader, there could be a race condition: the reader must have loaded before you can expect the data at the standard input. You might need to implement some mechanism to avoid this in a real case. Depending on your exact requirements, you could make it so the Emscripten program doesn't actually call main() until you have the data:

var fileRead = false;
var initialised = false;
var result;

var array =  new Int8Array([1,2,3,4,5]);
var blob = new Blob([array], {type: 'application/octet-binary'});
var reader = new FileReader();
reader.addEventListener("loadend", function() {
   result = new Int8Array(reader.result);
   fileRead = true;
   runIfCan();
});
reader.readAsArrayBuffer(blob);

var i = 0;
var Module = {
   preRun: function() {
      function stdin() {
         if (i < result.byteLength)
         {
            var code = result[i];
            ++i;
            return code;
         } else{
            return null;
         }
      }

      var stdout = null;
      var stderr = null;
      FS.init(stdin, stdout, stderr);
      initialised = true;
      runIfCan();
   },
   noInitialRun: true
};

function runIfCan() {
   if (fileRead && initialised) {
      // Module.run() doesn't seem to work here
      Module.callMain();
   }
}

Note: this is a version of my answer at Providing stdin to an emscripten HTML program? , but with focus on the standard input, and adding parts about passing data from a Blob.

like image 169
Michal Charemza Avatar answered Sep 20 '22 10:09

Michal Charemza


From what I understand you could try the following:

  1. Implement selecting a file in Javascript and access it via Javascript Blob interface.
  2. Allocate some memory in Emscripten

    var buf = Module._malloc( blob.size );
    
  3. Write the content of your Blob into the returned memory location from Javascript.

    Module.HEAPU8.set( new Uint8Array(blob), buf );
    
  4. Pass that memory location to a second Emscripten compiled function, which then processes the file content and

  5. Deallocate allocated memory.

    Module._free( buf );
    

Best to read the wiki first.

like image 20
abergmeier Avatar answered Sep 18 '22 10:09

abergmeier