Stroke 开源鼠标手势软件

软件名称

Stroke

应用平台

  • Windows

推荐类型

【开发者自荐】

一句简介

一款轻量、无界面的 Windows 鼠标手势引擎,通过绘制轨迹触发自定义操作。

应用简介

  • 开源免费,基于 MIT 许可证发布。
  • 采用基于距离的向量匹配算法,能够快速准确地识别任意连续轨迹。
  • 手势动作完全由 C# 脚本定义,编译时可直接调用 .NET Framework 及自定义 DLL,扩展性极强。
  • 支持上下文感知匹配:不同应用下可自动启用对应的手势动作包。
  • 纯引擎设计,无后台界面,与配置工具解耦,配置完成后配置工具可删除。
  • 提供 StrokePlugins 插件库,包含翻译、OCR 等现成插件,也支持自行编写。

官方网站 && 应用商店地址

5 个赞

用了下,感觉思路是 好的,但是,现实的路子 走窄了。。。

  • 自定义的东西,全要靠自己编译 DLL ,这个就受众面太小了
    • 以我测试需要自己用 DLL 解决的 包括:新增手势,新增操作对象,新增对象动作等,感觉想要适配自己用,就必须要 编译DLL。。。

除了,不方便外,感觉目前程序可能有点 bug

  • win7 x64 , 对于 explorer 的操作有很多不能实现,比如 新建文件夹 新建窗口 ,已经给了管理员权限,还是不行

感觉如果要自己写DLL,应该至少给个教程示例;或者更简单 把库函数 扩充,弄成 脚本形式 也行。。。感觉目前 门槛太高 耗费的精力和得到的回报 不平衡。。。

注: 目前本人 用的是 MouseInc


@Qingwa 更新:MouseInc 有很多的杀毒软件报毒,请自行、谨慎考虑是否使用。

2 个赞

几乎所有的 .NET Framework 有的东西你都是可以使用的,Windows 7 打开新窗口和新建文件夹的快捷键不是“Ctrl+n”和“Ctrl+Shift+n”吗?如果是的话那就应该是可以的。需要注意的地方是,手势的起点位置下面的窗口是你需要操作的窗口,这个窗口需要是资源管理器才能响应这两个快捷键。用户可能会有各种各样的需求,所以我只写了一个最常用的 DLL,也就是那个 Base,同时它也是一个示例。

另外,刚想到的是,Windows 7 上资源管理器的路径是否包含“explorer”,如果不能够被这条模式串匹配的话,动作包里面的动作就不会被触发,需要改成合适的模式串。你可以使用“添加路径”按钮查看资源管理器路径的正则表达式。

1 个赞

今天刚好有机会在 Windows 7 上测试了一下,原来是 GetModuleFileNameEx 这个函数不兼容 Windows 7 导致的,这样就不能获取到正确的进程相应的可执行文件路径,因此正则表达式也不能匹配成功模式串(比如“explorer”)。现在换成了 QueryFullProcessImageName,应该能很好的支持 Windows 7 以上的操作系统(XP 被抛弃了)。

1 个赞

2020-09-14 修复了点击右键在某些情况下不能弹出右键菜单的问题。

没截图吗?

1 个赞

门槛太高啦

就是写 C# 代码当脚本呀,C# 还算是比较流行的语言嘛。最常用的脚本已经在默认的配置文件里面了,Base 库里面也有一些最常用的函数可以调。
普通用户基本够用,会 C# 的程序员用得会比较爽,可以体验一下,响应速度能感觉到比 Strokesplus 更快一些,因为脚本是在启动时编译的,启动速度也更快,占用内存也更小喔。

2020-09-23:
修正了编译脚本时的一个小 Bug。
Base.PressKeys(string keys),更改了转义符的方向(例如:/t:Tab 键)。
Base库新增了两个函数:
Base.IsKeyDown(Keys key):某个键是否按下。
Base.IsKeyToggled(Keys key):某键是否开启(例如:CapsLock)。

可自由引用程序集和命名空间,默认配置文件提供的命名空间有:“System”, “System.Collections”, “System.Collections.Concurrent”, “System.Collections.Generic”, “System.Collections.ObjectModel”, “System.Collections.Specialized”, “System.Diagnostics”, “System.Drawing”, “System.Drawing.Imaging”, “System.Drawing.Text”, “System.IO”, “System.IO.Compression”, “System.IO.Pipes”, “System.IO.Ports”, “System.Linq”, “System.Linq.Expressions”, “System.Media”, “System.Net”, “System.Net.Http”, “System.Net.Http.Headers”, “System.Net.Security”, “System.Net.Sockets”, “System.Reflection”, “System.Security”, “System.Security.Authentication”, “System.Security.Cryptography”, “System.Text”, “System.Text.RegularExpressions”, “System.Threading”, “System.Threading.Tasks”, “System.Timers”, “System.Web”, “System.Windows.Forms”, “System.Xml”, “System.Xml.Linq”, “System.Xml.Schema”, “System.Xml.Serialization”。

