现在大多数的笔记本电脑都是用 Synaptics 的触摸板了,所以找这个公司的驱动妥妥的。接下来的内容是拿 Synaptics 的 SDK v1.0 做的一个小程序,C++ MFC 的(坑啊~)。功能嘛……将笔记本触摸板变成一块画图区域。虽然效果很挫,不过凑合还是可以用的。

下载和源码

程序下载

(基于这一个版本的代码,使用VC6.0编译)

源代码:https://github.com/laobubu/SynapticsPen

记得程序打开后手动点击“启动”,否则不会独占你的触摸板。

呃,你居然点开了这个文章,好吧,笔记如下:

不得不有的变量

ISynAPI *pAPI = NULL;
ISynDevice *pDev = NULL;

装载

HRESULT hRes = CoInitialize(0);
if (hRes && hRes != S_FALSE)
return false; // Error other than already initialized on thread.
long lHandle = -1;
if (CoCreateInstance(_uuidof(SynAPI), 0,
CLSCTX_INPROC_SERVER, _uuidof(ISynAPI), (void **) &pAPI) ||
pAPI->Initialize() ||
pAPI->FindDevice(SE_ConnectionAny, SE_DeviceTouchPad, &lHandle) ||
pAPI->CreateDevice(lHandle, &pDev))
{
AfxMessageBox(IDS_CANNOT_LOADAPI,16);
return false; // Couldn't initialize properly.
}

设置Listener

好吧这个叫法貌似是Java里的,不过无所谓了,知道意思即可。

将class继承 _ISynDeviceEventspublic _ISynDeviceEvents ),

还有加入方法 HRESULT STDMETHODCALLTYPE OnSynDevicePacket(long lSequence);

然后就可以 pDev->SetSynchronousNotification(this);

读取点的信息

HRESULT STDMETHODCALLTYPE 你的类::OnSynDevicePacket(long lSequence) {
pDev->LoadPacket(m_Packet);
printf("p %d\n",m_Packet.ZRaw());
return 0;
}

注意这里的 m_Packet 是另外声明的 SynPacket 类型的对象。

SynPacket类的方法

SynPacket把 ISynPacket 里面诸多蛋疼的东西搞得好看了一些,以下为官方文档:

最基本的方法(继承自ISynPacket)

  • GetLongProperty(属性名, *容器)
  • Copy(*src)

爽到飞起的方法

返回值 名字 备注
long AssociatedDeviceHandle()
long SequenceNumber()
long TimeStamp()
long W() 触摸物体的Width,胖手指用户要注意哈。
long X()
long Y()
long Z()
long XRaw()
long YRaw()
long ZRaw()
long ZXRaw() 貌似是计算Z值时使用的坐标。
long ZYRaw()
long XDelta() 和前一个Packet相差的距离。
long YDelta()
long ZDelta()
long XMickeys()
long YMickeys()
long ExtendedState() Synaptics内部使用的,没资料。
long FingerState() 参考 SynFingerFlags Enumeration
long ButtonState() 参考 SynButtonFlags Enumeration

包很多,给包一点缓冲

实测时候会发现哪怕轻轻一摸,数据包的数量也是超大的,所以可以考虑搞一个缓冲空间,就像官方example里给的这个PacketBuffer类:

class PacketBuffer
{
public:
enum numbers { eSize = 128 };  

PacketBuffer(void)
{
m_uiCurrent = 0;
m_iUnread = 0;
}  

int NextLocation(void)
{
m_iUnread += m_iUnread == eSize ? 0 : 1;
return m_uiCurrent++ % eSize;
}  

void Add(PacketData &Packet)
{
m_Packets[NextLocation()] = Packet;
}  

PacketData *Read(void)
{
if (m_iUnread)
return m_Packets + ((m_uiCurrent - m_iUnread--) % eSize);
else
return 0;
}  

int Unread(void) { return m_iUnread; }  

PacketData m_Packets[eSize];
int m_iUnread;
unsigned int m_uiCurrent;
};

关于坐标的事情

这个坐标建系得就像某些游戏SDK(还有数学课本)一样,以左下角为原点,直角坐标系。而且原点貌似不是 (0,0),在我的电脑上是(1024,1024),具体可能和设备相关。

解决方法是使用前面的pDev来获取 xMin xMax。

pDev->GetProperty(SP_XLoSensor, &m_XMin);
pDev->GetProperty(SP_XHiSensor, &m_XMax);
pDev->GetProperty(SP_YLoSensor, &m_YMin);
pDev->GetProperty(SP_YHiSensor, &m_YMax);

旋转触摸板

