BlueMonkMN's picture

Shall I Implement Joystick support on Windows platform

Because I'm trying to eliminate all traces of DirectX from SGDK2, I'll be manually implementing joystick support using winmm.dll. I've copied a couple lines from Tao because I didn't know the correct MarshalAs attributes to apply to some strings in the JOYCAPS structure, and a Google search for JOYCAPS returned a page of Tao source code. (Is that kosher?)

Might you be interested in the Joystick class when it's done, or will you be basing Joystick support on something else?


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

I'd be very interested in this class, indeed!

Reusing code from Tao is no problem (MIT/X11 license rocks). The only restriction is that you have to keep the original license intact, if you copy "significant portions" of the source code (e.g. take the whole file and make a couple of changes).

If you encounter any problems e.g. with p/invokes, make a post here and I'll help.

Edit: By the way, I think it's awesome that SGDK will use OpenTK. I've seen this project in the past and it looks very interesting - only problem being it was tied to Windows and the old MDX runtime.

BlueMonkMN's picture

Here's what I've got:

public class Joystick
{
   #region Embedded Types
   [Flags()]
   private enum JoystickFlags
   {
      JOY_RETURNX = 0x1,
      JOY_RETURNY = 0x2,
      JOY_RETURNZ = 0x4,
      JOY_RETURNR = 0x8,
      JOY_RETURNU = 0x10,
      JOY_RETURNV = 0x20,
      JOY_RETURNPOV = 0x40,
      JOY_RETURNBUTTONS = 0x80,
      JOY_RETURNALL = (JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | JOY_RETURNR | JOY_RETURNU | JOY_RETURNV | JOY_RETURNPOV | JOY_RETURNBUTTONS)
   }
 
   [StructLayout(LayoutKind.Sequential)]
   private struct JOYCAPS
   {
      public UInt16 wMid;
      public UInt16 wPid;
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
      public string szPname;
      public Int32 wXmin;
      public Int32 wXmax;
      public Int32 wYmin;
      public Int32 wYmax;
      public Int32 wZmin;
      public Int32 wZmax;
      public Int32 wNumButtons;
      public Int32 wPeriodMin;
      public Int32 wPeriodMax;
      public Int32 wRmin;
      public Int32 wRmax;
      public Int32 wUmin;
      public Int32 wUmax;
      public Int32 wVmin;
      public Int32 wVmax;
      public Int32 wCaps;
      public Int32 wMaxAxes;
      public Int32 wNumAxes;
      public Int32 wMaxButtons;
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
      public string szRegKey;
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
      public string szOEMVxD;
   }
 
   [StructLayout(LayoutKind.Sequential)]
   private struct JOYINFOEX
   {
      public UInt32 dwSize;
      [MarshalAs(UnmanagedType.I4)]
      public JoystickFlags dwFlags;
      public Int32 dwXpos;
      public Int32 dwYpos;
      public Int32 dwZpos;
      public Int32 dwRpos;
      public Int32 dwUpos;
      public Int32 dwVpos;
      public UInt32 dwButtons;
      public UInt32 dwButtonNumber;
      public Int32 dwPOV;
      public UInt32 dwReserved1;
      public UInt32 dwReserved2;
   }
   #endregion
 
   #region Private Members
   private int deviceNum;
 
   private JOYCAPS joyCaps;
   private JOYINFOEX joyInfo;
   #endregion
 
   [DllImport("Winmm.dll")]
   private static extern UInt32 joyGetDevCaps(Int32 uJoyID, out JOYCAPS pjc, Int32 cbjc);
   [DllImport("Winmm.dll")]
   private static extern UInt32 joyGetPosEx(Int32 uJoyID, out JOYINFOEX pji);
 
   /// <summary>
   /// Returns the number of joysticks available on the system.
   /// </summary>
   /// <returns>A number from 0 to 16</returns>
   public static int GetDeviceCount()
   {
      JOYCAPS joyCaps = new JOYCAPS();
      int count = 0;
      for (int i = 0; i < 16; i++)
      {
         UInt32 result = joyGetDevCaps(i, out joyCaps, Marshal.SizeOf(joyCaps));
         if (result == 0)
            count++;
      }
      return count;
   }
 
