# Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.

--- a/src/sun_mouse.c	Mon Jul 30 16:37:55 2012
+++ b/src/sun_mouse.c	Mon Mar 18 17:34:23 2013
@@ -1,6 +1,5 @@
 /*
- * Copyright (c) 2004-2005, 2008-2010, Oracle and/or its affiliates.
- * All rights reserved.
+ * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved.
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
@@ -69,6 +68,8 @@
 # include <sys/vuid_wheel.h>
 #endif
 
+#include <libdevinfo.h>
+
 /* Support for scaling absolute coordinates to screen size in
  * Solaris 10 updates and beyond */
 #if !defined(HAVE_ABSOLUTE_MOUSE_SCALING)
@@ -104,6 +105,9 @@
     Ms_screen_resolution         absres;
 #endif
     OsTimerPtr          remove_timer;   /* Callback for removal on ENODEV */
+    int                 relToAbs;
+    int                 absX;
+    int                 absY;
 } VuidMseRec, *VuidMsePtr;
 
 static VuidMsePtr       vuidMouseList = NULL;
@@ -117,6 +121,9 @@
 static void vuidMouseSendScreenSize(ScreenPtr pScreen, VuidMsePtr pVuidMse);
 static void vuidMouseAdjustFrame(ADJUST_FRAME_ARGS_DECL);
 
+static int CheckRelToAbs(InputInfoPtr pInfo);
+static int CheckRelToAbsWalker(di_node_t node, void *arg);
+
 static int vuidMouseGeneration = 0;
 
 #if HAS_DEVPRIVATEKEYREC
@@ -219,6 +226,11 @@
 
     pVuidMse->buffer = (unsigned char *)&pVuidMse->event;
     pVuidMse->strmod = xf86SetStrOption(pInfo->options, "StreamsModule", NULL);
+    pVuidMse->relToAbs = xf86SetIntOption(pInfo->options, "RelToAbs", -1);
+    if (pVuidMse->relToAbs == -1)
+	pVuidMse->relToAbs = CheckRelToAbs(pInfo);
+    pVuidMse->absX = 0;
+    pVuidMse->absY = 0;
 
     /* Setup the local procs. */
     pVuidMse->wrapped_device_control = pInfo->device_control;
@@ -240,6 +252,120 @@
     return TRUE;
 }
 
+/*
+ * It seems that the mouse that is presented by the Emulex ILOM
+ * device (USB 0x430, 0xa101 and USB 0x430, 0xa102) sends relative
+ * mouse movements.  But relative mouse movements are subject to
+ * acceleration.  This causes the position indicated on the ILOM
+ * window to not match what the Xorg server actually has.  This
+ * makes the mouse in this environment rather unusable.  So, for the
+ * Emulex ILOM device, we will change all relative mouse movements
+ * into absolute mouse movements, making it appear more like a
+ * tablet.  This will not be subject to acceleration, and this
+ * should keep the ILOM window and Xorg server with the same values
+ * for the coordinates of the mouse.
+ */
+
+typedef struct reltoabs_match {
+	int matched;
+	char const *matchname;
+	} reltoabs_match_t;
+
+/* Sun Microsystems, keyboard / mouse */
+#define	RELTOABS_MATCH1	"usbif430,a101.config1.1"
+/* Sun Microsystems, keyboard / mouse / storage */
+#define	RELTOABS_MATCH2	"usbif430,a102.config1.1"
+
+static int
+CheckRelToAbsWalker(di_node_t node, void *arg)
+{
+	di_minor_t minor;
+	char *path;
+	int numvalues;
+	int valueon;
+	char const *stringptr;
+	int status;
+	reltoabs_match_t *const matchptr = (reltoabs_match_t *)arg;
+	char *stringvalues;
+
+	for (minor = di_minor_next(node, DI_MINOR_NIL);
+	  minor != DI_MINOR_NIL;
+	  minor = di_minor_next(node, minor)) {
+	    path = di_devfs_minor_path(minor);
+	    status = path != NULL && strcmp(path, matchptr -> matchname) == 0;
+	    di_devfs_path_free(path);
+	    if (status)
+		break;
+	    }
+
+	if (minor == DI_MINOR_NIL)
+	    return (DI_WALK_CONTINUE);
+
+	numvalues = di_prop_lookup_strings(DDI_DEV_T_ANY, node,
+	  "compatible", &stringvalues);
+	if (numvalues <= 0)
+	    return (DI_WALK_CONTINUE);
+
+	for (valueon = 0, stringptr = stringvalues; valueon < numvalues;
+	  valueon++, stringptr += strlen(stringptr) + 1) {
+	    if (strcmp(stringptr, RELTOABS_MATCH1) == 0) {
+		matchptr -> matched = 1;
+		return (DI_WALK_TERMINATE);
+	    }
+	    if (strcmp(stringptr, RELTOABS_MATCH2) == 0) {
+		matchptr -> matched = 2;
+		return (DI_WALK_TERMINATE);
+	    }
+	}
+	return (DI_WALK_CONTINUE);
+}
+
+static int
+CheckRelToAbs(InputInfoPtr pInfo)
+{
+	char const *device;
+	char const *matchname;
+	ssize_t readstatus;
+	di_node_t node;
+	struct stat statbuf;
+	char linkname[512];
+	reltoabs_match_t reltoabs_match;
+
+	device = xf86CheckStrOption(pInfo->options, "Device", NULL);
+	if (device == NULL)
+	    return (0);
+
+	matchname = device;
+
+	if (lstat(device, &statbuf) == 0 &&
+	  (statbuf.st_mode & S_IFMT) == S_IFLNK) {
+	    readstatus = readlink(device, linkname, sizeof(linkname));
+	    if (readstatus > 0 && readstatus < sizeof(linkname)) {
+		linkname[readstatus] = 0;
+		matchname = linkname;
+		if (strncmp(matchname, "../..", sizeof("../..") - 1) == 0)
+		    matchname += sizeof("../..") - 1;
+	    }
+	}
+
+	if (strncmp(matchname, "/devices", sizeof("/devices") - 1) == 0)
+	    matchname += sizeof("/devices") - 1;
+
+	reltoabs_match.matched = 0;
+	reltoabs_match.matchname = matchname;
+
+	node = di_init("/", DINFOCPYALL);
+	if (node == DI_NODE_NIL)
+	    return (0);
+
+	di_walk_node(node, DI_WALK_CLDFIRST, (void *)&reltoabs_match,
+	  CheckRelToAbsWalker);
+
+	di_fini(node);
+
+	return (reltoabs_match.matched != 0);
+}
+
 static void
 vuidFlushAbsEvents(InputInfoPtr pInfo, int absX, int absY,
                    Bool *absXset, Bool *absYset)