就像旋转屏幕一样,会使你感觉很纠结。。。此代码为官方示例代码:

    long lVerticalFlags = 0;
    long lHorizontalFlags = 0;
    m_pDevice->GetProperty(SP_VerticalScrollingFlags, &lVerticalFlags);
    m_pDevice->GetProperty(SP_HorizontalScrollingFlags, &lHorizontalFlags);  

    // adjust the scrolling direction flags based on rotation angle
    switch(Angle) {
    case 0:
      lVerticalFlags &= ~SF_ScrollingReversed;
      lHorizontalFlags &= ~SF_ScrollingReversed;
      lVerticalFlags &= ~SF_ScrollingAxisSwapped;
      lHorizontalFlags &= ~SF_ScrollingAxisSwapped;
      break;
    case 90:
      // Right means up, left means down, 
      // up means left, and down means right.
      lVerticalFlags &= ~SF_ScrollingReversed;
      lHorizontalFlags |= SF_ScrollingReversed;
      lVerticalFlags |= SF_ScrollingAxisSwapped;
      lHorizontalFlags |= SF_ScrollingAxisSwapped;
      break;
    case 180:
      lVerticalFlags |= SF_ScrollingReversed;
      lHorizontalFlags |= SF_ScrollingReversed;
      lVerticalFlags &= ~SF_ScrollingAxisSwapped;
      lHorizontalFlags &= ~SF_ScrollingAxisSwapped;
      break;
    case 270:
      // Right means down, left means up, 
      // up means right, and down means left.
      lVerticalFlags |= SF_ScrollingReversed;
      lHorizontalFlags &= ~SF_ScrollingReversed;
      lVerticalFlags |= SF_ScrollingAxisSwapped;
      lHorizontalFlags |= SF_ScrollingAxisSwapped;
      break;
    }  

    // Set the new scrolling directions
    m_pDevice->SetProperty(SP_VerticalScrollingFlags, lVerticalFlags);
    m_pDevice->SetProperty(SP_HorizontalScrollingFlags, lHorizontalFlags);  

    // Set the new motion angle.
    m_pDevice->SetProperty(SP_MotionRotationAngle, Angle);  

    // Make new settings persistent
    m_pAPI->PersistState(SF_PersistMachine);

对触摸板的其他操作

禁止触摸板

pDev->SetProperty(SP_DisableState, 1);

这个会导致包括自身在内不能获取触摸板的点击的信息

独占触摸板

pDev->Acquire(0);

执行此代码后目前程序仍然可以获取点击的信息,但是其他程序无法获取,包括操作系统。

至于为什么参数是0,这个是规定,貌似目前还没有解释。

pDev->Unacquire();

释放触摸板,其他程序可以使用之。

最后工作

CoUninitialize();

据说这样好,如果你没有搞其他的COM东西的话……

此外还有就是如果你屏蔽了触摸板,记得恢复哈~

附:显示MFC里printf的输出

MFC 可以 printf 但是它不会自动打开输出窗口,这里有一段代码很好用的,不妨试试。

#ifdef _DEBUG
#include <io.h>
#include <fcntl.h>
void OpenConsole()
{
	AllocConsole();
	HANDLE handle   =   GetStdHandle(STD_OUTPUT_HANDLE);
	int hCrt   =   _open_osfhandle((long)handle,_O_TEXT);
	FILE   *   hf   =   _fdopen(   hCrt,   "w"   );
	*stdout   =   *hf;
}
#endif

再在主函数里加入

#ifdef _DEBUG
OpenConsole();
#endif

其他事情

估计有人好奇为什么 Windows 7 了还使用老掉牙的 VC6,这里稍微说一下吧……

  • 本来要用 VC 2012 之类的高端版,但是据说默认编译出来的程序不支持 XP 等老系统
  • 然后转战 VC 2010 ,但是不知道为什么打死安装不上……
  • 然后是同学推荐的 C-Free 。这个嘛……MINGW32杀手,而且各种运行不了的奇葩问题。
  • 要点来啦!课本都是 VC6 的说~
  • 已准备 Code::Blocks 为备胎。

还有就是MFC,用起来感觉差不多啊(好吧貌似现在只有这个选择),虽然每一次面对MFC都有一种要撞墙的冲动。还有就是为什么MFC里方法第一个字母都是大写?!接受不了啊。

此外就是这个程序了。打算近期购入一个数位板烧钱玩玩,但是屌丝心态还是叫我仍然在这里写奇怪的东西YY。 最后证明,笔记本触摸板真的不能代替数位板,但是拿它来对付 OSU 里面的转盘,妥妥的!