   /// <summary>
   /// Create an object which can be used to acces information about the specified
   /// joystick number.
   /// </summary>
   /// <param name="deviceNum">Number from 0 to 15 indicating a joystick number</param>
   public Joystick(int deviceNum)
   {
      this.deviceNum = deviceNum;
      if (0 != joyGetDevCaps(deviceNum, out joyCaps, Marshal.SizeOf(joyCaps)))
         throw new InvalidOperationException("Failed to access specified joystick");
   }
 
   /// <summary>
   /// Read all data from the device associated with this Joystick into the
   /// Position, POVAngle and Button properties.
   /// </summary>
   /// <remarks>Buttons are retrieved by accessing the Joystick's indexer
   /// property</remarks>
   /// <example>
   /// if (myJoy[0])
   /// {
   ///    // button 0 is pressed
   /// }
   /// </example>
   public void Read()
   {
      joyInfo.dwSize = (UInt32)Marshal.SizeOf(joyInfo);
      joyInfo.dwFlags = JoystickFlags.JOY_RETURNALL;
      joyGetPosEx(deviceNum, out joyInfo);
   }
 
   /// <summary>
   /// The position of the joystick's x-axis input during the last call to <see cref="Read"/>.
   /// </summary>
   public int XPosition
   {
      get
      {
         return joyInfo.dwXpos;
      }
   }
 
   /// <summary>
   /// The position of the joystick's y-axis input during the last call to <see cref="Read"/>.
   /// </summary>
   public int YPosition
   {
      get
      {
         return joyInfo.dwYpos;
      }
   }
 
   /// <summary>
   /// The position of the joystick's z-axis input during the last call to <see cref="Read"/>.
   /// </summary>
   public int ZPosition
   {
      get
      {
         return joyInfo.dwZpos;
      }
   }
 
   /// <summary>
   /// The position of the joystick's rudder input during the last call to <see cref="Read"/>.
   /// </summary>
   public int RPosition
   {
      get
      {
         return joyInfo.dwRpos;
      }
   }
 
   /// <summary>
   /// The position of the joystick's u-axis input during the last call to <see cref="Read"/>.
   /// </summary>
   public int UPosition
   {
      get
      {
         return joyInfo.dwUpos;
      }
   }
 
   /// <summary>
   /// The position of the joystick's v-axis input during the last call to <see cref="Read"/>.
   /// </summary>
   public int VPosition
   {
      get
      {
         return joyInfo.dwVpos;
      }
   }
 
   /// <summary>
   /// The position of the joystick's POV control during the last call to <see cref="Read"/>,
   /// represented as a number between 0 and 35900 in hundredths of degrees
   /// </summary>
   public int POVAngle
   {
      get
      {
         return joyInfo.dwPOV;
      }
   }
 
   /// <summary>
   /// Determines which buttons were pressed during the last call to <see cref="Read"/>.
   /// </summary>
   /// <param name="buttonNum">A number from 0 to 31 specifying a button number</param>
   /// <returns>True if the button is pressed, or False otherwise.</returns>
   public bool this[byte buttonNum]
   {
      get
      {
         if (buttonNum >= 32)
            throw new ArgumentException("Invalid button number", "buttonNum");
         return 0 != (joyInfo.dwButtons & (1 << buttonNum));
      }
   }
 
   /// <summary>
   /// Returns a string identifying the joystick in plain text
   /// </summary>
   public string Name
   {
      get
      {
         return joyCaps.szPname;
      }
   }
 
   /// <summary>
   /// Returns the minimum value of <see cref="XPosition"/>.
   /// </summary>
   public int MinimumX
   {
      get
      {
         return joyCaps.wXmin;
      }
   }
 
   /// <summary>
   /// Returns the maximum value of <see cref="XPosition"/>.
   /// </summary>
   public int MaximumX
   {
      get
      {
         return joyCaps.wXmax;
      }
   }
 