这里附赠一个百度翻译的脚本:

Base.Activate();
Base.PressKeys("(c)"); 
Thread.Sleep(4);
SynchronizationContext context = SynchronizationContext.Current;
string original = Clipboard.GetText().Replace("-\r\n", "").Replace("\r\n", " ");
(new Thread(new ThreadStart(() =>
{
    string q = original;
    string from = "en";
    string to = "zh";
    string appId = "";
    string salt = (new Random()).Next(100000).ToString();
    string secretKey = "";
    StringBuilder sb = new StringBuilder();
    foreach (byte b in MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(appId + q + salt + secretKey)))
    {
        sb.Append(b.ToString("x2"));
    }
    string sign = sb.ToString();
    string url = "http://api.fanyi.baidu.com/api/trans/vip/translate?";
    url += "q=" + HttpUtility.UrlEncode(q);
    url += "&from=" + from;
    url += "&to=" + to;
    url += "&appid=" + appId;
    url += "&salt=" + salt;
    url += "&sign=" + sign;
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    request.Method = "GET";
    request.ContentType = "text/html;charset=UTF-8";
    request.UserAgent = null;
    request.Timeout = 6000;
    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
    Stream responseStream = response.GetResponseStream();
    StreamReader streamReader = new StreamReader(responseStream, Encoding.GetEncoding("utf-8"));
    string result = streamReader.ReadToEnd();
    streamReader.Close();
    responseStream.Close();
    string translation = "";
    foreach (Match item in Regex.Matches(Regex.Match(result, "trans_result\":\\[.*\\]").Value, "{\"src\":\".*?\",\"dst\":\".*?\"}"))
    {
        translation += Regex.Unescape(Regex.Match(item.Value, "\"dst\":\".*\"").Value.Replace("\"dst\":", "")).Trim('\"');
    }
    if (translation != "")
    {
        context.Post((text) =>
        {
            Form form = new Form();
            form.FormBorderStyle = FormBorderStyle.None;
            form.StartPosition = FormStartPosition.Manual;
            form.Location = Cursor.Position;
            form.ControlBox = false;
            form.ShowIcon = false;
            form.ShowInTaskbar = false;
            form.BackColor = Color.White;
            form.Opacity = 0.96D;
            Label label = new Label();
            label.Text = (string)text;
            label.Font = new Font("宋体", 10.1f);
            label.MaximumSize = new Size(400, 1080);
            label.AutoSize = true;
            label.Location = new Point(4, 4);
            label.TextAlign = ContentAlignment.MiddleLeft;
            form.Controls.Add(label);
            form.MinimumSize = new Size(label.Width + 8, label.Height + 8);
            form.MaximumSize = form.MinimumSize;
            form.Size = form.MinimumSize;
            bool MoveWindow = false;
            int MoveX = Control.MousePosition.X;
            int MoveY = Control.MousePosition.Y;
            label.MouseDown += ((object sender, MouseEventArgs e) =>
            {
                if (e.Button == MouseButtons.Left)
                {
                    MoveX = e.X;
                    MoveY = e.Y;
                    MoveWindow = true;
                }
                if (e.Button == MouseButtons.Right)
                {
                    form.Close();
                }
            });
            label.MouseMove += ((object sender, MouseEventArgs e) =>
            {
                if (MoveWindow == true)
                {
                    form.Location = new Point(Control.MousePosition.X - MoveX, Control.MousePosition.Y - MoveY);
                    return;
                }
            });
            label.MouseUp += ((object sender, MouseEventArgs e) =>
            {
                if (e.Button == MouseButtons.Left && MoveWindow == true)
                {
                    MoveWindow = false;
                }
            });
            label.MouseDoubleClick += ((object sender, MouseEventArgs e) =>
            {
                Clipboard.SetText(label.Text);
            });
            form.Show();
            form.TopMost = true;
        }, translation);
    }
}))).Start();

请前往 https://api.fanyi.baidu.com/api/trans/product/desktop?req=developer ,申请并获取以下两个值。
appId(APP ID)、secretKey(密钥) 。

请问运行之后,在哪里设置?

c#写插件对大多数人而言还是太困难了,毕竟程序员只是少数,用一些简单的脚本语言做扩展可能受众会更广。当然最广泛的受众还是那种一行代码都不会写的,最好你把所有的功能都提供好,只需要在设置里选择就行了。

不过每个人对手势软件的需求都不一样,考虑到每个方面是很困难的,比较现实的方式是提供一个公共的插件共享平台。当然以上讨论只是从如何推广这个软件的角度出发,如果作者本来只是以完成一个相对小众的工具为目标,自然也没必要在这些方面下太多的工夫。

