Silence 发布的文章

Delphi 好像是没有支持 toml 格式解析的单元,从 github 上找到了来自悉尼的 Iwan Kelaiah 写的一个 TOML Parser for Free Pascal 单元。简单修改了一下,让其支持 delphi。

下载:TOML.rar

以下为翻译的一些用法:

config.toml 示例:

# 注释:config.toml

revision = "1.2.1af"

[project]
name = "My Amazing Project"
version = "1.0.0"

基本用法

  • 读取 TOML 文件
    program BasicParseTOML;
    uses
      TOML;    
    var
      Config: TTOMLTable;
      RevisionValue, ProjectValue, ProjectName: TTOMLValue;
      ProjectTable: TTOMLTable;    
    begin
      // 打开 TOML 文件
      Config := ParseTOMLFromFile('config.toml');
      try
        // 访问字符串类型数据
        if (Config.TryGetValue('revision', RevisionValue)) then
          WriteLn('''revision'' 的值为:', RevisionValue.AsString);   
        // 安全地访问数据
        if Config.TryGetValue('project', ProjectValue) and
          (ProjectValue is TTOMLTable) then
        begin
          ProjectTable := TTOMLTable(ProjectValue);
          if ProjectTable.TryGetValue('name', ProjectName) then
            WriteLn('Project Name: ', ProjectName.AsString)
          else
            WriteLn('Project name 未找到。');
        end
        else
          WriteLn('Project 配置未找到。');
      finally
        Config.Free;
      end;
    end.
  • 写入 TOML 文件
    program BasicSerializeTOML;
    uses
      TOML;
    var
      Config: TTOMLTable;
      Database: TTOMLTable;
    begin
      Config := TOMLTable;
      try
        Database := TOMLTable;
        // 添加数据
        Database.Add('host', TOMLString('localhost'));
        Database.Add('port', TOMLInteger(5432));
        // 修改数据
        Database.Items.AddOrSetValue('enable',TOMLBoolean(True));
        Config.Add('database', Database);    
        if SerializeTOMLToFile(Config, 'config.toml') then
          WriteLn('配置保存成功。')
        else
          WriteLn('保存配置出错。');
      finally
        Config.Free;
      end;
    end.

常见用法

  • 使用数组
    var
      Config: TTOMLTable;
      Tags: TTOMLArray;
    begin
      Config := TOMLTable;
      try
        Tags := TOMLArray;
        Tags.Add(TOMLString('pascal'));
        Tags.Add(TOMLString('toml'));
        Config.Add('tags', Tags);    
        WriteLn(SerializeTOML(Config));
      finally
        Config.Free;
      end;
    end.
  • 嵌套表
    var
      Config: TTOMLTable;
      Database: TTOMLTable;
    begin
      Config := TOMLTable;
      try
        Database := TOMLTable;
        Database.Add('host', TOMLString('localhost'));
        Database.Add('port', TOMLInteger(5432));
        Config.Add('database', Database);
        WriteLn(SerializeTOML(Config));
      finally
        Config.Free;
      end;
    end.
  • 序列化复杂结构
    program BasicSerializeTOML;
    uses
      TOML, SysUtils;
    var
      Config, ServerConfig: TTOMLTable;
      Ports: TTOMLArray;
      SerializedTOML: string;
    begin
      Config := TOMLTable;
      try
        // 创建嵌套表
        ServerConfig := TOMLTable;
        ServerConfig.Add('host', TOMLString('127.0.0.1'));
        ServerConfig.Add('enabled', TOMLBoolean(True));
        // 创建和填充数组
        Ports := TOMLArray;
        Ports.Add(TOMLInteger(80));
        Ports.Add(TOMLInteger(443));
        ServerConfig.Add('ports', Ports);    
        // 添加 server 配置到主配置
        Config.Add('server', ServerConfig);    
        // 添加一些基础元数据
        Config.Add('version', TOMLFloat(1.0));
        Config.Add('last_updated', TOMLDateTime(Now));    
        // 序列化为 TOML 格式
        SerializedTOML := SerializeTOML(Config);
        WriteLn('生成 TOML:');
        WriteLn(SerializedTOML);    
        // 保存文件
        if SerializeTOMLToFile(Config, 'config.toml') then
          WriteLn('保存成功。');    
      finally
        Config.Free;
      end;
    end.

上述代码将生成 TOML 文件,内容如下:

    version = 1.0
    last_updated = 2024-03-20T15:30:45Z
    [server]
    host = "127.0.0.1"
    enabled = true
    ports = [ 80, 443 ]

说明:所有数据都经过适当的类型检查和内存管理。该单元可确保:

  • 每个值都有正确的 TOML 类型
  • 数组保持类型一致性
  • 所有对象都已正确释放
  • 验证类型转换
  • 内存管理

注意:

  • 仅释放顶级 owner 表以避免内存管理问题。
  • 适当的内存管理对于防止应用程序中的内存泄漏至关重要。

创建/释放表:

    var
      Config: TTOMLTable;
      Database: TTOMLTable;
    begin
      Config := TOMLTable;
      Database := TOMLTable;
      //使用 Add 方法将嵌套表或值插入父表中。
      Config.Add('database', Database);
      // ... 添加其它嵌套表及数据...
      Config.Free; // 仅释放顶级所有者即可自动释放所有嵌套的表和值。
    end.

避免显式释放嵌套对象:
• 不要手动释放嵌套表或值,以防止出现内存管理问题。

API 参考

类型

  • TTOMLValue - 所有 TOML 值的基本类型
  • TTOMLTable - TOML 表
  • TTOMLArray - TOML 数组
  • TTOMLString - TOML 字符串类型
  • TTOMLInteger - TOML 整数类型
  • TTOMLFloat - TOML 浮点类型
  • TTOMLBoolean - TOML 布尔类型
  • TTOMLDateTime - TOML 日期时间类型

用于创建 TOML 值的辅助函数

创建 TOML 字符串值。

    TOMLString
      function TOMLString(const AValue: string): TTOMLString;

创建 TOML 整数值。

    TOMLInteger
      function TOMLInteger(const AValue: Int64): TTOMLInteger;

创建 TOML 浮点值。

    TOMLFloat
      function TOMLFloat(const AValue: Double): TTOMLFloat;

创建 TOML 布尔值。

    TOMLBoolean
        function TOMLBoolean(const AValue: Boolean): TTOMLBoolean;

创建 TOML 时间日期格式。

    TOMLDateTime
        function TOMLDateTime(const AValue: TDateTime): TTOMLDateTime;

创建 TOML 数组。

    TOMLArray
      function TOMLArray: TTOMLArray;

创建 TOML 表。

    TOMLTable
      function TOMLTable: TTOMLTable;

解析函数

  • ParseTOML
    将 TOML 格式的字符串解析为 TTOMLTable 对象。
  function ParseTOML(const ATOML: string): TTOMLTable;
  begin
    Result := TOML.Parser.ParseTOMLString(ATOML);
  end;

• ParseTOMLFromFile
将 TOML 文件解析为 TTOMLTable 对象。

      function ParseTOMLFromFile(const AFileName: string): TTOMLTable;
      var
        FileStream: TFileStream;
        StringStream: TStringStream;
      begin
        FileStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
        try
          StringStream := TStringStream.Create('');
          try
            StringStream.CopyFrom(FileStream, 0);
            Result := ParseTOMLString(StringStream.DataString);
          finally
            StringStream.Free;
          end;
        finally
          FileStream.Free;
        end;
      end;

序列化函数

  • SerializeTOML
    将 TTOMLValue 序列化为 TOML 格式的字符串。

      function SerializeTOML(const AValue: TTOMLValue): string;
      begin
        Result := TOML.Serializer.SerializeTOML(AValue);
      end;

• SerializeTOMLToFile

序列化 TTOMLValue 并将其保存为文件。

      function SerializeTOMLToFile(const AValue: TTOMLValue; const AFileName: string): Boolean;
      begin
        Result := TOML.Serializer.SerializeTOMLToFile(AValue, AFileName);
      end;

Windows 11 更新到 24H2 版本后,很多用户都出现了鼠标光标在一些应用上光标消失的问题,如 chrome 浏览器、QQ NT 登录窗口、WPS 等。现在流传比较广的一个解决方案是通过控制面板/鼠标属性/指针设置“文本选择”的光标为 beam_r.cur,经测试只能部分解决问题,在鼠标光标经过窗口里的某些地方时,光标仍然会短暂地消失(透明)。
另一个比较好用的方案是:打开设置/屏幕/颜色管理,将“自动管理应用的颜色”选项关掉,效果立杆见影。
color.png

Windows 11 锁屏界面上默认是设置了显示“天气等”小部件,一同显示的还有路况、体育和财经。如果你不想看体育或财经等内容,设置里并没有选项可以定制。
屏幕截图 2024-12-11 135930.png

其实这个定制功能还是有的,用 EDGE 浏览器打开如下地址 https://www.msn.com/zh-cn/feed/personalize/settings,从资讯源里的信息卡设置里关掉体育或财经即可。
屏幕截图 2024-12-11 141232.png

公司又半死不活地坚持了一年,技术岗裁员裁到只剩我自己。终于在六月份的最后一天收到了解除劳动合同协议书,虽然早就知道这是注定的结局,但事到临头,心底一片茫然,不知该何去何从。
年近半百、只懂点技术、不通人情世故的老 IT 人,难道真的没有活路了吗?
万千思绪,竟无人可诉。

                 键盘映射工具 KeybMap V2.2 64位版本

KeybMap.png
功能:通过修改注册表来进行键盘键的重新定义。
文件大小:4.54M
适用系统:Windows VISTA/7/8/10/11 64位版本
界面:简体中文/英文

说明:本键盘映射工具仅修改注册表,不驻留内存。通过本工具,你可以将键盘上一些不常用的键定义为其它功能或者将其禁用,如将 ScrollLock 定义为静音、Wake Up 定义为减小音量、Sleep 定义为增大音量等等。
注意:
1、Power、WWW Home、Mute 等部分多媒体键(非 104 标准键)的映射只对 PS/2 键盘有效。若使用 USB 键盘则无法将其映射为其它键,除非使用 USB -> PS/2 转换器。
2、Pause 和 Fn 键不允许映射,程序已将其排除在外。
3、本版本只支持 64 位操作系统,使用时需要管理员权限。
4、32位版本的 KeybMap 在 64 位系统上可以运行,但映射功能无效,故不再提供。
更新:
2.2 版本修订了导入配置后界面不显示导入数据的 bug。
2.1 版本完善了导入导出流程,去除了当前用户的键盘映射功能(Windows7 之后操作系统不支持。但多媒体键重新定义动作是支持当前用户的,且优先级高于所有用户)。有人说看着键定义列表里有加号而没有等号感觉别扭,本来是写代码时为了绕过一个可能会出现的问题而故意这么做的,现在我还是改回来吧。
2.0 版本使用 Delphi 重写了一遍,优化了大量代码,修正了一处键名错误,补充了在非中文系统中遗漏的几处翻译。增加了几个键定义,屏蔽了 Windows 已经废弃的 RegisteredApp 功能。
1.9 版本修订了网友提出的在按键捕捉时的 bug,另外优化了一些相关流程。
1.8 版本暂时只发布了 64 位版本,因改用 Lazarus 编译,文件大小增加很多。高级功能里将当前用户和全局用户的定义分开排列,修正了类型为 Shellexecute 时指定的程序包含路径时导出为注册表文件时的一个问题。
1.7.3 版修正自定义键值无法保存问题,修正列表中动作状态刷新问题。
1.7.2 版增加对映射键适用范围的选择(当前用户/所有用户选择);允许自行输入键值进行映射;改进导入功能的兼容性。
1.7.1 版修正导入功能的一些问题。
1.7 版增加捕捉键值功能,可以识别大部分键盘的非标准扩展键位(注意:如果 PS/2 键盘使用了 USB 转换器,一些非标准扩展键位将会失效)。去掉程序的重启功能,改由用户自行操作。增加了键值显示。修正一个子窗口显示字体过大问题。
1.6 版恢复导入导出配置功能。
1.5 版增加对一些多媒体键定义修改功能。暂时屏蔽导入导出功能。
1.4 版增强对 VISTA 的兼容性,支持 Windows 7。
1.3 版修订由 1.2 版引起的键无法正确禁用问题。
1.2 版增补遗漏的 Z 键。
1.1 版修正多余两个键名问题。

                  Silence

下载:keybmap.rar

钉钉的机器人群消息很好用,也不用申请特别的权限,免费的消息条数也很多,做个监控类的消息通知很合适。一般都是用 python、JAVA 等语言来编写调用代码,简单使用也可以直接命令行调 curl。
这几天想给一个用 delphi 编写的运维工具加上钉钉消息功能。尝试编写了一下,用自定义关键字方式发送消息很简单,一次就通过了;然而加签方式却死活通不过,总是返回加签错误。仔细阅读了N遍官方文档,就是个很常用的 HMAC-SHA256 + Base64 加签算法。蹊跷的是,我换了三四种不同的代码去实现,每一种算法得到的结果和网上的在线计算器的结果都一模一样,然而就是和官方的 Python 语言例程的结果不一样。
晚饭后出门散步时我继续思考这个问题:既然我的代码和在线计算器的一致,说明算法本身没有错误,那么只能是输入参数不一致;这时我突然领悟到,Python 和 JAVA 语言都会自动处理 \n 这样的转义字符串而 Delphi 不会,官方文档里要求在加签字符串中添加了一个 \n,肯定就是这里导致的错误。后来在代码中用 #10 来代替 \n,果然验签通过。
那么就分享一段完整的用 delphi 发送钉钉机器人消息的代码吧:

unit main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  System.JSON, Vcl.StdCtrls, Hash, System.Net.URLClient, DateUtils,
  System.Net.HttpClient, System.Net.HttpClientComponent, NetEncoding;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    NetHTTPClient1: TNetHTTPClient;
    function dingtalk(content: string): boolean;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
  dingtalk('test,测试。[关键字]');
end;

function TForm1.dingtalk(content: string): boolean;
//钉钉机器人消息发送函数
//URL 格式:https://oapi.dingtalk.com/robot/send?access_token=XXXXXX&timestamp=XXX&sign=XXX
var
  url, keywords, token, secret, timestamp, sign, str: string;
  s: TStringStream;
  json: TJSONObject;
begin
  //result := false;
  begin
    NetHTTPClient1.ContentType := 'application/json';
    NetHTTPClient1.AcceptCharSet := 'utf-8';
    NetHTTPClient1.AcceptLanguage := 'zh-CN';
    token := 'xxxxxxxx';
    keywords := '';
    secret := 'SECxxxxxxxx';
    url := 'https://oapi.dingtalk.com/robot/send?access_token=' + token;
    (*
    发送消息有两种安全策略,自定义关键字和加签,至少选择一种,也可同时使用。
    secret 为加签密钥,不为空时表示需要加签。
    timestamp 为毫秒级时间戳。
    加签算法是常用的 HmacSHA256 + Base64:
    把 timestamp+"\n"+secret 当做签名字符串,使用HmacSHA256算法计算签名,
    然后进行 Base64 编码,将得到的结果再进行 urlEncode 编码,从而得到最终签名数据 sign。
    *)
    if secret <> '' then
    begin
      //获取毫秒级时间戳
      timestamp := (MilliSecondsBetween(Now, EncodeDateTime(1970, 1, 1, 0, 0, 0, 0)) - 8 * 60 * 60 * 1000).ToString;
      //加签
      sign := TNetEncoding.Base64.EncodeBytesToString(THashSHA2.GetHMACAsBytes(timestamp + #10 + secret, secret, SHA256));
      //URLEncode 编码
      sign := TNetEncoding.url.Encode(sign);
      //拼接 url
      url := url + '&timestamp=' + timestamp + '&sign=' + sign;
    end;
    //keywords 为自定义关键字,若为空时则必须加签,即 secret 不能为空。
    if keywords <> '' then
      keywords := keywords + '\n';
    //拼接 json 格式的消息内容字符串,如有自定义关键字,可插入到内容的任意位置。
    s := TStringStream.Create('{"msgtype":"text","text": {"content":"' + keywords + content + '"}}', TEncoding.UTF8);
    //发送 post 请求
    try
      str := NetHTTPClient1.Post(url, s).ContentAsString(nil);
    except
      on E: Exception do
        memo1.Lines.Add('发送消息时出错:' + e.Message);
    end;
    //解析返回结果
    try
      json := TJSONObject.ParseJSONValue(str, true, true) as TJSONObject;
      if json.GetValue<integer>('errcode') = 0 then
      begin
        result := true;
        memo1.Lines.Add('消息发送成功。');
      end
      else
      begin
        result := false;
        memo1.Lines.Add('消息发送失败,错误代码:' + json.GetValue<string>('errcode') + json.GetValue<string>('errmsg'));
      end;
      s.Free;
      json.Free;
    except
      on E: Exception do
      begin
        result := false;
        memo1.Lines.Add('发送消息时出错:' + e.Message);
      end;
    end;
  end;
end;

end.

大概是从 Windows 7 时代开始,很多声卡的立体声混音功能就消失了,据说是厂家迫于某些组织的压力为了维护音乐版权不得不屏蔽了这个功能。
既然是屏蔽,那么大概率是可以重新打开的。下面以 Thinkpad 某老款笔记本(Windows 10)自带的 Conexant 20671 声卡为例,讲一下处理方法:

  1. 运行注册表编辑器 regedit.exe,找到如下分支:

    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4d36e96c-e325-11ce-bfc1-08002be10318}
  2. 在找到的分支下会有 0000、0001……等子分支,对应着各个音频设备。使用“Conexant”或“20671”关键字搜索到要修改的声卡分支。
  3. 假设上一步找到的分支是 0000,将如下内容保存为 conexant.reg 文件。如果你找到的分支不是 0000,自行修改内容。

    Windows Registry Editor Version 5.00
    
    [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4d36e96c-e325-11ce-bfc1-08002be10318}\0000\Settings\EPattributes\EpSettings\StereoMixEnable]
    "MixAssocSeq"=hex:e0,e0
    "MuteGainSettings"=hex:00,00
    "Enable"=hex:01
  4. 双击 conexant.reg 文件导入注册表,然后重启操作系统即可。

注:此方法只适用于 conexant 声卡的部分型号,其它型号的声卡可能设置方法有所不同。

AlmaLinux 在安装完成重启后,在图形界面下会出现无法关闭的向导,强制让你创建用户,否则无法进入系统。有时候弄个测试用的系统,只用一个 root 账户就可以了,所以要想办法把它关掉。
注意:因为这个向导是在系统登录前出现的,以前的那种在用户的 .config 目录下创建一个内容为“yes”的 gnome-initial-setup-done 文件的做法不会生效。
用 root 账户 ssh 登录系统后:

  • 方法一:
    第一步:
    vim /etc/gdm/custom.conf
    在 [daemon] 小节下增加一行:
    InitialSetupEnable=false
    如果只做这一步,重启后可以登录系统,但仍会出现向导。
    第二步:
    vim /etc/xdg/autostart/gnome-initial-setup-first-login.desktop
    增加一行:
    X-GNOME-Autostart-enabled=false
    重启系统即可。
  • 方法二:
    直接卸载 gnome-initial-setup:
    dnf erase gnome-initial-setup