1. 控制台里拿到鼠标字符坐标到底难在哪如果你写过 Windows 控制台小游戏、终端菜单、字符画编辑器大概率会遇到同一个问题鼠标点下去我想知道它落在第几行第几列而不是像素坐标。windows.h里其实早就给齐了工具只是几个 API 拼起来容易踩坑比如坐标系原点不对、字体尺寸取错、窗口缩放后行列偏移。这篇就围绕「C 控制台鼠标字符坐标获取」这件事把GetCursorPos、ScreenToClient、GetCurrentConsoleFont三个函数串成一条能直接编译运行的链路输出鼠标当前所在的字符行列。同时给出一份config.toml骨架把 TaoToken 的统一 Key / API 通道写进去方便你后续在这个控制台程序里接 AI 能力比如让模型根据点击坐标生成内容、做终端里的智能补全。适合谁看会一点 C、用过windows.h、想在控制台里做交互点击、拾取、菜单的人以及想把 AI 调用统一收口到一个配置文件里的开发者。下面所有代码都在 Windows MinGW / MSVC 下实测可编译坐标读取部分不依赖任何第三方库。2. 前置准备TaoToken 统一 Key 与 API 通道在写坐标代码之前先把后面要用的 AI 通道配置好避免代码写完还要回头补。TaoToken 的作用是把多家模型的调用统一到一个 Key 和一套 API 地址上你不需要为每个模型单独记 base_url 和鉴权方式。官网入口在这里注册和查看文档都从这进https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基地址代码里填这个注意不带任何跟踪参数https://taotoken.net/api拿到 Key 的路径是登录后进控制台在 API Keys 页面创建一个新 Key。这个 Key 就是后面config.toml里api_key字段的值。相关页面模型对话 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite 控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite API Keys https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite 接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite注意Key 只创建一次就够多个工具共用同一个 Key靠config.toml里的model字段区分调用哪个模型。不要把 Key 硬编码进.cpp源码提交到仓库会泄露。如果你后面要做长期编码或 Agent 类工具可以看 Coding Plan 那条链接只是临时验证模型返回用模型对话页面更直接。接入细节以接入文档为准。3. 可复制配置坐标读取代码 config.toml 骨架3.1 三个核心 API 的分工先把原理讲清楚不然改代码时容易懵。GetCursorPos(p)拿到的是鼠标在屏幕上的像素坐标原点是整个屏幕左上角。这个坐标不能直接用来算字符行列因为控制台窗口可能不在屏幕左上角。ScreenToClient(GetForegroundWindow(), p)把屏幕像素坐标转换成「相对于当前前台窗口客户区」的像素坐标。转换后p.x、p.y就是鼠标在窗口内的像素位置原点变成窗口客户区左上角。GetCurrentConsoleFont(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, a)拿到当前控制台字体信息其中a.dwFontSize.X是单个字符宽度像素a.dwFontSize.Y是单个字符高度像素。用客户区像素坐标分别除以这两个值就得到字符行列。3.2 最小可运行坐标读取程序新建mouse_pos.cpp内容如下#include windows.h #include iostream using namespace std; int main() { POINT p; CONSOLE_FONT_INFO fontInfo; for (;;) { system(cls); // 1. 获取鼠标屏幕像素坐标 GetCursorPos(p); // 2. 转换为当前前台窗口客户区像素坐标 ScreenToClient(GetForegroundWindow(), p); // 3. 获取控制台字体尺寸 GetCurrentConsoleFont( GetStdHandle(STD_OUTPUT_HANDLE), FALSE, fontInfo ); // 4. 像素坐标 / 字符尺寸 字符行列 int col p.x / fontInfo.dwFontSize.X; int row p.y / fontInfo.dwFontSize.Y; cout 字符坐标 - 列: col 行: row endl; cout 像素坐标 - x: p.x y: p.y endl; Sleep(100); } return 0; }编译命令MinGW 为例g mouse_pos.cpp -o mouse_pos.exe -lgdi32 -luser32MSVC 下用cl mouse_pos.cpp user32.lib gdi32.lib运行后移动鼠标窗口里会实时刷新行列值。这里有个细节dwFontSize.X和dwFontSize.Y在部分终端下可能返回 0直接除会崩所以生产代码里要加保护见第 5 节排障。3.3 config.toml 骨架在项目根目录建config.toml把 TaoToken 通道写进去。字段设计成后续接 AI 工具时直接读# TaoToken 统一接入配置 [taotoken] base_url https://taotoken.net/api api_key sk-你的Key填这里 model claude-sonnet-4-20250514 timeout_seconds 60 # 控制台交互相关 [console] refresh_ms 100 font_width 8 font_height 16 enable_click truebase_url固定填https://taotoken.net/api不要加尾斜杠。api_key从 API Keys 页面复制。model按你实际要用的模型名填接入文档里有完整列表。[console]段是给坐标程序读的refresh_ms控制刷新频率font_width/height是兜底值当GetCurrentConsoleFont返回 0 时用它。读取这个配置的 C 片段不引第三方库简单解析#include fstream #include string #include map mapstring, string loadConfig(const string path) { mapstring, string cfg; ifstream fin(path); string line; while (getline(fin, line)) { // 跳过注释和空行 if (line.empty() || line[0] # || line[0] [) continue; auto pos line.find(); if (pos string::npos) continue; string key line.substr(0, pos); string val line.substr(pos 1); // 去掉首尾空格和引号 auto trim [](string s) { size_t a s.find_first_not_of( \t\); size_t b s.find_last_not_of( \t\); if (a string::npos) { s ; return; } s s.substr(a, b - a 1); }; trim(key); trim(val); cfg[key] val; } return cfg; }调用时auto cfg loadConfig(config.toml);然后cfg[base_url]、cfg[api_key]就能取到值。这样坐标程序和 AI 调用共用一份配置改 Key 只改一个文件。4. 验证请求编译运行与成功结果4.1 编译并运行坐标程序按 3.2 的命令编译双击或命令行运行mouse_pos.exe。窗口打开后把鼠标移到控制台窗口内不同位置观察输出。成功结果长这样字符坐标 - 列: 12 行: 5 像素坐标 - x: 96 y: 80 字符坐标 - 列: 30 行: 10 像素坐标 - x: 240 y: 160行列值随鼠标移动连续变化且鼠标停在同一个字符格内时行列不变说明换算正确。如果行列跳动很大或始终为 0看第 5 节。4.2 验证 TaoToken 通道是否通配置写好后用一条 curl 验证 Key 和 base_url 是否可用curl https://taotoken.net/api/v1/messages \ -H Content-Type: application/json \ -H x-api-key: sk-你的Key \ -H anthropic-version: 2023-06-01 \ -d { model: claude-sonnet-4-20250514, max_tokens: 64, messages: [{role:user,content:回复 OK 两个字母}] }返回里能看到content字段带文本就说明通道正常。这一步和坐标程序是独立的先确认通道通再把 AI 调用接进控制台逻辑排障时能快速定位是网络问题还是代码问题。4.3 把坐标和 AI 调用串起来一个典型用法鼠标点击控制台某个字符格把该格坐标发给模型让模型返回该位置应该显示的内容。核心逻辑if (GetAsyncKeyState(VK_LBUTTON) 0x8000) { int col p.x / fontInfo.dwFontSize.X; int row p.y / fontInfo.dwFontSize.Y; // 用 col/row 拼 prompt走 config.toml 里的 base_url api_key string prompt 坐标( to_string(col) , to_string(row) )处生成一个字符; // 调用 TaoToken 接口把返回字符画到该位置 }GetAsyncKeyState(VK_LBUTTON) 0x8000判断左键是否按下配合坐标换算就能做点击拾取。注意点击检测要放在循环里且和Sleep配合不然会重复触发。5. 本篇常见错排查5.1 行列始终为 0 或除零崩溃最常见原因是dwFontSize.X或dwFontSize.Y返回 0。某些终端、某些字体设置下GetCurrentConsoleFont拿不到有效尺寸。解决方式是加兜底int fw fontInfo.dwFontSize.X 0 ? fontInfo.dwFontSize.X : 8; int fh fontInfo.dwFontSize.Y 0 ? fontInfo.dwFontSize.Y : 16; int col p.x / fw; int row p.y / fh;8和16就是config.toml里font_width、font_height的默认值保持一致。5.2 坐标原点不对行列整体偏移ScreenToClient的第二个参数必须是「鼠标所在的那个窗口」的句柄。如果你用GetForegroundWindow()但鼠标其实在另一个窗口上换算就会偏。更稳的做法是用WindowFromPoint先拿到鼠标下的窗口POINT p; GetCursorPos(p); HWND hwnd WindowFromPoint(p); ScreenToClient(hwnd, p);这样无论鼠标在哪个窗口坐标都相对该窗口客户区符合「原点为当前操作窗口」的需求。5.3 窗口缩放 / DPI 导致行列不准高 DPI 屏上系统可能对窗口做缩放GetCursorPos返回的是物理像素而字体尺寸是逻辑像素两者不匹配。在main开头声明 DPI 感知SetProcessDPIAware();或者用清单文件声明dpiAware。加完之后行列换算会一致。如果不想动 DPI就在config.toml里把font_width/height按实际缩放比例调大。5.4 config.toml 读不到值检查三点文件是否和 exe 同目录相对路径基于工作目录不是源码目录api_key是否带了多余空格或引号解析函数已去引号但手写时容易多打空格base_url是否误加了尾斜杠。用cfg[base_url]打印出来确认。5.5 点击重复触发GetAsyncKeyState在按键持续按下时会一直返回按下状态导致一次点击触发多次。加一个状态变量记录上次是否已处理static bool lastPressed false; bool nowPressed (GetAsyncKeyState(VK_LBUTTON) 0x8000) ! 0; if (nowPressed !lastPressed) { // 只在按下的瞬间处理一次 } lastPressed nowPressed;6. 后续接入与配置收口坐标读取本身不复杂难的是把它和后续 AI 能力接起来时Key、base_url、模型名散落在各处。把 TaoToken 通道统一写进config.toml坐标程序、AI 调用、日志模块都读同一份配置改一处全生效。需要 Key 和通道信息时走这几个入口API Keys https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite 接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite 模型对话 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite排障和接入细节看接入文档验证模型返回看模型对话长期编码或 Agent 场景看 Coding Plan。坐标代码先跑通再把config.toml里的base_url和api_key填上用 4.2 的 curl 确认通道最后把点击逻辑和模型调用拼起来。整个过程不需要改坐标换算部分那三个 API 的组合是稳定的。