nameit: 通用化设备名水印(AHK v2),支持配置/开机自启/README

This commit is contained in:
Cheney
2026-10-29 16:06:53 +08:00
commit a970c6db8c
4 changed files with 498 additions and 0 deletions
+307
View File
@@ -0,0 +1,307 @@
; ============================================================
; nameit —— 屏幕角落设备名水印(AutoHotkey v2)
;
; 功能:在屏幕指定角落显示设备名/任意文字
; · 字高按像素精确控制
; · 白色字身 + 黑色描边,深浅背景都清晰
; · 始终置顶,鼠标完全穿透
; 依赖:仅需 AutoHotkey v2(无需其他组件)
; 配置:同目录 nameit.ini,托盘菜单可打开/重载
; 用法:见 README.md 或运行 nameit.ahk --help
; ============================================================
#Requires AutoHotkey v2.0
#SingleInstance Force
Persistent
APP_NAME := "nameit"
INI := A_ScriptDir "\nameit.ini"
LNK := A_Startup "\" APP_NAME ".lnk"
; ---------------------- 命令行 ----------------------
if (A_Args.Length > 0) {
cmd := A_Args[1]
if (cmd = "--install" || cmd = "-i") {
ok := EnsureStartup()
MsgBox(ok ? "已设置开机自启:`n" LNK : "设置开机自启失败,请检查权限。", APP_NAME, ok ? "Iconi" : "Icon!")
} else if (cmd = "--uninstall" || cmd = "-u") {
RemoveStartup()
MsgBox("已取消开机自启。", APP_NAME, "Iconi")
ExitApp
} else if (cmd = "--help" || cmd = "-h" || cmd = "/?") {
MsgBox(UsageText(), APP_NAME, "Iconi")
ExitApp
}
}
; ---------------------- 读取配置 ----------------------
if !FileExist(INI)
try FileAppend(IniTemplate(), INI, "UTF-8")
CFG_TEXT := Cfg("general", "Text", A_ComputerName)
CASE_MODE := Cfg("general", "Case", "as-is")
FONT_FACE := Cfg("font", "Face", "")
FONT_PX := Integer(Cfg("font", "SizePx", "50"))
FONT_WT := Integer(Cfg("font", "Weight", "700"))
COL_TEXT := Cfg("color", "Text", "FFFFFF")
COL_STROKE := Cfg("color", "Stroke", "000000")
STROKE_W := Integer(Cfg("color", "StrokeWidth", "4"))
CORNER := Cfg("layout", "Corner", "bottom-left")
MONITOR := Integer(Cfg("layout", "Monitor", "1"))
MARGIN_X := Integer(Cfg("layout", "MarginX", "24"))
MARGIN_Y := Integer(Cfg("layout", "MarginY", "18"))
if (CASE_MODE = "upper")
CFG_TEXT := StrUpper(CFG_TEXT)
else if (CASE_MODE = "lower")
CFG_TEXT := StrLower(CFG_TEXT)
if (FONT_FACE = "")
FONT_FACE := RegExMatch(CFG_TEXT, "[\x{4E00}-\x{9FFF}]") ? "Microsoft YaHei UI" : "Segoe UI"
if (FONT_PX < 1)
FONT_PX := 50
if (STROKE_W < 0)
STROKE_W := 0
; 透明色键:抠掉这个颜色即"看不见背景";确保不与文字/描边同色
KEY_COLOR := "0A0A0A"
if (COL_TEXT = KEY_COLOR || COL_STROKE = KEY_COLOR)
KEY_COLOR := "010203"
; ---------------------- 创建窗口 ----------------------
hFont := CreateFontPx(FONT_PX, FONT_WT, FONT_FACE)
sz := MeasureText(hFont, CFG_TEXT)
; WS_EX_TRANSPARENT(0x20) 鼠标穿透 | WS_EX_LAYERED(0x80000) 色键透明 | WS_EX_NOACTIVATE(0x08000000) 不抢焦点
g := Gui("+AlwaysOnTop -Caption +ToolWindow +E0x20 +E0x80000 +E0x08000000", APP_NAME "Overlay")
g.BackColor := KEY_COLOR
g.MarginX := 0
g.MarginY := 0
padL := STROKE_W + 2
ctlW := sz.w + 4
ctlH := sz.h + 4
; 1) 黑色描边层:绕字形外圈放多个同色文字控件(BackgroundTrans 使它们互不遮挡)
if (STROKE_W > 0) {
g.SetFont("c" COL_STROKE, FONT_FACE)
for off in RingOffsets(STROKE_W) {
lbl := g.Add("Text", "x" padL + off.x " y" padL + off.y " w" ctlW " h" ctlH " BackgroundTrans", CFG_TEXT)
DllCall("SendMessageW", "Ptr", lbl.Hwnd, "UInt", 0x0030, "Ptr", hFont, "Ptr", 1) ; WM_SETFONT
}
}
; 2) 字身层(最后添加 = 位于最上层)
g.SetFont("c" COL_TEXT, FONT_FACE)
main := g.Add("Text", "x" padL " y" padL " w" ctlW " h" ctlH " BackgroundTrans", CFG_TEXT)
DllCall("SendMessageW", "Ptr", main.Hwnd, "UInt", 0x0030, "Ptr", hFont, "Ptr", 1) ; WM_SETFONT
W := ctlW + 2 * padL
H := ctlH + 2 * padL
g.Show("NoActivate w" W " h" H)
WinSetAlwaysOnTop(1, "ahk_id " g.Hwnd)
DllCall("SetLayeredWindowAttributes", "Ptr", g.Hwnd, "UInt", BGR(KEY_COLOR), "UInt", 255, "UInt", 1)
PlaceOverlay()
SetTimer(PlaceOverlay, 2000) ; 分辨率/任务栏/显示器变化时自动重新定位
; ---------------------- 托盘菜单 ----------------------
A_IconTip := "nameit:" CFG_TEXT
BuildMenu()
; ======================================================
; 函数
; ======================================================
Cfg(section, key, def) {
global INI
v := ""
try v := IniRead(INI, section, key, "")
return (v = "" ? def : v)
}
CreateFontPx(pixelHeight, weight, face) {
return DllCall("CreateFontW"
, "Int", -pixelHeight ; 负值 = 字身高度(像素)
, "Int", 0, "Int", 0, "Int", 0
, "Int", weight
, "UInt", 0, "UInt", 0, "UInt", 0 ; 斜体 / 下划线 / 删除线
, "UInt", 1 ; DEFAULT_CHARSET
, "UInt", 0, "UInt", 0
, "UInt", 4 ; ANTIALIASED_QUALITY
, "UInt", 0
, "WStr", face, "Ptr")
}
MeasureText(hFont, text) {
hdc := DllCall("GetDC", "Ptr", 0, "Ptr")
old := DllCall("SelectObject", "Ptr", hdc, "Ptr", hFont, "Ptr")
size := Buffer(8, 0)
DllCall("GetTextExtentPoint32W", "Ptr", hdc, "WStr", text, "Int", StrLen(text), "Ptr", size)
DllCall("SelectObject", "Ptr", hdc, "Ptr", old)
DllCall("ReleaseDC", "Ptr", 0, "Ptr", hdc)
return { w: NumGet(size, 0, "Int"), h: NumGet(size, 4, "Int") }
}
; 半径 radius 的一圈整数像素偏移(去重)
RingOffsets(radius) {
offsets := []
seen := Map()
steps := 64
Loop steps {
a := (A_Index - 1) * (360 / steps) * 3.14159265358979 / 180
dx := Round(Cos(a) * radius)
dy := Round(Sin(a) * radius)
if (!dx && !dy)
continue
k := dx "," dy
if seen.Has(k)
continue
seen[k] := 1
offsets.Push({ x: dx, y: dy })
}
return offsets
}
BGR(c) => ((Integer("0x" c) & 0xFF) << 16) | (Integer("0x" c) & 0xFF00) | ((Integer("0x" c) >> 16) & 0xFF)
PlaceOverlay() {
global g, CORNER, MONITOR, MARGIN_X, MARGIN_Y
if !g
return
n := MONITOR
if (n < 1 || n > MonitorGetCount())
n := 1
MonitorGetWorkArea(n, &mL, &mT, &mR, &mB)
g.GetPos(, , &w, &h)
vert := "bottom", horiz := "left"
parts := StrSplit(CORNER, "-")
if (parts.Length >= 2) {
vert := parts[1]
horiz := parts[2]
}
if (horiz = "left")
x := mL + MARGIN_X
else if (horiz = "right")
x := mR - w - MARGIN_X
else
x := mL + (mR - mL - w) // 2
if (vert = "top")
y := mT + MARGIN_Y
else if (vert = "middle")
y := mT + (mB - mT - h) // 2
else
y := mB - h - MARGIN_Y
g.Move(x, y)
}
ToggleOverlay() {
global g
if DllCall("IsWindowVisible", "Ptr", g.Hwnd)
g.Hide()
else
g.Show("NoActivate")
}
BuildMenu() {
global INI, LNK
A_TrayMenu.Delete()
A_TrayMenu.Add("显示 / 隐藏", (*) => ToggleOverlay())
A_TrayMenu.Add("重新载入配置", (*) => Reload())
A_TrayMenu.Add("打开配置文件", (*) => Run('"' INI '"'))
A_TrayMenu.Add()
A_TrayMenu.Add("开机自启", MenuToggleStartup)
A_TrayMenu.Add()
A_TrayMenu.Add("退出", (*) => ExitApp())
if FileExist(LNK)
A_TrayMenu.Check("开机自启")
}
MenuToggleStartup(ItemName, ItemPos, MyMenu) {
global LNK
if FileExist(LNK) {
RemoveStartup()
MyMenu.Uncheck(ItemName)
} else {
EnsureStartup()
if FileExist(LNK)
MyMenu.Check(ItemName)
}
}
EnsureStartup() {
global APP_NAME, LNK
if FileExist(LNK)
return true
try {
sh := ComObject("WScript.Shell")
sc := sh.CreateShortcut(LNK)
sc.TargetPath := A_AhkPath
sc.Arguments := '"' A_ScriptFullPath '"'
sc.WorkingDirectory := A_ScriptDir
sc.IconLocation := A_AhkPath ",0"
sc.Description := "nameit - 屏幕角落设备名水印"
sc.Save()
} catch {
return false
}
return FileExist(LNK) ? true : false
}
RemoveStartup() {
global LNK
if FileExist(LNK)
try FileDelete LNK
}
UsageText() {
return "nameit - 屏幕角落设备名水印`n`n"
. "用法:`n"
. " 双击 nameit.ahk 启动水印`n"
. " nameit.ahk --install 设置开机自启(并启动)`n"
. " nameit.ahk --uninstall 取消开机自启`n"
. " nameit.ahk --help 显示本帮助`n`n"
. "配置:编辑同目录 nameit.ini,托盘右键菜单可打开/重新载入。"
}
IniTemplate() {
return "; ============================================================" "`n"
. "; nameit 配置文件(UTF-8)" "`n"
. "; 修改后:托盘图标右键 -> 重新载入配置(或重启脚本)" "`n"
. "; ============================================================" "`n"
. "" "`n"
. "[general]" "`n"
. "; 显示的文字;留空 = 自动使用本机电脑名" "`n"
. "Text=" "`n"
. "; 大小写:as-is 原样 / upper 全大写 / lower 全小写" "`n"
. "Case=as-is" "`n"
. "" "`n"
. "[font]" "`n"
. "; 字体名;留空 = 自动(纯英文用 Segoe UI,含中文用 Microsoft YaHei UI)" "`n"
. "Face=" "`n"
. "; 字高(像素)" "`n"
. "SizePx=50" "`n"
. "; 字重:700 粗体 / 400 常规" "`n"
. "Weight=700" "`n"
. "" "`n"
. "[color]" "`n"
. "; 字身颜色(RRGGBB)" "`n"
. "Text=FFFFFF" "`n"
. "; 描边颜色(RRGGBB)" "`n"
. "Stroke=000000" "`n"
. "; 描边粗细(像素),0 = 不描边" "`n"
. "StrokeWidth=4" "`n"
. "" "`n"
. "[layout]" "`n"
. "; 位置: top / middle / bottom 配 left / center / right" "`n"
. "; 例如 bottom-left、top-right、middle-center" "`n"
. "Corner=bottom-left" "`n"
. "; 显示器编号:1 = 主显示器,2 = 第二块…" "`n"
. "Monitor=1" "`n"
. "; 到屏幕边缘的间距(像素)" "`n"
. "MarginX=24" "`n"
. "MarginY=18" "`n"
}