
/*

USBJoystick - a RISC OS USB HID Joystick driver

Copyright 2018-2026 Richard Walker
Licensed under the Apache License, Version 2.0 - see LICENSE.

Third-party code, licensing and acknowledgements: see doc.License

*/

#include "swis.h"
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "Global/NewErrors.h"
#include "Global/RISCOS.h"
#include "Global/Keyboard.h"
#include "Global/Pointer.h"

#include "debugrep.h"
#include "adc.h"
#include "mouse.h"
#include "joyhelp.h"
#include "joyswis.h"
#include "usb.h"
#include "joydev.h"
#include "USBJoystickHdr.h"
#include "usbjoystick.h"
#include "errors.h"
#include "joycompat.h"
#include "cmdmap.h"
#include "cmdemu.h"
#include "cmdctrl.h"
#include "config.h"
#include "keys.h"
#include "fake.h"



extern void *Resources(void);



// Module state

extern void* g_module_pw = NULL;



// Module errors are looked up via MessageTrans against Resources.UK.Messages
// - see h.errors and c.errors. messages is opened in module_initialise and
// closed in module_finalise.

int messages[4];





// Joystick data
struct joydata_struct joy_data[JOY_MAX];
struct adc_data_struct adc_data;
struct adc_map_struct adc_map;

// which joystick slot are we currently setting up?
uint32_t joy_slot = 0;

// bumped every time a device attaches or detaches - lets a poller (a
// Configure plug-in, an SDL backend, etc) cheaply notice the device list
// changed without re-enumerating every call - see Joystick_Enumerate
uint32_t joy_generation = 0;








int emulate_serial_port_on = TRUE;
int emulate_joy_on = TRUE;

int mouse_control_on = FALSE;
int keyboard_control_on = FALSE;


int upcall_claimed = FALSE;
int ukswiv_claimed = FALSE;
int bytev_claimed = FALSE;









_kernel_oserror *module_initialise(const char *cmd_tail, int32_t podule_base, void *pw)
{
  _kernel_oserror *error;

  IGNORE(cmd_tail);
  IGNORE(podule_base);

  debug_printf("module_initialise: begin\n");
  g_module_pw = pw;

  // clean up joysticks data structure
  clean_joystick_data();

  // reset synthesised-key held state (static storage is 0, a valid KeyNo)
  keys_init();

  // register resources, and open our Messages file, before anything else -
  // both are needed for geterror()/MessageTrans lookups to work in every
  // step that follows (including the very next one, the USB check below)
  #ifndef ROM_MODULE
  error = _swix(ResourceFS_RegisterFiles, _IN(0), Resources());
  #else
  /* In ROM builds, all resource files live in the Messages module instead */
  error = NULL;
  #endif

  if (!error) {
    error = _swix(MessageTrans_OpenFile, _INR(0,2), messages, "Resources:$.Resources.USBJoystick.Messages", 0);
    if (error)
      debug_printf("module_initialise: Cannot open Messages file: %u %s\n", error->errnum, error->errmess);
  }

  // we must have USB
  // num_from_string will give 'swi not known', so swap this for our own error
  if (!error) {
    int32_t usbdriver_version = 0;
    error = _swix(OS_SWINumberFromString, _IN(1) | _OUT(0), "USBDriver_Version", &usbdriver_version);
    if (error) error = (_kernel_oserror*) geterror_p(Error_NoUSB, Module_Title);
  }

  // claim upcall vector for acting on usb data received
  if (!error) {
    error = _swix(OS_Claim, _INR(0,2), UpCallV, (void const *)&upcallv_hook, pw);
    if (error)
      debug_printf("module_initialise: Cannot claim UpCallV: %u %s\n", error->errnum, error->errmess);
    else
      upcall_claimed = TRUE;
  }

  // claim ukswi vector for serial port api cmpatibility
  if (!error) {
    error = _swix(OS_Claim, _INR(0,2), UKSWIV, (void const *)ukswiv_pre_hook, pw);
    if (error)
      debug_printf("module_initialise: Cannot claim UkSWIV: %u %s\n", error->errnum, error->errmess);
    else
     ukswiv_claimed = TRUE;
  }

  // claim byte vector for emulating Acorn I/O podule ADC
  if (!error) {
    error = _swix(OS_Claim, _INR(0,2), ByteV, (void const *)&bytev_hook, pw);
    if (error)
      debug_printf("module_initialise: Cannot claim ByteV: %u %s\n", error->errnum, error->errmess);
    else
      bytev_claimed = TRUE;
  }

  // build a list of connected joysticks
  // each usb device will be queried to check for suitability
  if (!error) {
    debug_printf("module_initialise: Scanning USB devices...\n");
    usb_scan_devices(&joy_check_device, NULL);
    debug_printf("module_initialise: Scan complete\n");

    // Inject any synthetic test device(s) - a no-op unless USBJOY_FAKE_DEVICE
    // is set (h.fake). Lets the mapping UI be exercised with no real hardware.
    fake_devices_create();
  }

  debug_printf("module_initialise: end\n");

  return (_kernel_oserror *) error;
}