   /// <summary>
   /// Returns the minimum value of <see cref="YPosition"/>.
   /// </summary>
   public int MinimumY
   {
      get
      {
         return joyCaps.wYmin;
      }
   }
 
   /// <summary>
   /// Returns the maximum value of <see cref="YPosition"/>.
   /// </summary>
   public int MaximumY
   {
      get
      {
         return joyCaps.wYmax;
      }
   }
 
   /// <summary>
   /// Returns the minimum value of <see cref="ZPosition"/>.
   /// </summary>
   public int MinimumZ
   {
      get
      {
         return joyCaps.wZmin;
      }
   }
 
   /// <summary>
   /// Returns the maximum value of <see cref="ZPosition"/>.
   /// </summary>
   public int MaximumZ
   {
      get
      {
         return joyCaps.wZmax;
      }
   }
 
   /// <summary>
   /// Returns the minimum value of <see cref="RPosition"/>.
   /// </summary>
   public int MinimumR
   {
      get
      {
         return joyCaps.wRmin;
      }
   }
 
   /// <summary>
   /// Returns the maximum value of <see cref="RPosition"/>.
   /// </summary>
   public int MaximumR
   {
      get
      {
         return joyCaps.wRmax;
      }
   }
 
   /// <summary>
   /// Returns the minimum value of <see cref="UPosition"/>.
   /// </summary>
   public int MinimumU
   {
      get
      {
         return joyCaps.wUmin;
      }
   }
 
   /// <summary>
   /// Returns the maximum value of <see cref="UPosition"/>.
   /// </summary>
   public int MaximumU
   {
      get
      {
         return joyCaps.wUmax;
      }
   }
 
   /// <summary>
   /// Returns the minimum value of <see cref="VPosition"/>.
   /// </summary>
   public int MinimumV
   {
      get
      {
         return joyCaps.wVmin;
      }
   }
 
   /// <summary>
   /// Returns the maximum value of <see cref="VPosition"/>.
   /// </summary>
   public int MaximumV
   {
      get
      {
         return joyCaps.wVmax;
      }
   }
}
BlueMonkMN's picture

My GetDeviceCount function was a little silly. Here's a slightly more intelligent version:

   /// <summary>
   /// Returns the number of joysticks available on the system.
   /// </summary>
   /// <returns>A number from 0 to 16</returns>
   public static int GetDeviceCount()
   {
      JOYCAPS joyCaps = new JOYCAPS();
      int count;
      for (count = 0; count < 16; count++)
      {
         if (0 == joyGetDevCaps(count, out joyCaps, Marshal.SizeOf(joyCaps)))
            return count;
      }
      return count;
   }
the Fiddler's picture

Thanks for the code, this helps quite a lot.

Do you know if there are any limitations in winmm.dll that we should be aware of? Is there any reason to implement a Raw Input driver besides this (apart from rumble/force-feedback support?)

BlueMonkMN's picture

I'm afraid I don't have any inside scoop on winmm. I was just going for a quick and simple implementation, so I didn't do much research, and don't have much experience with winmm.dll. Maybe someone else will chime in with details if there's anything important to be aware of.

BlueMonkMN's picture

One more correction:
0 == joyGetDevCaps
should be
0 != joyGetDevCaps

the Fiddler's picture

Nice, thanks. I'll be adding this code to 0.9.1, but it won't be enabled for the release (need to port it to X11 and test it, first).

BlueMonkMN's picture

Did Joystick support ever make it into OpenTK? I'm not seeing much there.

the Fiddler's picture

There's no joystick support at this point. I am working on a linux joystick driver, but progress is slow since I don't plan to use this functionality in the immediate future.

Edit: It was surprisingly simple to add joystick support for linux, so I'll be merging the code at some point in the near future.

BlueMonkMN's picture

Could you give me a clue how to access the joystick from Linux MonoDevelop in C#? I was going to use similar code in my project (at least until OpenTK makes it available, but I know in the case of keyboard access, I had to write my own code because I'm not using GameWindow).