Code Monkey home page Code Monkey logo

sane-wasm's Introduction

SANE WebAssembly (sane-wasm)

A project to bring the SANE API to Node.js and the Web using WebAssembly.

It supports USB scanners on browser environments through the WebUSB API and on Node.js using node-usb.

This works by compiling all SANE backends (and required dependencies) to WebAssembly using Emscripten. The other key piece is @RReverser's bridge from libusb to WebUSB (article/article/code/code) for direct USB access.

Right now, it includes all backends that have support for at least one USB device (genesys is not included due to issues). No external backends are included at the moment.

WebScan

Check webscan.goncalomb.com for a demo of sane-wasm. This is a React application that uses sane-wasm for document/image scanning directly in the browser. It exposes all scanning options to the user for full control.

Usage

The pre-compiled WebAssembly package for sane-wasm is published on NPM:

https://www.npmjs.com/package/sane-wasm?activeTab=code

For Web Environments (e.g. Webpack)

npm install -D sane-wasm

The main .js will not be bundled with your application. A loader (lib/loader.js) is provided to automatically load the .js/.wasm files from a CDN (jsdelivr.com). You can configure the loader to serve the files from your server if you want.

See examples/webpack/ for a working example.

For Node.js

npm install sane-wasm usb

The usb package (node-usb) is required to provide USB functionality.

See examples/node/ for a working example.

As Git Submodule (not recommended) / Custom Build

As an alternative, you can add sane-wasm as a submodule to your application and integrate ./build.sh with your build process. This is not recommended as it relies on some magical/dubious code on ./build.sh to make the build work.

You can also just build sane-wasm independently and use the build artifacts...

<script src="build/libsane.js"></script>
<script>
    window.LibSANE().then(lib => {
        // your code
        console.log(lib.sane_init());
    });
</script>
const { libsane } = require('./sane-wasm');
libsane().then(lib => {
    console.log(lib.sane_init());
});
import { libsane } from './sane-wasm';
const lib = await libsane();
console.log(lib.sane_init());

Building

SANE Only

Building requires emscripten and all the required tools to build the dependencies.

The preferred way is just to use the pre-configured Docker image:

git clone --recurse-submodules https://github.com/goncalomb/sane-wasm.git
cd sane-wasm
./build.sh --with-docker --clean

Start the test server with:

./build.sh --with-docker --no-build --emrun

Check the test page at: http://localhost:6931/libsane.html.

build.sh

The ./build.sh script has other options for debugging:

usage: build.sh [options]
  --with-docker  run with docker (preferred)
  --clean        clean 'deps' and 'build' directories
  --no-build     don't actually build
  --debug        enable debug flags
  --emrun        run emrun development server
  --shell        run debug shell (depends on --with-docker)

Full Build (SANE + TypeScript)

To do a full build use npm run build.

API

Most of the SANE API is exposed as-is. Check the SANE Standard.

Some safeguards are in place to avoid API misuse and prevent memory leaks. It's important to understand the SANE API and follow the Code Flow to avoid issues.

Differences with the SANE API

The most important difference with the underlying SANE API is that device handles are not exposed. This means that sane_open() does not return a device handle. A single handle is managed by the internal code. This effectively means that only one device can be accessed at a time. This was a design decision made to simplify the API and prevent other issues.

Personally, I believe that this is an acceptable change, especially for WebAssembly where it may be easier to lose track of opened resources and crash the application. The SANE API is also somewhat unforgiving and building more safeguards around it (especially with multiple handles) is not worth the effort. Ultimately I don't see a use that requires more than one device open at a time. -goncalomb

Documentation

Check API documentation at: goncalomb.github.io/sane-wasm/modules.html.

License

Because of the weird state of SANE's licensing (GPL + linking exception, on some backends), see backends/LICENSE. I'm releasing this project with dual licensing GNU GPLv2 + GNU LGPLv2.1. IANAL, you choose.

sane-wasm's People

Contributors

goncalomb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

sane-wasm's Issues

Memory Access Violation on SANE's test backend

During the sane-wasm build we are suppose to set number_of_devices to 0 (test backend). But I've discovered an issue with that option, see build.sh comment:

sane-wasm/build.sh

Lines 236 to 251 in 3100bc9