@@ -275,21 +401,29 @@
 static void
 vuidReadInput(InputInfoPtr pInfo)
 {
-    MouseDevPtr pMse;
-    VuidMsePtr pVuidMse;
-    int buttons;
+    MouseDevPtr pMse = pInfo->private;
+    VuidMsePtr pVuidMse = getVuidMsePriv(pInfo);
+    int buttons = pMse->lastButtons;
     int dx = 0, dy = 0, dz = 0, dw = 0;
-    unsigned int n;
-    unsigned char *pBuf;
+    unsigned int n = 0;
+    unsigned char *pBuf = pVuidMse->buffer;
     int absX = 0, absY = 0;
+    int hdisplay = 0, vdisplay = 0;
     Bool absXset = FALSE, absYset = FALSE;
+    int relToAbs = pVuidMse->relToAbs;
 
-    pMse = pInfo->private;
-    pVuidMse = getVuidMsePriv(pInfo);
-    buttons = pMse->lastButtons;
-    pBuf = pVuidMse->buffer;
-    n = 0;
+    if (relToAbs) {
+	ScreenPtr   pScreen = miPointerGetScreen(pInfo->dev);
+	ScrnInfoPtr pScr = XF86SCRNINFO(pScreen);
 
+	if (pScr->currentMode) {
+	    hdisplay = pScr->currentMode->HDisplay;
+	    vdisplay = pScr->currentMode->VDisplay;
+	}
+	absX = pVuidMse->absX;
+	absY = pVuidMse->absY;
+    }
+
     do {
         n = read(pInfo->fd, pBuf, sizeof(Firm_event));
 
@@ -348,10 +482,34 @@
             int delta = pVuidMse->event.value;
             switch(pVuidMse->event.id) {
             case LOC_X_DELTA:
-                dx += delta;
+		if (!relToAbs)
+		    dx += delta;
+		else {
+		    if (absXset)
+			vuidFlushAbsEvents(pInfo, absX, absY, &absXset, &absYset);
+		    absX += delta;
+		    if (absX < 0)
+			absX = 0;
+		    else if (absX >= hdisplay && hdisplay > 0)
+			absX = hdisplay - 1;
+		    pVuidMse->absX = absX;
+		    absXset = TRUE;
+		}
                 break;
             case LOC_Y_DELTA:
-                dy -= delta;
+		if (!relToAbs)
+		    dy -= delta;
+		else {
+		    if (absYset)
+			vuidFlushAbsEvents(pInfo, absX, absY, &absXset, &absYset);
+		    absY -= delta;
+		    if (absY < 0)
+			absY = 0;
+		    else if (absY >= vdisplay && vdisplay > 0)
+			absY = vdisplay - 1;
+		    pVuidMse->absY = absY;
+		    absYset = TRUE;
+		}
                 break;
             case LOC_X_ABSOLUTE:
                 if (absXset) {