运行时是没有设置的,运行前使用“Stroke.Configure.exe”进行设置。

是的,默认的配置文件中提供了最常用的功能了。如果有更高的需求,主要受众还是程序员。

2020-09-24

新增:在“配置”中鼠标中键点击手势画布可清除手势。
修改:调整了“配置”中手势图形的显示大小。
修复:在“配置”中未选中任何手势时修改手势引发的异常。

另外,忘了说明,插入或删除动作包、动作、手势都是对列表中已有手势点右键进行操作的。

截图:

v2-f055c40047166948f1223d382260fac7_hd

v2-27a1d112b2e985f6db4fd73b7ef97ec2_hd

不过,复杂点的功能还是编译成 dll 比较好:

链接: https://pan.baidu.com/s/1RCBpbFf5Kz00DXa0cJGzGw 提取码: 45cp
(2020-09-30:使用 AddClipboardFormatListener 替换了 SetClipboardViewer 来解决长时间使用后可能由于未能处理好剪贴板消息导致翻译功能失效的问题。)
以上是百度翻译的 dll 版本,使用时放在和 Stroke.exe 同一目录下,使用以下脚本可实现以上的功能,同样需要填写你的“APP ID”和“密钥”:

BaiduTranslation.AppId = "";
BaiduTranslation.SecretKey = "";
BaiduTranslation.AwaitClipboardToTranslate();
Base.Activate();
Base.PressKeys("(c)");

鼠标左键可拖动译文,鼠标右键关闭,鼠标中键复制译文。
翻译

你还可以使用 AwaitClipboardToDisplay() 直接将选中的文字不翻译直接展示,功能就类似于一个文字剪贴板。

mouselnc这软件那么多引擎报毒,我可是不敢用.作者竟然明目张胆说自己没时间解决报毒问题,说信它就用.

MouseInc 的报毒还是惨了点: 31/ 71,无论开发者如何强调误报,有接近半数的杀毒软件报毒,真的就不能甩锅给杀毒软件了。

我 也曾经 怀疑过,,,但是 监控 24h ,它也没 明显 收集信息,也没联网,我也就用了。。。反正目前也没事。。。


主要是 目前 它最综合。。。没有一个能替代全部功能的

比如什么功能呢?
那个截图吗?交给更专业的截图软件做不是更爽吗?画一个框框即不快捷也不准确呀。如果要功能全面的截图软件推荐试试 snipaste,想要一个小巧的可以试试我的 Screenote,如果希望鼠标触发截图,用手势映射一个快捷键就是。
在四个角触发相应的功能,这里有个开源的 WinXcorners,而且实现起来不是挺简单嘛,勾一下鼠标,如果在四个角的范围就执行事件绑定的功能。
如果需要功能全面的鼠标手势,推荐一下 strokesplus。Stroke 来说分两种情况,如果你会写 C#,那么你可以实现几乎任何鼠标手势可以有的功能,如果你不会,Base 库确实很少的功能(以后估计也不会增加太多功能),因为它的设计初衷就是提供鼠标手势可能需要的最基本的功能。Stroke 的目标也不是做一个像 strokesplus 那样内置了各种功能的软件,而是一个小巧(配置好之后,你甚至可以把 Stroke.Configure.exe 删了)而反应灵敏的软件。
而你前面说的“新增手势,新增操作对象,新增对象动作等”这些功能真的是需要在运行时动态调整的刚需吗?会经常使用到吗?使用 Stroke.Configure.exe 来配置不是更简单吗?如果一定要实现这样的功能也不是不可以,相当于自己再实现一个“Stroke.Configure.exe”咯,然后执行你设定的手势调出配置的窗体。或者你直接执行“Stroke.Configure.exe”意思也差不多吧? :sweat_smile:

那么 Stroke 反应有多灵敏呢?从鼠标弹起,开始判别是否在任何动作包中触发了相应的动作,到执行手势完成,我们来看看花了多少时间(13 年买的 i5 笔记本)。
我们在以下两个位置插入记录时间的代码:
2020-10-02-12-26-44


测试 5 轮结果如下,Ticks 是以 100 纳秒(1 ns = 10^(-9) s)为单位计数的:
Start:637372381902555779
End:637372381902595758
Start:637372381947865912
End:637372381947905872
Start:637372381966984207
End:637372381967024144
Start:637372381983986626
End:637372381984046605
Start:637372382038631758
End:637372382038661736
即 分别得到 39979、39960、39937、59979、29978,即大概是 4毫秒、4毫秒、4毫秒、6毫秒、3毫秒。(1秒 = 1000 毫秒)。
取决于你的使用滚轮的手速和火狐的反应,你可以:
演示