Last updated on

Macbook 下嘉立创 EDA 触控板手势修复


我也不知道为什么学院会给所有专业的人安排硬件实训,我一个人工智能方向的学生为什么要使用嘉立创画板子搞四足机器人呢?

Mac 版嘉立创 EDA 在触控板上的默认手感有点反直觉:双指上下滚动经常被当成放大缩小,而不是像在触摸屏上一样拖动画布。真正想要的效果其实很简单:

  • 双指上下滚动:拖动画布。
  • 双指左右滚动:保持原本的左右移动。
  • 双指捏合:正常放大缩小。

嘉立创 EDA 里虽然可以用 Ctrl + 滚轮 实现拖动画布,但放在触控板上就会变得很难用。每次都按着 Ctrl 再双指上下拖,实在不像一个正常 Mac 软件该有的操作方式。

问题

在 macOS 上,触控板的双指上下滚动会被嘉立创 EDA 当成滚轮缩放处理。也就是说:

  • 左右移动正常
  • 上下滑动变成了放大缩小
  • 双指捏合变成了上下滚动

嘉立创其实针对这个问题提出了解决方法,但是具体解决方法吗……如提。

官方解决方法

解决思路

Hammerspoon 拦截嘉立创 EDA 的输入事件,只在嘉立创 EDA 处于前台时生效:

  1. 捕获触控板双指上下滚动。
  2. 给它自动加上 Ctrl,让嘉立创 EDA 识别成拖动画布。
  3. 捕获触控板捏合手势。
  4. 把捏合手势伪造成普通滚轮事件,让嘉立创 EDA 继续执行放大缩小。
  5. 捏合过程中屏蔽触控板自己夹带的滚轮事件,避免又被误判成 Ctrl + 滚轮

最终效果是:双指上下拖就是拖板子,双指捏合才是缩放。

安装 Hammerspoon

如果已经安装过 Hammerspoon,可以跳过这一步。

brew install --cask hammerspoon

第一次打开 Hammerspoon 时,需要在 macOS 里给它辅助功能权限:

系统设置 -> 隐私与安全性 -> 辅助功能 -> 打开 Hammerspoon。

如果后面脚本没反应,也可以检查一下:

系统设置 -> 隐私与安全性 -> 输入监控 -> 打开 Hammerspoon。

完整配置

打开 Hammerspoon 菜单栏图标,选择 Open Config,把下面这段粘贴到 ~/.hammerspoon/init.lua 里。

如果之前已经写过旧版嘉立创相关脚本,建议先删掉旧代码,再完整粘贴这一份。

-- LCEDA / EasyEDA Mac trackpad gesture swap
-- 双指上下滚动 = Ctrl + 滚轮 = 拖动画布
-- 双指捏合 = 伪造触控板上下滚动 = 放大缩小

local eventtap = hs.eventtap
local event = eventtap.event
local types = event.types
local props = event.properties

local TRACKPAD_ONLY = true

-- 捏合缩放力度:太慢就调大,比如 48 / 64 / 80;太快就调小
local ZOOM_STEP = 5

-- 每次捏合事件发几个滚轮包:一般保持 1,太慢再改成 2
local ZOOM_BURST = 1

-- 方向反了就改成 -1
local ZOOM_DIRECTION = 1

local PINCH_BLOCK_SECONDS = 0.12
local SHOW_DEBUG = false

local MAGIC = 0x1CEDA2026
local pinchUntil = 0
local lastDebugAt = 0

local function now()
  return hs.timer.secondsSinceEpoch()
end

local function debug(msg)
  if not SHOW_DEBUG then return end
  local t = now()
  if t - lastDebugAt > 0.45 then
    lastDebugAt = t
    hs.alert.show(msg, 0.2)
  end
end

local function isLCEDA(app)
  if not app then return false end

  local name = app:name() or ""
  local bundleID = app:bundleID() or ""

  return name:find("嘉立创EDA", 1, true)
      or name:find("LCEDA", 1, true)
      or name:find("EasyEDA", 1, true)
      or bundleID:lower():find("lceda", 1, true)
      or bundleID:lower():find("easyeda", 1, true)
end

local function isTrackpadScroll(e)
  if not TRACKPAD_ONLY then return true end
  local continuous = e:getProperty(props.scrollWheelEventIsContinuous)
  return continuous ~= nil and continuous ~= 0
end

local function axis(e, pointProp, lineProp)
  local v = e:getProperty(pointProp)
  if v == nil or v == 0 then
    v = e:getProperty(lineProp)
  end
  return v or 0
end

