AI 是否能完全替代码农的 1 点思考
2025-03-22 01:51:00
在最近使用 Copilot 和 Cursor 进行 Coding 的时候,偶尔会考虑现在的 AI 对于开发者到底意味着什么,我感觉其实 AI 带来的最大价值不是彻底取代开发人员,而是大幅提升了领域专家...
AI 生成 UI 设计的 Cursor 实践
2025-03-05 03:31:00
最近在探索 AI Coding in Front-End 的时候看到一篇较为🐂🍺的文章《一个提示词 claude 生成一个 app 的 ui/ux》(UC 震惊部提前预定作者入职)。虽然标题比较震惊,...
论如何打击 LLMs 过度的自信心爆棚,让其产生的内容更准确、真实
2025-02-22 02:03:00
本文为未经 AI 润色的原文如我之前的某次分享,我一直感觉 LLMs 们喜欢胡说八道满嘴放炮,平常大家都说这是「LLMs 的幻觉情况」,但我还是恶意的称呼为它们喜欢胡说八道。在《Does Fine-T...
关于 DeepSeek NSA 论文的一点思考
2025-02-19 01:09:00
今天看到 DeepSeek 团队前几日发布的论文《Native Sparse Attention: Hardware-Aligned and Natively Trainable Sparse Att...
Claude 4:混合型大模型的崭新思路与未来潜力
2025-02-15 02:21:00
今天在 Twitter(求马王爷还我 Twitter 原名!!)看到一个消息,来自一个推文大致提到:「Claude 4 in the coming weeks」,具体内容可以参考这条推文。重点是:Cl...
Deep Research:开源替代方案与未来发展潜力
2025-02-14 01:17:00
最近大家都在谈论 CloseAI 的 Deep Research 模式,称其研究效果非常强大,但面对高达 200???? 的价格,不少人却感到难以承受。幸运的是,开源社区也有不少项目尝试复现类似的效果...
观 OpenAI 广告有 1 点感
2025-02-12 03:19:00
本文为未经 AI 润色的原文今天看了 OpenAI 的广告,一开始只是觉得这个创意很有意思。视频采用了黑白点画风格,从一个小小的圆点开始,随着画面展开,逐渐呈现出越来越复杂的图像,展现了人类历史上各个...
关于 DeepSeek-R1 与 CoT 模型的提示词策略一点记录
2025-02-11 02:23:00
今天在公司和同事们开会,讨论到了 CoT(Chain of Thought)模型 和 通用模型 在提示词策略方面的差异,尤其是与 DeepSeek-R1 的训练过程有关的内容。此话题让我想起了之前阅读...
DeepSeek V3 与 AI 训练新思路:低成本硬件与技术突破
2025-02-09 01:29:00
今天在站会上和同事讨论了从一个 AI 应用团队(即 AI 的使用者)向 AI 全链路团队(即从模型训练到应用全程都参与)转型的可能性。这让我联想到了最近看到的一篇关于 DeepSeek V3 技术的访...
关于「AI 创始人的惨痛教训」系列文章的 1 点感想
2025-02-01 01:36:00
本文为未经 AI 润色的原文我个人一直不相信当前生成式 AI 的能力,认为 AI 总是会胡说八道。年前有一天中午和 Ricky 在食堂,达哥也在边上,我提到如何建立对 AI 在严肃工作领域的信任感。我...
关于「人工智能」分类下文章的说明
2025-02-01 00:43:00
在「人工智能」分类下,发布的所有文章其实来源于我工作中的一些学习笔记。为了将这些笔记更清晰、更易读地呈现给大家,我选择了使用 AI 对内容进行调整和润色。通过这种方式,文章的表达更为流畅,信息传达也更...
对 v2c 进行了一次前端的重构
2024-09-17 20:24:00
0x0自从 2019 年把博客迁移到 Typecho,再到 2020 年用 React 自己写了博客的前端进行了前后端分离后,我的博客前端就几乎没怎么动过了。期间其实也多次想开始重构,但总是因为工作忙...
如何让 uTools 通过代理服务器连接网络
2024-06-18 14:35:00
总的来说就是为 uTools 添加启动参数 --proxy-server 即可通过代理访问网络。备注:此方法只能代理掉 chromium 侧的流量,无法覆盖 uTools 本身非渲染进程的流量、插件 ...
关于这三年:我也是当过美食博主了
2024-05-26 22:15:00
是的,I am back!很久没有更新博客了,一方面是忙于工作无心更新(这是个借口),另一方面是自从 2021 年 8 月发生了丢失数据的问题,导致很多历史文章都消失在互联网长河中。虽然尽了很大的努力...
关于
FydeOS AI LogoFydeOS LogoAI
是如何诞生的
2023-12-06 22:01:00
0x0 为什么要做这个项目 FydeOS Logo AI 项目的初衷是为了让用户可以更加自然地控制操作系统,能够使用自然语言与系统进行交互。例如,通过语音或文本与系统对话,控制软件、查找信息,甚至快速解答工...
[家宴 · 2021]也许是今年最认真的一顿饭,红红火火锅
2021-12-31 23:33:00
在 2020 年,我曾经搞过几次家宴,邀请了一众好友来家里吃吃喝喝。甚至在 V 站加了不少好友,对他们说『下次家宴有空来家里一起吃』,但是事实上因为种种原因,2021 年非但没有邀请 V 友来家里吃饭...
【一场灾难】多站点数据丢失说明
2021-08-23 21:47:16
大概在一个多月前,包括 我的博客、LoveLive.tools(渣男:说话的艺术)、Mr.Task 等网站突然无法访问,服务器无法连接。本来以为只是服务提供商突发故障(之前也出现过,后来都正常恢复),...
[LoveTime] 一个与爱情和时间线相关的项目
2021-03-20 11:11:00
0x0 为什么做这个项目大概在一年前,我注册了 lovetime.tools 的域名。说来也是奇怪,我总是喜欢在脑子里冒出一个想法之后立刻注册相关的域名,但是往往实际完成上线的时间都会拖很久。比如 渣...
有目的 (di) 地 (de) 瞎折腾 —— 为了温暖的被窝而实现远程开机
2021-01-09 13:14:00
0x0这个冬天真的太 TM 的冷了,冷到我想一天 24 小时都呆在床上哪儿也不去。本来在这个美好的周六是可以实现这个同样美好的愿望,但是一大早同事来的电话击碎了我的梦想 —— 线上项目出了点问题需要排...
[家宴・2020] 入冬的第一次聚会,是带些许火辣的味道
2020-11-18 20:00:00
0x0是入冬的日子了,终于送走了盘踞在头上小两三个月的秋老虎。说来也是奇怪,在我记忆中大概七八年前,大概还是我上初中的时候,总是能精准的掐着日子算到什么时候要入冬了,左右不过是国庆过后五六天就可以翻出...
下一篇
弹出
关闭