# Set test backend's number of devices to 0.
# XXX: Oops what? There is some kind of invalid memory access on the backend
# file deps/backends/backend/test.c, setting number_of_devices to 0 causes
# some weird behavior and it's as if 1 device was selected.
# I've looked briefly into this, but was unable to find the cause,
# it's somehow related to how the option is read with:
# read_option (line, "number_of_devices", ...
# and then used on a for loop:
# for (num = 0; num < init_number_of_devices; num++)
# that loop should never run with init_number_of_devices = 0.
# I've tested this config on my linux distribution version of SANE and
# it triggers a Segmentation Fault so there is definitely a problem there.
# XXX: Setting it to -999 for now, fix this, and revert this to 0.
# The fix should probably be pushed upstream to SANE itself:
# https://gitlab.com/sane-project/backends/-/issues
sed -i "s/^number_of_devices .*/number_of_devices -999/g" "$PREFIX/etc/sane.d/test.conf"

The issue is most likely on the file backends/backend/test.c.

The backends patch should be updated to include the fix: https://github.com/goncalomb/sane-wasm/tree/master/deps. And bonus points for fixing it upstream: https://gitlab.com/sane-project/backends/-/issues.

WebUSB + libusb + pthreads

Before the public release of sane-wasm I spent A LOT of time patching and debugging @RReverser's initial WebUSB backend for libusb (libusb/libusb#1057) to add basic multi-threaded support. This was basically my introduction to Emscripten and its inner workings.

Multi-threaded support at the libusb level is required for sane-wasm because normally the SANE backends open the USB device on the main thread, but then they spawn a new thread to do the actual scanning. Changing this behavior on all backends is unfeasible.

https://github.com/goncalomb/sane-wasm/blob/3100bc98c037cd8ff63ea19c8dd98e3f13931b2f/deps/libusb.patch

The patch adds very rudimentary support for phreads by proxying critical calls back to the main thread, where the device was originally opened. This is required because we cannot share WebUSB USBDevice handles (or emscripten::val) across to the Service Workers (Emscripten threads).

This approach works for now. But I'm opening this issue to continue the discussion on this topic. This is by no means fixed or a complete solution.

Some current shortcomings are:

  • not all calls are being proxied to the main thread (fixable, and required for complete support)
  • the event system is also far from ideal, we are just posting messages to all threads
  • deadlock issues with the main thread waiting for a worker thread (pthread_join), while the worker thread is waiting for a USB transaction on the main thread, "fixed", but not pretty
  • overall very unappealing implementation
  • not thoroughly tested (I only used the SANE pixma backend for testing)

It remains to be seen if this is the correct approach or the implementation should just be rewritten from scratch, maybe using a dedicated thread for all the WebUSB calls? The original pull request (libusb/libusb#1057) also has some discussion on this topic.

Contributions are welcome.

sane_get_devices returns empty list, while scanimage -L does not

While scanimage -L correctly lists one device: device brother5:bus1;dev1' is a Brother ADS-1700W USB scanner

sane_get_devices() returns an empty device list in the code below:

import { SANEStatus, libsane } from "sane-wasm";

libsane({
  debugSANE: true,
  debugUSB: true,
  debugFunctionCalls: true,
}).then(async (sane) => {
  const init = sane.sane_init();
  console.log(init.status === SANEStatus.GOOD);

  const state = sane.sane_get_state();
  console.log(state);

  let { status, devices } = await sane.sane_get_devices();
  console.log(status === SANEStatus.GOOD, devices);

  await sane.sane_exit();
});

The output of the code on my machine is

true
{
  initialized: true,
  version_code: 16842753,
  version: { major: 1, minor: 1, build: 1 },
  open: false
}
true []

I would expect the device list to contain all devices listed by scanimage -L, instead of being empty.

I am on node v21.7.0, on EndeavourOS (an Arch-based Linux distro).

SANE genesys backend causes lockup during sane_get_devices

The genesys backend (http://www.sane-project.org/sane-backends.html#S-GENESYS) is used by a fair number of devices, so it would be good to find the issue that is causing it to lock during sane_get_devices, and re-enable the backend.

sane-wasm/build.sh

Lines 126 to 136 in 2d11481

# The backends are selected automatically by reading SANE's .desc files and
# selecting the backends that support at least one USB device.
# http://www.sane-project.org/sane-backends.html
# Extra backends:
# test: for testing
# gphoto2,v4l: not enabled ATM, XXX: check compatibility and usefulness
# Excluded backends:
# template,unsupported: not real backends
# dell1600n_net: doesn't really support any USB device
# genesys: causes lockup during sane_get_devices, XXX: to be fixed
SANE_WASM_BACKENDS=$(./utils.py usb-backends -i test -e template,unsupported,dell1600n_net,genesys)

Can be debugged using the test page, by stopping script execution right after sane_get_devices. But can cause the inspector to lock and crash.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.