local function isMagnify(e)
  local t = e:getType(true)
  return t == types.magnify or types[t] == "magnify"
end

if lcedaScrollFix then
  lcedaScrollFix:stop()
  lcedaScrollFix = nil
end

if lcedaPinchZoom then
  lcedaPinchZoom:stop()
  lcedaPinchZoom = nil
end

if lcedaPinchWatch then
  lcedaPinchWatch:stop()
  lcedaPinchWatch = nil
end

lcedaPinchZoom = eventtap.new({ types.gesture }, function(e)
  if not isLCEDA(hs.application.frontmostApplication()) then
    return false
  end

  if not isMagnify(e) then
    return false
  end

  local details = e:getTouchDetails() or {}
  local mag = details.magnification or 0
  if math.abs(mag) < 0.000001 then
    return true
  end

  pinchUntil = now() + PINCH_BLOCK_SECONDS
  debug("pinch")

  local sign = mag > 0 and 1 or -1
  local amount = sign * ZOOM_STEP * ZOOM_DIRECTION

  local out = {}
  for i = 1, ZOOM_BURST do
    local s = event.newScrollEvent({ 0, amount }, {}, "pixel")
    s:setProperty(props.eventSourceUserData, MAGIC)
    table.insert(out, s)
  end

  return true, out
end)

lcedaScrollFix = eventtap.new({ types.scrollWheel }, function(e)
  if not isLCEDA(hs.application.frontmostApplication()) then
    return false
  end

  -- 放行脚本自己造出来的缩放滚轮
  if e:getProperty(props.eventSourceUserData) == MAGIC then
    return false
  end

  local trackpad = isTrackpadScroll(e)

  -- 捏合时吞掉触控板夹带的原始滚轮,防止又被加 Ctrl
  if trackpad and now() < pinchUntil then
    return true
  end

  local flags = e:getFlags()
  if flags.ctrl or flags.shift or flags.alt or flags.cmd or flags.fn then
    return false
  end

  if not trackpad then
    return false
  end

  local dy = axis(e, props.scrollWheelEventPointDeltaAxis1, props.scrollWheelEventDeltaAxis1)
  local dx = axis(e, props.scrollWheelEventPointDeltaAxis2, props.scrollWheelEventDeltaAxis2)

  -- 左右双指滚动保持原样
  if math.abs(dy) <= math.abs(dx) then
    return false
  end

  -- 上下双指滚动改成 Ctrl + 滚轮,也就是拖动画布
  e:setFlags({ ctrl = true })
  return false
end)

lcedaPinchZoom:start()
lcedaScrollFix:start()

hs.alert.show("LCEDA trackpad swap loaded")

保存后点击 Hammerspoon 菜单栏图标,选择 Reload Config

调参

如果捏合缩放比例太大,调小这两行:

local ZOOM_STEP = 5
local ZOOM_BURST = 1

如果捏合缩放太慢,调大 ZOOM_STEP

local ZOOM_STEP = 48
local ZOOM_BURST = 1

如果缩放方向反了:

local ZOOM_DIRECTION = -1

如果想确认 Hammerspoon 有没有抓到捏合,可以临时打开调试提示:

local SHOW_DEBUG = true

捏合时如果右上角弹出 pinch,说明 Hammerspoon 已经捕获到捏合事件。

排错

如果 Ctrl + 滚动 被系统拿去做整屏缩放,需要关闭 macOS 的辅助功能缩放手势:

系统设置 -> 辅助功能 -> 缩放 -> 关闭“使用带修饰键的滚动手势来缩放”。

如果脚本完全没反应,优先检查:

  • Hammerspoon 是否已经 Reload Config
  • Hammerspoon 是否有辅助功能权限。
  • Hammerspoon 是否有输入监控权限。
  • 嘉立创 EDA 的应用名或 bundle ID 是否包含 嘉立创EDALCEDAEasyEDA

如果应用名不匹配,可以临时运行下面这段查看当前前台应用信息:

hs.alert.show(
  (hs.application.frontmostApplication():name() or "unknown")
  .. "\n"
  .. (hs.application.frontmostApplication():bundleID() or "unknown")
)

然后把输出的名称或 bundle ID 补进 isLCEDA 函数里。

最终效果

改完以后,嘉立创 EDA 在 Mac 触控板上的操作就接近正常画布软件了:

  • 双指上下滚动:拖动画布。
  • 双指左右滚动:左右移动。
  • 双指捏合:放大缩小。

这个方案不改嘉立创 EDA 本身,只在 Hammerspoon 层对输入事件做转换,所以也方便随时关闭或继续微调。