[ T9 x 触控 ·0x0 ] 为便携触控式 Windows 设备打造 T9 输入法

为什么要做它

起因还是我习惯了半躺在床上抱着 Surface 聊天、刷网页,但是 Windows 10 自带的屏幕虚拟键盘只有全键盘模式,在单手输入的场景下对着 Surface 那 12.3 英寸的屏幕在虚拟键盘敲敲点点简直是场噩梦——输入效率低下,顺便还敲得手酸。当年刚买回来 Surface 的时候我试图在系统里找到切换虚拟键盘的模式的时候我才发现 Windows 10 竟然并没有 T9 键盘的支持,真是一件可怕的事情。而上网搜索良久,我惆怅地发现并没有一款专门针对便携触控式 Windows 设备且支持 T9 模式的输入法,所以这么多年也就忍下来了。

最近再此在某个特殊场景单手输入时我实在是无法忍受蹩脚的操作方式,于是为了可怜的手指我决定挖一个新坑,自己做一款支持 T9 模式的触控键盘(输入法)。

挖坑开始

准备工作

考虑一下要实现的功能:

  • 最基本的:一个按键显示足够大的 T9 键盘
  • 模式的切换:在中英文、数字、Emoji 表情输入之间进行切换
  • 辅助性的功能:暂时想到的是语音输入的支持和光标控制

那么要实现上述功能,要怎么做。

功能实现

既然是为 Windows 设备开发软件,从开发效率来讲我肯定选择 WPF 来开发,毕竟微软自家亲儿子而且还有宇宙第一级别的 IDE —— Visual Studio 的支持(再此强调:微软爸爸好)。虽然听起来用 WPF 来开发输入法很奇怪,而且也很少见,但是并不代表是不可能的。