_kernel_oserror *module_swi(int32_t swi_offset, _kernel_swi_regs *r, void *pw)
{
  IGNORE(pw);

  switch(swi_offset)
  {
    // Always active - see h.joyswis for why this one, unlike the three
    // Emulate*-toggled below, isn't optional: it's this module's own
    // registered SWI chunk, not a shared vector grab.
    case MODULE_SWI(Joystick_Read):
      return swi_joystick_read(r);

    case MODULE_SWI(Joystick_CalibrateTopRight):
    case MODULE_SWI(Joystick_CalibrateBottomLeft):
      return NULL;

    case MODULE_SWI(Joystick_Status):
    {
      if (emulate_serial_port_on)
        return swi_joystick_status(r);
      else
        return geterror(Error_NoSuchSWI);
      break;
    }

    // Native slot-based enumeration/introspection API (doc.APIs section 5)
    case MODULE_SWI(Joystick_Enumerate):
      return swi_joystick_enumerate(r);
    case MODULE_SWI(Joystick_DeviceInfo):
      return swi_joystick_device_info(r);
    case MODULE_SWI(Joystick_AxisInfo):
      return swi_joystick_axis_info(r);
    case MODULE_SWI(Joystick_AxisValues):
      return swi_joystick_axis_values(r);
    case MODULE_SWI(Joystick_GetMapping):
      return swi_joystick_get_mapping(r);

    // Assign a device to a legacy Joystick_Read stick number (a device
    // attribute, not a binding - see c.cmdmap).
    case MODULE_SWI(Joystick_SetStick):
      return swi_joystick_set_stick(r);

    // Native emulation toggle (shared implementation with
    // *USBJoystick_Emulate* - see c.cmdemu). R0 selects which classic
    // API's flag to touch - see h.joyswis for the JOYSTICK_EMULATE_* values.
    case MODULE_SWI(Joystick_Emulate):
      return swi_joystick_emulate(r);

    // Config-store persistence (see doc.Config, c.config)
    case MODULE_SWI(Joystick_SaveMapping):
      return swi_joystick_save_mapping(r);
    case MODULE_SWI(Joystick_RevertToDefaultMap):
      return swi_joystick_revert_to_default_map(r);
    case MODULE_SWI(Joystick_ReloadConfig):
      return swi_joystick_reload_config(r);

    // Binding-table API (see h.binding, c.binding).
    case MODULE_SWI(Joystick_ReadBindings):
      return swi_joystick_read_bindings(r);
    case MODULE_SWI(Joystick_WriteBindings):
      return swi_joystick_write_bindings(r);
    case MODULE_SWI(Joystick_AddBinding):
      return swi_joystick_add_binding(r);
    case MODULE_SWI(Joystick_ReadDefaultBindings):
      return swi_joystick_read_default_bindings(r);

    // Global output gates, independent of the binding table (see h.joyswis)
    case MODULE_SWI(Joystick_Control):
      return swi_joystick_control(r);
    case MODULE_SWI(Joystick_SaveSettings):
      return swi_joystick_save_global(r);
  }

  return geterror_p(Error_ModuleBadSWI, Module_Title);
}























_kernel_oserror *module_finalise(int32_t fatal, int32_t podule, void *pw)
{
  IGNORE(fatal);
  IGNORE(podule);

  _kernel_oserror *error = NULL;

  debug_printf("module_finalise: begin");

  // release any keys still held down by synthesised-key mappings, so they
  // don't stick after the module goes away
  for (uint32_t i=0; i<JOY_MAX; i++)
    release_keys(i);

  if (upcall_claimed == TRUE) {
    error = _swix(OS_Release, _INR(0,2), UpCallV, (void const *)&upcallv_hook, pw);
    if (error) {
      debug_printf("module_finalise: failed to release upcallv, error number %d: %s\n", error->errnum, error->errmess);
    }
  }

  if (ukswiv_claimed == TRUE) {
    error = _swix(OS_Release, _INR(0,2), UKSWIV, (void const *)ukswiv_pre_hook, pw);
    if (error) {
      debug_printf("module_finalise: failed to release ukswiv, error number %d: %s\n", error->errnum, error->errmess);
    }
  }

  if (bytev_claimed == TRUE) {
    error = _swix(OS_Release, _INR(0,2), ByteV, (void const *)&bytev_hook, pw);
    if (error) {
      debug_printf("module_finalise: failed to release bytev, error number %d: %s\n", error->errnum, error->errmess);
    }
  }

  // (no PointerV to release - movement is injected via PointerReason_Report)

  // close down any USB file handles we had active
  for (uint32_t i=0; i<JOY_MAX; i++) {
    if (joy_data[i].fp != NULL) {
      error = _swix(OS_Find, _INR(0,1), 0, joy_data[i].fp);
      if (error) {
        debug_printf("module_finalise: failed to close joystick %d fp, error number %d: %s\n", i, error->errnum, error->errmess);
      }
      joy_data[i].fp = 0;
    }
  }

  #ifndef ROM_MODULE
  error = _swix(ResourceFS_DeregisterFiles, _IN(0), Resources());
  if (error) {
    debug_printf("module_finalise: failed to degreister resources, error number %d: %s\n", error->errnum, error->errmess);
  }
  #endif

  error = _swix(MessageTrans_CloseFile, _IN(0), messages);
  if (error) {
    debug_printf("module_finalise: failed to close Messages file, error number %d: %s\n", error->errnum, error->errmess);
  }

  // allow module to die
  debug_printf("module_finalise: end (module can die)");
  return NULL;
}