实现功能需要涉及到一些底层的功能,目前想到的两个功能:

  • 输入法窗口永远置于顶层,并且不夺取触控时的焦点(不激活窗口)。如果输入法窗口被激活(系统层面的激活,而不是广义上的激活),那么我们要进行输入的窗口就会失去焦点,无法正常进行接下来的输入操作。

    • 实现

      可以通过调用 Win 32 API 来实现这个功能,先上代码:

    public MainWindow()
    {
        InitializeComponent();
        SourceInitialized += EventSourceInitialized;
        this.Topmost = true;
    }
    
    private void EventSourceInitialized(object sender, EventArgs e)
    {
        var handle = new WindowInteropHelper(this).Handle;
        SetWindowsProps(handle, -20, new IntPtr(GetWindowsProps(handle, -20).ToInt32() | 0x08000000));
    }
    
    public static IntPtr GetWindowsProps(IntPtr hWnd, int nIndex)
    {
        return Environment.Is64BitProcess ? GetWindowLongPtr(hWnd, nIndex) : GetWindowLong(hWnd, nIndex);
    }
    
    public static IntPtr SetWindowsProps(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
    {
        return Environment.Is64BitProcess ? SetWindowLongPtr(hWnd, nIndex, dwNewLong) : SetWindowLong(hWnd, nIndex, dwNewLong);
    }
    
    [DllImport("user32.dll", EntryPoint = "GetWindowLong")]
    private static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
    
    [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
    private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
    
    [DllImport("user32.dll", EntryPoint = "SetWindowLong")]
    private static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
    
    [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
    private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

    简单的来解释一下上述代码的功能:通过 Win 32 API 提供的两组 API(对应 32 位和 64 位系统),先获取当前窗口(输入法窗口)的扩展样式(Extend Style,对应代码中的常数“-20”),然后设置当前窗口不会变成活动窗口(WS_EX_NOACTIVATE,即不会获取焦点/被激活,对应代码中的常数“0x08000000”)。[*]

    将上述功能写成方法绑定在窗体的 SourceInitialized 事件上(此事件发生在 WPF 窗体资源加载完毕,此时就可以通过 WindowInteropHelper 来获取窗体句柄与 Win 32 API 交互),最后通过 this.Topmost = true 来设置窗体永远保持在最前端即可。

    [*] 具体的资料和信息可以参见 Win 32 API 文档

  • 把输入的内容发送给“被输入”的窗口(也就是获取焦点的窗口)

    • 实现

    其实本来我也是想通过调用 Win 32 API 来实现功能的,但是考虑了一下之后发现自己写这个功能涉及到很多底层的问题,就凭我这菜鸡水平的确很难完美的实现(其实就是给懒找借口)。

    当然,这难不倒机智的木頭(……),可以直接调用微软爸爸给我们封装好的方法:SendKeys.SendWait,这个方法位于 System.Windows.Forms 命名空间下。虽然在 WPF 中调用 WinForm 的方法显得格外的蠢,但是既然能实现功能还要什么自行车。

    核心代码如下:

    // 这里通过 Static Using 是为了防止 WPF 和 WinForm 一些重名类导致的问题
    using SendKeys = System.Windows.Forms.SendKeys;
    
    …… 省略其他代码
    
    private void SendInput(string inputStr) {
        SendKeys.SendWait(inputStr);
    }

    通过 SendWait 方法可以发送输入到焦点窗口的功能,如果需要发送特殊按键(比如常用的回车键、退格键)则只需要传递对应的键值即可。[*]

    [*] 特殊按键的键值可以参考 MSDN - SendKeys Class

上述两个功能实现后,再添加一些花里胡哨的花边功能以及进行界面的排版等等,一个输入法的雏形就诞生了(那些花边功能就不一一赘述了)。大概就是长这个样子:

GIF 2019-10-21 1-43-00.gif

↑ 可以输入、响应退格、回车、空格功能;不会夺取窗口焦点;永远置顶显示。

后续工作

截至目前,输入法大概的样子已经做出来了,但是只是实现了最简单的功能:响应触控、输出点击的 T9 键盘的数字等。接下来就是实现起来比较麻烦的功能了—— T9 词库算法,如何根据输入的“数字”(也就是按下的 T9 按键)来构造拼音、去词库匹配、常用词等等。目前还没有很好的想法,等功能实现并完善后我们在下一篇文章再见。