diff --git a/assets/PixPin_2024-01-25_08-45-54.png b/assets/PixPin_2024-01-25_08-45-54.png new file mode 100644 index 00000000..dd603a7c Binary files /dev/null and b/assets/PixPin_2024-01-25_08-45-54.png differ diff --git a/assets/client-icon.ico b/assets/client-icon.ico new file mode 100644 index 00000000..ea9c7207 Binary files /dev/null and b/assets/client-icon.ico differ diff --git a/assets/server-icon.ico b/assets/server-icon.ico new file mode 100644 index 00000000..e514b54e Binary files /dev/null and b/assets/server-icon.ico differ diff --git a/assets/start_server_and_client_in_tray.gif b/assets/start_server_and_client_in_tray.gif new file mode 100644 index 00000000..4d6f9795 Binary files /dev/null and b/assets/start_server_and_client_in_tray.gif differ diff --git a/assets/start_server_or_client_in_tray.gif b/assets/start_server_or_client_in_tray.gif new file mode 100644 index 00000000..6afdcc90 Binary files /dev/null and b/assets/start_server_or_client_in_tray.gif differ diff --git a/hint_while_recording.ahk b/hint_while_recording.ahk new file mode 100644 index 00000000..b98446ee --- /dev/null +++ b/hint_while_recording.ahk @@ -0,0 +1,93 @@ +#Requires AutoHotkey v2.0 +CoordMode("ToolTip", "Screen") + + +~CapsLock::{ + if hwnd := GetCaretPosEx(&x, &y, &w, &h){ + ; 能够获取到文本光标时,提示信息在光标位置,且x坐标向右偏移5 + x := x + 5 + } + else{ + ; 获取不到文本光标时,提示信息在当前窗口的位置 + WinGetPos &X, &Y, &W, &H, "A" + x := X + W * 0.25 + y := Y + H * 0.7 + } + ToolTip("✦语音输入中‧‧‧", x, y) ; 提示信息内容 + KeyWait("CapsLock") + ToolTip() + return +} + + +GetCaretPosEx(&x?, &y?, &w?, &h?) { + x := h := w := h := 0 + static iUIAutomation := 0, hOleacc := 0, IID_IAccessible, guiThreadInfo, _ := init() + if !iUIAutomation || ComCall(8, iUIAutomation, "ptr*", eleFocus := ComValue(13, 0), "int") || !eleFocus.Ptr + goto useAccLocation + if !ComCall(16, eleFocus, "int", 10002, "ptr*", valuePattern := ComValue(13, 0), "int") && valuePattern.Ptr + if !ComCall(5, valuePattern, "int*", &isReadOnly := 0) && isReadOnly + return 0 + useAccLocation: + ; use IAccessible::accLocation + hwndFocus := DllCall("GetGUIThreadInfo", "uint", DllCall("GetWindowThreadProcessId", "ptr", WinExist("A"), "ptr", 0, "uint"), "ptr", guiThreadInfo) && NumGet(guiThreadInfo, A_PtrSize == 8 ? 16 : 12, "ptr") || WinExist() + if hOleacc && !DllCall("Oleacc\AccessibleObjectFromWindow", "ptr", hwndFocus, "uint", 0xFFFFFFF8, "ptr", IID_IAccessible, "ptr*", accCaret := ComValue(13, 0), "int") && accCaret.Ptr { + NumPut("ushort", 3, varChild := Buffer(24, 0)) + if !ComCall(22, accCaret, "int*", &x := 0, "int*", &y := 0, "int*", &w := 0, "int*", &h := 0, "ptr", varChild, "int") + return hwndFocus + } + if iUIAutomation && eleFocus { + ; use IUIAutomationTextPattern2::GetCaretRange + if ComCall(16, eleFocus, "int", 10024, "ptr*", textPattern2 := ComValue(13, 0), "int") || !textPattern2.Ptr + goto useGetSelection + if ComCall(10, textPattern2, "int*", &isActive := 0, "ptr*", caretTextRange := ComValue(13, 0), "int") || !caretTextRange.Ptr || !isActive + goto useGetSelection + if !ComCall(10, caretTextRange, "ptr*", &rects := 0, "int") && rects && (rects := ComValue(0x2005, rects, 1)).MaxIndex() >= 3 { + x := rects[0], y := rects[1], w := rects[2], h := rects[3] + return hwndFocus + } + useGetSelection: + ; use IUIAutomationTextPattern::GetSelection + if textPattern2.Ptr + textPattern := textPattern2 + else if ComCall(16, eleFocus, "int", 10014, "ptr*", textPattern := ComValue(13, 0), "int") || !textPattern.Ptr + goto useGUITHREADINFO + if ComCall(5, textPattern, "ptr*", selectionRangeArray := ComValue(13, 0), "int") || !selectionRangeArray.Ptr + goto useGUITHREADINFO + if ComCall(3, selectionRangeArray, "int*", &length := 0, "int") || length <= 0 + goto useGUITHREADINFO + if ComCall(4, selectionRangeArray, "int", 0, "ptr*", selectionRange := ComValue(13, 0), "int") || !selectionRange.Ptr + goto useGUITHREADINFO + if ComCall(10, selectionRange, "ptr*", &rects := 0, "int") || !rects + goto useGUITHREADINFO + rects := ComValue(0x2005, rects, 1) + if rects.MaxIndex() < 3 { + if ComCall(6, selectionRange, "int", 0, "int") || ComCall(10, selectionRange, "ptr*", &rects := 0, "int") || !rects + goto useGUITHREADINFO + rects := ComValue(0x2005, rects, 1) + if rects.MaxIndex() < 3 + goto useGUITHREADINFO + } + x := rects[0], y := rects[1], w := rects[2], h := rects[3] + return hwndFocus + } + useGUITHREADINFO: + if hwndCaret := NumGet(guiThreadInfo, A_PtrSize == 8 ? 48 : 28, "ptr") { + if DllCall("GetWindowRect", "ptr", hwndCaret, "ptr", clientRect := Buffer(16)) { + w := NumGet(guiThreadInfo, 64, "int") - NumGet(guiThreadInfo, 56, "int") + h := NumGet(guiThreadInfo, 68, "int") - NumGet(guiThreadInfo, 60, "int") + DllCall("ClientToScreen", "ptr", hwndCaret, "ptr", guiThreadInfo.Ptr + 56) + x := NumGet(guiThreadInfo, 56, "int") + y := NumGet(guiThreadInfo, 60, "int") + return hwndCaret + } + } + return 0 + static init() { + try + iUIAutomation := ComObject("{E22AD333-B25F-460C-83D0-0581107395C9}", "{30CBE57D-D9D0-452A-AB13-7AC5AC4825EE}") + hOleacc := DllCall("LoadLibraryW", "str", "Oleacc.dll", "ptr") + NumPut("int64", 0x11CF3C3D618736E0, "int64", 0x719B3800AA000C81, IID_IAccessible := Buffer(16)) + guiThreadInfo := Buffer(A_PtrSize == 8 ? 72 : 48), NumPut("uint", guiThreadInfo.Size, guiThreadInfo) + } +} diff --git a/hint_while_recording.exe b/hint_while_recording.exe new file mode 100644 index 00000000..d4279a5b Binary files /dev/null and b/hint_while_recording.exe differ diff --git a/python_modules.txt b/python_modules.txt new file mode 100644 index 00000000..73fa66be --- /dev/null +++ b/python_modules.txt @@ -0,0 +1,33 @@ +archspec @ file:///croot/archspec_1697725767277/work +boltons @ file:///C:/ci_311/boltons_1677729932371/work +Brotli @ file:///C:/ci_311/brotli-split_1676435766766/work +certifi @ file:///C:/b/abs_91u83siphd/croot/certifi_1700501720658/work/certifi +cffi @ file:///C:/b/abs_924gv1kxzj/croot/cffi_1700254355075/work +charset-normalizer @ file:///tmp/build/80754af9/charset-normalizer_1630003229654/work +colorama @ file:///C:/ci_311/colorama_1676422310965/work +conda @ file:///C:/b/abs_88drkd35d4/croot/conda_1701719561256/work +conda-content-trust @ file:///C:/b/abs_e3bcpyv7sw/croot/conda-content-trust_1693490654398/work +conda-libmamba-solver @ file:///croot/conda-libmamba-solver_1702997573971/work/src +conda-package-handling @ file:///C:/b/abs_b9wp3lr1gn/croot/conda-package-handling_1691008700066/work +conda_package_streaming @ file:///C:/b/abs_6c28n38aaj/croot/conda-package-streaming_1690988019210/work +cryptography @ file:///C:/b/abs_e8cnom_zw_/croot/cryptography_1702071486468/work +distro @ file:///C:/b/abs_a3uni_yez3/croot/distro_1701455052240/work +idna @ file:///C:/ci_311/idna_1676424932545/work +jsonpatch @ file:///tmp/build/80754af9/jsonpatch_1615747632069/work +jsonpointer==2.1 +libmambapy @ file:///C:/b/abs_efpsdwt4ya/croot/mamba-split_1698782663578/work/libmambapy +menuinst @ file:///C:/b/abs_e8p75b4m9q/croot/menuinst_1702390332729/work +packaging @ file:///C:/b/abs_28t5mcoltc/croot/packaging_1693575224052/work +platformdirs @ file:///C:/b/abs_b6z_yqw_ii/croot/platformdirs_1692205479426/work +pluggy @ file:///C:/ci_311/pluggy_1676422178143/work +pycosat @ file:///C:/b/abs_31zywn1be3/croot/pycosat_1696537126223/work +pycparser @ file:///tmp/build/80754af9/pycparser_1636541352034/work +pyOpenSSL @ file:///C:/b/abs_08f38zyck4/croot/pyopenssl_1690225407403/work +PySocks @ file:///C:/ci_311/pysocks_1676425991111/work +requests @ file:///C:/b/abs_316c2inijk/croot/requests_1690400295842/work +ruamel.yaml @ file:///C:/ci_311/ruamel.yaml_1676439214109/work +tqdm @ file:///C:/b/abs_f76j9hg7pv/croot/tqdm_1679561871187/work +truststore @ file:///C:/b/abs_55z7b3r045/croot/truststore_1695245455435/work +urllib3 @ file:///C:/b/abs_9cmlsrm3ys/croot/urllib3_1698257595508/work +win-inet-pton @ file:///C:/ci_311/win_inet_pton_1676425458225/work +zstandard==0.19.0 diff --git a/readme.md b/readme.md index 10f80b56..7e153481 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ ## CapsWriter-Offline -![image-20240108115946521](assets/image-20240108115946521.png) +![image-20240108115946521](assets/image-20240108115946521.png) 这是 `CapsWriter-Offline` ,一个 PC 端的语音输入、字幕转录工具。 @@ -9,13 +9,13 @@ 1. 按下键盘上的 `大写锁定键`,录音开始,当松开 `大写锁定键` 时,就会识别你的录音,并将识别结果立刻输入 2. 将音视频文件拖动到客户端打开,即可转录生成 srt 字幕 -视频教程:[CapsWriter-Offline 电脑端离线语音输入工具](https://www.bilibili.com/video/BV1tt4y1d75s/) +视频教程:[CapsWriter-Offline 电脑端离线语音输入工具](https://www.bilibili.com/video/BV1tt4y1d75s/) ## 特性 1. 完全离线、无限时长、低延迟、高准确率、中英混输、自动阿拉伯数字、自动调整中英间隔 2. 热词功能:可以在 `hot-en.txt hot-zh.txt hot-rule.txt` 中添加三种热词,客户端动态载入 -3. 日记功能:默认每次录音识别后,识别结果记录在 `年份/月份/日期.md` ,录音文件保存在 `年份/月份/assets` +3. 日记功能:默认每次录音识别后,识别结果记录在 `年份/月份/日期.md` ,录音文件保存在 `年份/月份/assets` 4. 关键词日记:识别结果若以关键词开头,会被记录在 `年份/月份/关键词-日期.md`,关键词在 `keywords.txt` 中定义 5. 转录功能:将音视频文件拖动到客户端打开,即可转录生成 srt 字幕 6. 服务端、客户端分离,可以服务多台客户端 @@ -43,14 +43,33 @@ 下载地址: -- 百度盘: https://pan.baidu.com/s/1zNHstoWZDJVynCBz2yS9vg 提取码: eu4c -- GitHub Release: [Releases · HaujetZhao/CapsWriter-Offline](https://github.com/HaujetZhao/CapsWriter-Offline/releases) +- 百度盘: https://pan.baidu.com/s/1zNHstoWZDJVynCBz2yS9vg 提取码: eu4c +- GitHub Release: [Releases · HaujetZhao/CapsWriter-Offline](https://github.com/HaujetZhao/CapsWriter-Offline/releases) (百度网盘容易掉链接,补链接太麻烦了,我不一定会补链接。GitHub Releases 界面下载是最可靠的。) -![image-20240108114351535](assets/image-20240108114351535.png) +![image-20240108114351535](assets/image-20240108114351535.png) +## 图形界面包 ( 仅 Windows 端) +1. 基于 [PySide6](https://pypi.org/project/PySide6/) 的 GUI,默认使用 [Qt-Material](https://github.com/UN-GCPDS/qt-material) dark_yellow 主题;基于 [PyStand](https://github.com/skywind3000/PyStand) 绿化便携 `start.exe`。 +2. 支持最小化到系统托盘。 +3. ~~Server 和 Client 以 tab 集成在一个 gui,无任务栏占用。~~ +4. ~~双击 start.exe 运行,可自行设置开机自启动。~~ 不建议使用,参考[这里](https://github.com/HaujetZhao/CapsWriter-Offline/pull/53#issuecomment-1903681063)。 +5. 已包含所有 Python 环境和 models 模型,解压即用。 +6. 支持转录功能,将文件拖动到 `start_client_gui.exe` +7. `hint_while_recording.exe`跟随`start_client_gui.exe`启停,实现按下 Capslock 键会在光标处提示 [✦ 语音输入中‧‧‧](https://github.com/HaujetZhao/CapsWriter-Offline/issues/52#issuecomment-1905758203) + + 下载地址: + +- 123 盘:https://www.123pan.com/s/qBxUVv-H4Zq3.html 提取码:h8vb) +- GitHub Release: [Releases · H1DDENADM1N/CapsWriter-Offline](https://github.com/H1DDENADM1N/CapsWriter-Offline/releases) + +![start_server_and_client_in_tray](assets/start_server_and_client_in_tray.gif) + +![start_server_or_client_in_tray](assets/start_server_or_client_in_tray.gif) + +![PixPin_2024-01-25_08-45-54](assets/PixPin_2024-01-25_08-45-54.png) ## 功能:热词 @@ -60,7 +79,7 @@ - 英文热词请写到 `hot-en.txt` 文件,每行一个,替换依据为字母拼写 -- 自定义规则热词请写到 `hot-rule.txt` 文件,每行一个,将搜索和替换词以等号隔开,如 `毫安时 = mAh` +- 自定义规则热词请写到 `hot-rule.txt` 文件,每行一个,将搜索和替换词以等号隔开,如 `毫安时 = mAh` 你可以在 `core_client.py` 文件中配置是否匹配中文多音字,是否严格匹配拼音声调。 @@ -72,8 +91,6 @@ ![image-20230531221314983](assets/image-20230531221314983.png) - - ## 功能:日记、关键词 默认每次语音识别结束后,会以年、月为分类,保存录音文件和识别结果: @@ -81,13 +98,13 @@ - 录音文件存放在「年/月/assets」文件夹下 - 识别结果存放在「年/月/日.md」Markdown 文件中 -例如今天是2023年6月5号,示例: +例如今天是 2023 年 6 月 5 号,示例: 1. 语音输入任一句话后,录音就会被保存到 `2023/06/assets` 路径下,以时间和识别结果命名,并将识别结果保存到 `2023/06/05.md` 文件中,方便我日后查阅 2. 例如我在 `keywords.txt` 中定义了关键词「健康」,用于随时记录自己的身体状况,吃完饭后我可以按住 `CapsLock` 说「健康今天中午吃了大米炒饭」,由于识别结果以「健康」关键词开头,这条识别记录就会被保存到 `2023/06/05-健康.md` 中 -3. 例如我在 `keywords.txt` 中定义了关键词「重要」,用于随时记录突然的灵感,有想法时我就可以按住 `CapsLock` 说「重要,xx问题可以用xxxx方法解决」,由于识别结果以「重要」关键词开头,这条识别记录就会被保存到 `2023/06/05-重要.md` 中 +3. 例如我在 `keywords.txt` 中定义了关键词「重要」,用于随时记录突然的灵感,有想法时我就可以按住 `CapsLock` 说「重要,xx 问题可以用 xxxx 方法解决」,由于识别结果以「重要」关键词开头,这条识别记录就会被保存到 `2023/06/05-重要.md` 中 -![image-20230604144824341](assets/image-20230604144824341.png) +![image-20230604144824341](assets/image-20230604144824341.png) ## 功能:转录文件 @@ -98,12 +115,12 @@ - `merge.txt` 文件,包含了带标点的整段结果 - `srt` 文件,字幕文件 -如果生成的字幕有微小错误,可以在分行的 `txt` 文件中修改,然后将 `txt` 文件拖动到客户端打开,客户端检测到输入的是 `txt` 文件,就会查到同名的 `json` 文件,结合 `json` 文件中的字级时间戳和 `txt` 文件中修正结果,更新 `srt` 字幕文件。 +如果生成的字幕有微小错误,可以在分行的 `txt` 文件中修改,然后将 `txt` 文件拖动到客户端打开,客户端检测到输入的是 `txt` 文件,就会查到同名的 `json` 文件,结合 `json` 文件中的字级时间戳和 `txt` 文件中修正结果,更新 `srt` 字幕文件。 ## 注意事项 1. 当用户安装了 `FFmpeg` 时,会以 `mp3` 格式保存录音;当用户没有装 `FFmpeg` 时,会以 `wav` 格式保存录音 -2. 音视频文件转录功能依赖于 `FFmpeg`,打包版本已内置 `FFmpeg` +2. 音视频文件转录功能依赖于 `FFmpeg`,打包版本已内置 `FFmpeg` 3. 默认的快捷键是 `caps lock`,你可以打开 `core_client.py` 进行修改 4. MacOS 无法监测到 `caps lock` 按键,可改为 `right shift` 按键 @@ -111,34 +128,29 @@ 你可以编辑 `config.py` ,在开头部分有注释,指导你修改服务端、客户端的: -- 连接的地址和端口,默认是 `127.0.0.1` 和 `6006` +- 连接的地址和端口,默认是 `127.0.0.1` 和 `6006` - 键盘快捷键 - 是否要保存录音文件 - 要移除识别结果末尾的哪些标点,(如果你想把句尾的问号也删除掉,可以在这边加上) -![image-20240108114558762](assets/image-20240108114558762.png) - - - +![image-20240108114558762](assets/image-20240108114558762.png) ## 下载模型 -服务端使用了 [sherpa-onnx](https://k2-fsa.github.io/sherpa/onnx/index.html) ,载入阿里巴巴开源的 [Paraformer](https://www.modelscope.cn/models/damo/speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-pytorch) 模型([转为量化的onnx格式](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/offline-paraformer/paraformer-models.html)),来作语音识别,整个模型约 230MB 大小。下载有已转换好的模型文件: +服务端使用了 [sherpa-onnx](https://k2-fsa.github.io/sherpa/onnx/index.html) ,载入阿里巴巴开源的 [Paraformer](https://www.modelscope.cn/models/damo/speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-pytorch) 模型([转为量化的 onnx 格式](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/offline-paraformer/paraformer-models.html)),来作语音识别,整个模型约 230MB 大小。下载有已转换好的模型文件: -- [csukuangfj/sherpa-onnx-paraformer-zh-2023-09-14](https://huggingface.co/csukuangfj/sherpa-onnx-paraformer-zh-2023-09-14) +- [csukuangfj/sherpa-onnx-paraformer-zh-2023-09-14](https://huggingface.co/csukuangfj/sherpa-onnx-paraformer-zh-2023-09-14) 另外,还使用了阿里巴巴的标点符号模型,大小约 1GB: -- [CT-Transformer标点-中英文-通用-large-onnx](https://www.modelscope.cn/models/damo/punc_ct-transformer_cn-en-common-vocab471067-large-onnx/summary) - -**模型文件太大,并没有包含在 GitHub 库里面,你可以从百度网盘或者 GitHub Releases 界面下载已经转换好的模型文件,解压后,将 `models` 文件夹放到软件根目录** - - +- [CT-Transformer 标点-中英文-通用-large-onnx](https://www.modelscope.cn/models/damo/punc_ct-transformer_cn-en-common-vocab471067-large-onnx/summary) +**模型文件太大,并没有包含在 GitHub 库里面,你可以从百度网盘或者 GitHub Releases 界面下载已经转换好的模型文件,解压后,将 `models` 文件夹放到软件根目录** ## 源码安装依赖 ### \[New\] Linux 端 + ```bash # for core_server.py pip install -r requirements-server.txt -i https://mirror.sjtu.edu.cn/pypi/web/simple @@ -148,9 +160,10 @@ pip install -r requirements-server.txt -i https://mirror.sjtu.edu.cn/pypi/web/s pip install -r requirements-client.txt -i https://mirror.sjtu.edu.cn/pypi/web/simple sudo apt-get install xclip # 让core_client.py正常运行 ``` + **运行方式** -`core_server.py` # 无需以 root 权限运行 -`core_client.py` # 注意: 必须以 root 权限运行!! +`core_server.py` # 无需以 root 权限运行 +`core_client.py` # 注意: 必须以 root 权限运行!! ### Windows 端 @@ -159,7 +172,7 @@ pip install -r requirements-server.txt pip install -r requirements-client.txt ``` -有些依赖在 `Python 3.11` 还暂时不无法安装,建议使用 `Python 3.8 - Python3.10` +有些依赖在 `Python 3.11` 还暂时不无法安装,建议使用 `Python 3.8 - Python3.10` ### Mac 端 @@ -175,25 +188,28 @@ python3 setup.py install ## 源码运行 -1. 运行 `core_server.py` 脚本,会载入 Paraformer 模型识别模型和标点模型(这会占用2GB的内存,载入时长约 50 秒) +1. 运行 `core_server.py` 脚本,会载入 Paraformer 模型识别模型和标点模型(这会占用 2GB 的内存,载入时长约 50 秒) 2. 运行 `core_client.py` 脚本,它会打开系统默认麦克风,开始监听按键(`MacOS` 端需要 `sudo`) -3. 按住 `CapsLock` 键,录音开始,松开 `CapsLock` 键,录音结束,识别结果立马被输入(录音时长短于0.3秒不算) +3. 按住 `CapsLock` 键,录音开始,松开 `CapsLock` 键,录音结束,识别结果立马被输入(录音时长短于 0.3 秒不算) MacOS 端注意事项: -- MacOS 上监听 `CapsLock` 键可能会出错,需要快捷键修改为其他按键,如 `right shift` +- MacOS 上监听 `CapsLock` 键可能会出错,需要快捷键修改为其他按键,如 `right shift` ## 打包方法 -Windows/MacOS/Linux均使用如下命令完成打包: + +Windows/MacOS/Linux 均使用如下命令完成打包: `pyinstaller build.spec` ## 运行方式 -### Linux -双击 `run.sh` 自动输入sudo密码且实现左右分屏展示 + +### Linux + +双击 `run.sh` 自动输入 sudo 密码且实现左右分屏展示 ![](./assets/run-sh.png) ## 打赏 如果你愿意,可以以打赏的方式支持我一下: -![sponsor](assets/sponsor.jpg) \ No newline at end of file +![sponsor](assets/sponsor.jpg) diff --git a/requirements-client.txt b/requirements-client.txt index dbd4749b..17aa47b1 100644 --- a/requirements-client.txt +++ b/requirements-client.txt @@ -1,6 +1,6 @@ rich keyboard -pyclip +pyperclip numpy sounddevice websockets diff --git a/runtime/LICENSE.txt b/runtime/LICENSE.txt new file mode 100644 index 00000000..7a0193a1 --- /dev/null +++ b/runtime/LICENSE.txt @@ -0,0 +1,702 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see https://opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +Python software and documentation are licensed under the +Python Software Foundation License Version 2. + +Starting with Python 3.8.6, examples, recipes, and other code in +the documentation are dual licensed under the PSF License Version 2 +and the Zero-Clause BSD license. + +Some software incorporated into Python is under different licenses. +The licenses are listed with code falling under that license. + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION +---------------------------------------------------------------------- + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + + + +Additional Conditions for this Windows binary build +--------------------------------------------------- + +This program is linked with and uses Microsoft Distributable Code, +copyrighted by Microsoft Corporation. The Microsoft Distributable Code +is embedded in each .exe, .dll and .pyd file as a result of running +the code through a linker. + +If you further distribute programs that include the Microsoft +Distributable Code, you must comply with the restrictions on +distribution specified by Microsoft. In particular, you must require +distributors and external end users to agree to terms that protect the +Microsoft Distributable Code at least as much as Microsoft's own +requirements for the Distributable Code. See Microsoft's documentation +(included in its developer tools and on its website at microsoft.com) +for specific details. + +Redistribution of the Windows binary build of the Python interpreter +complies with this agreement, provided that you do not: + +- alter any copyright, trademark or patent notice in Microsoft's +Distributable Code; + +- use Microsoft's trademarks in your programs' names or in a way that +suggests your programs come from or are endorsed by Microsoft; + +- distribute Microsoft's Distributable Code to run on a platform other +than Microsoft operating systems, run-time technologies or application +platforms; or + +- include Microsoft Distributable Code in malicious, deceptive or +unlawful programs. + +These restrictions apply only to the Microsoft Distributable Code as +defined above, not to Python itself or any programs running on the +Python interpreter. The redistribution of the Python interpreter and +libraries is governed by the Python Software License included with this +file, or by other licenses as marked. + + + +-------------------------------------------------------------------------- + +This program, "bzip2", the associated library "libbzip2", and all +documentation, are copyright (C) 1996-2019 Julian R Seward. All +rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + +3. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + +4. The name of the author may not be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Julian Seward, jseward@acm.org +bzip2/libbzip2 version 1.0.8 of 13 July 2019 + +-------------------------------------------------------------------------- + +libffi - Copyright (c) 1996-2022 Anthony Green, Red Hat, Inc and others. +See source files for details. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +``Software''), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + +This software is copyrighted by the Regents of the University of +California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState +Corporation and other parties. The following terms apply to all files +associated with the software unless explicitly disclaimed in +individual files. + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +MODIFICATIONS. + +GOVERNMENT USE: If you are acquiring this software on behalf of the +U.S. government, the Government shall have only "Restricted Rights" +in the software and related documentation as defined in the Federal +Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you +are acquiring the software on behalf of the Department of Defense, the +software shall be classified as "Commercial Computer Software" and the +Government shall have only "Restricted Rights" as defined in Clause +252.227-7014 (b) (3) of DFARs. Notwithstanding the foregoing, the +authors grant the U.S. Government and others acting in its behalf +permission to use and distribute the software in accordance with the +terms specified in this license. + +This software is copyrighted by the Regents of the University of +California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState +Corporation, Apple Inc. and other parties. The following terms apply to +all files associated with the software unless explicitly disclaimed in +individual files. + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +MODIFICATIONS. + +GOVERNMENT USE: If you are acquiring this software on behalf of the +U.S. government, the Government shall have only "Restricted Rights" +in the software and related documentation as defined in the Federal +Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you +are acquiring the software on behalf of the Department of Defense, the +software shall be classified as "Commercial Computer Software" and the +Government shall have only "Restricted Rights" as defined in Clause +252.227-7013 (b) (3) of DFARs. Notwithstanding the foregoing, the +authors grant the U.S. Government and others acting in its behalf +permission to use and distribute the software in accordance with the +terms specified in this license. + +Copyright (c) 1993-1999 Ioi Kim Lam. +Copyright (c) 2000-2001 Tix Project Group. +Copyright (c) 2004 ActiveState + +This software is copyrighted by the above entities +and other parties. The following terms apply to all files associated +with the software unless explicitly disclaimed in individual files. + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +MODIFICATIONS. + +GOVERNMENT USE: If you are acquiring this software on behalf of the +U.S. government, the Government shall have only "Restricted Rights" +in the software and related documentation as defined in the Federal +Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you +are acquiring the software on behalf of the Department of Defense, the +software shall be classified as "Commercial Computer Software" and the +Government shall have only "Restricted Rights" as defined in Clause +252.227-7013 (c) (1) of DFARs. Notwithstanding the foregoing, the +authors grant the U.S. Government and others acting in its behalf +permission to use and distribute the software in accordance with the +terms specified in this license. + +---------------------------------------------------------------------- + +Parts of this software are based on the Tcl/Tk software copyrighted by +the Regents of the University of California, Sun Microsystems, Inc., +and other parties. The original license terms of the Tcl/Tk software +distribution is included in the file docs/license.tcltk. + +Parts of this software are based on the HTML Library software +copyrighted by Sun Microsystems, Inc. The original license terms of +the HTML Library software distribution is included in the file +docs/license.html_lib. + diff --git a/runtime/_asyncio.pyd b/runtime/_asyncio.pyd new file mode 100644 index 00000000..20703dc1 Binary files /dev/null and b/runtime/_asyncio.pyd differ diff --git a/runtime/_bz2.pyd b/runtime/_bz2.pyd new file mode 100644 index 00000000..820f45a2 Binary files /dev/null and b/runtime/_bz2.pyd differ diff --git a/runtime/_ctypes.pyd b/runtime/_ctypes.pyd new file mode 100644 index 00000000..73372d54 Binary files /dev/null and b/runtime/_ctypes.pyd differ diff --git a/runtime/_decimal.pyd b/runtime/_decimal.pyd new file mode 100644 index 00000000..b332c6be Binary files /dev/null and b/runtime/_decimal.pyd differ diff --git a/runtime/_elementtree.pyd b/runtime/_elementtree.pyd new file mode 100644 index 00000000..a2b67617 Binary files /dev/null and b/runtime/_elementtree.pyd differ diff --git a/runtime/_hashlib.pyd b/runtime/_hashlib.pyd new file mode 100644 index 00000000..94f86200 Binary files /dev/null and b/runtime/_hashlib.pyd differ diff --git a/runtime/_lzma.pyd b/runtime/_lzma.pyd new file mode 100644 index 00000000..b6030741 Binary files /dev/null and b/runtime/_lzma.pyd differ diff --git a/runtime/_msi.pyd b/runtime/_msi.pyd new file mode 100644 index 00000000..6453746a Binary files /dev/null and b/runtime/_msi.pyd differ diff --git a/runtime/_multiprocessing.pyd b/runtime/_multiprocessing.pyd new file mode 100644 index 00000000..d4fbedb6 Binary files /dev/null and b/runtime/_multiprocessing.pyd differ diff --git a/runtime/_overlapped.pyd b/runtime/_overlapped.pyd new file mode 100644 index 00000000..2ff23323 Binary files /dev/null and b/runtime/_overlapped.pyd differ diff --git a/runtime/_queue.pyd b/runtime/_queue.pyd new file mode 100644 index 00000000..66ede127 Binary files /dev/null and b/runtime/_queue.pyd differ diff --git a/runtime/_socket.pyd b/runtime/_socket.pyd new file mode 100644 index 00000000..8530bc00 Binary files /dev/null and b/runtime/_socket.pyd differ diff --git a/runtime/_sqlite3.pyd b/runtime/_sqlite3.pyd new file mode 100644 index 00000000..d62581bb Binary files /dev/null and b/runtime/_sqlite3.pyd differ diff --git a/runtime/_ssl.pyd b/runtime/_ssl.pyd new file mode 100644 index 00000000..f49f5fcb Binary files /dev/null and b/runtime/_ssl.pyd differ diff --git a/runtime/_uuid.pyd b/runtime/_uuid.pyd new file mode 100644 index 00000000..fcecbbd9 Binary files /dev/null and b/runtime/_uuid.pyd differ diff --git a/runtime/_zoneinfo.pyd b/runtime/_zoneinfo.pyd new file mode 100644 index 00000000..d22f6f0b Binary files /dev/null and b/runtime/_zoneinfo.pyd differ diff --git a/runtime/libcrypto-3.dll b/runtime/libcrypto-3.dll new file mode 100644 index 00000000..c0997259 Binary files /dev/null and b/runtime/libcrypto-3.dll differ diff --git a/runtime/libffi-8.dll b/runtime/libffi-8.dll new file mode 100644 index 00000000..8ebbbe8d Binary files /dev/null and b/runtime/libffi-8.dll differ diff --git a/runtime/libssl-3.dll b/runtime/libssl-3.dll new file mode 100644 index 00000000..07ebcf6f Binary files /dev/null and b/runtime/libssl-3.dll differ diff --git a/runtime/pyexpat.pyd b/runtime/pyexpat.pyd new file mode 100644 index 00000000..520ef3c5 Binary files /dev/null and b/runtime/pyexpat.pyd differ diff --git a/runtime/python.cat b/runtime/python.cat new file mode 100644 index 00000000..2adb5581 Binary files /dev/null and b/runtime/python.cat differ diff --git a/runtime/python.exe b/runtime/python.exe new file mode 100644 index 00000000..04ec0b88 Binary files /dev/null and b/runtime/python.exe differ diff --git a/runtime/python3.dll b/runtime/python3.dll new file mode 100644 index 00000000..f1ad01ce Binary files /dev/null and b/runtime/python3.dll differ diff --git a/runtime/python311._pth b/runtime/python311._pth new file mode 100644 index 00000000..a7c1d499 --- /dev/null +++ b/runtime/python311._pth @@ -0,0 +1,6 @@ +python311.zip +. +../site-packages +.. +# Uncomment to run site.main() automatically +#import site diff --git a/runtime/python311.dll b/runtime/python311.dll new file mode 100644 index 00000000..874a06e3 Binary files /dev/null and b/runtime/python311.dll differ diff --git a/runtime/python311.zip b/runtime/python311.zip new file mode 100644 index 00000000..5241e9bf Binary files /dev/null and b/runtime/python311.zip differ diff --git a/runtime/pythonw.exe b/runtime/pythonw.exe new file mode 100644 index 00000000..38728520 Binary files /dev/null and b/runtime/pythonw.exe differ diff --git a/runtime/pythonw_CapsWriter_Client.exe b/runtime/pythonw_CapsWriter_Client.exe new file mode 100644 index 00000000..38728520 Binary files /dev/null and b/runtime/pythonw_CapsWriter_Client.exe differ diff --git a/runtime/pythonw_CapsWriter_Server.exe b/runtime/pythonw_CapsWriter_Server.exe new file mode 100644 index 00000000..38728520 Binary files /dev/null and b/runtime/pythonw_CapsWriter_Server.exe differ diff --git a/runtime/select.pyd b/runtime/select.pyd new file mode 100644 index 00000000..3291d86e Binary files /dev/null and b/runtime/select.pyd differ diff --git a/runtime/sqlite3.dll b/runtime/sqlite3.dll new file mode 100644 index 00000000..7d5cf878 Binary files /dev/null and b/runtime/sqlite3.dll differ diff --git a/runtime/unicodedata.pyd b/runtime/unicodedata.pyd new file mode 100644 index 00000000..cbd7b089 Binary files /dev/null and b/runtime/unicodedata.pyd differ diff --git a/runtime/vcruntime140.dll b/runtime/vcruntime140.dll new file mode 100644 index 00000000..edba5485 Binary files /dev/null and b/runtime/vcruntime140.dll differ diff --git a/runtime/vcruntime140_1.dll b/runtime/vcruntime140_1.dll new file mode 100644 index 00000000..6091fbf0 Binary files /dev/null and b/runtime/vcruntime140_1.dll differ diff --git a/runtime/winsound.pyd b/runtime/winsound.pyd new file mode 100644 index 00000000..da8fbb4c Binary files /dev/null and b/runtime/winsound.pyd differ diff --git "a/site-packages/\350\277\220\350\241\214\344\276\235\350\265\226\344\270\213\350\275\275\346\226\271\345\274\217.txt" "b/site-packages/\350\277\220\350\241\214\344\276\235\350\265\226\344\270\213\350\275\275\346\226\271\345\274\217.txt" new file mode 100644 index 00000000..51e68068 --- /dev/null +++ "b/site-packages/\350\277\220\350\241\214\344\276\235\350\265\226\344\270\213\350\275\275\346\226\271\345\274\217.txt" @@ -0,0 +1,19 @@ +方法一: +从123盘下载发布包,并提权site-packages目录下依赖 +https://www.123pan.com/s/qBxUVv-z1Zq3.html提取码:h8vb + + + + + + + +方法二: +本地安装Python3.11.5虚拟环境(与runtime目录嵌入式Python版本保持一致) + +pip install -r requirements-server.txt +pip install -r requirements-client.txt +pip install PySide6 +pip install qt-material + +将本地site-packages目录下依赖复制到此文件夹 \ No newline at end of file diff --git a/start.exe b/start.exe new file mode 100644 index 00000000..3d9e4122 Binary files /dev/null and b/start.exe differ diff --git a/start.py b/start.py new file mode 100644 index 00000000..f7793e0f --- /dev/null +++ b/start.py @@ -0,0 +1,154 @@ +import sys +import subprocess +from queue import Queue +import threading +from PySide6.QtWidgets import (QApplication, QMainWindow, QTabWidget, QTextEdit, QSystemTrayIcon, QMenu, QPushButton, QVBoxLayout, QWidget) +from PySide6.QtGui import (QIcon, QAction) +from PySide6.QtCore import (Qt, QTimer) +from qt_material import apply_stylesheet + +class GUI(QMainWindow): + def __init__(self): + super().__init__() + self.init_ui() + self.output_queue_server = Queue() + self.output_queue_client = Queue() + self.start_script() + + def init_ui(self): + self.resize(425, 425) + self.setWindowTitle('CapsWriter-Offline') + self.setWindowIcon(QIcon("assets/icon.ico")) + self.create_tabs() + self.create_clear_buttons() # Create clear buttons + self.create_systray_icon() + self.hide() + self.tab_server.clear() + self.tab_client.clear() + + def create_tabs(self): + self.tab_widget = QTabWidget() + self.setCentralWidget(self.tab_widget) + + self.tab_server = QTextEdit() + self.tab_client = QTextEdit() + + # Configure the QTextEdit widgets to not show scroll bars + self.tab_server.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.tab_server.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.tab_client.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.tab_client.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + + self.tab_widget.addTab(self.tab_server, "Server") + self.tab_widget.addTab(self.tab_client, "Client") + + def create_clear_buttons(self): + # Create two buttons + self.clear_server_button = QPushButton("Clear Server Text", self) + self.clear_client_button = QPushButton("Clear Client Text", self) + + # Connect click events + self.clear_server_button.clicked.connect(lambda: self.clear_text_box(self.tab_server)) + self.clear_client_button.clicked.connect(lambda: self.clear_text_box(self.tab_client)) + + # Create a central widget + central_widget = QWidget() + + # Create a vertical layout + layout = QVBoxLayout() + + # Add tab widgets and buttons to the layout + layout.addWidget(self.tab_widget) + layout.addWidget(self.clear_server_button) + layout.addWidget(self.clear_client_button) + + # Set the layout as the central widget's layout + central_widget.setLayout(layout) + + # Set the central widget + self.setCentralWidget(central_widget) + + def clear_text_box(self, text_box): + # Clear the content of the specified text box + text_box.clear() + + def create_systray_icon(self): + self.tray_icon = QSystemTrayIcon(self) + self.tray_icon.setIcon(QIcon("assets/icon.ico")) + show_action = QAction("Show", self) + quit_action = QAction("Quit", self) + show_action.triggered.connect(self.show) + quit_action.triggered.connect(self.quit_app) + self.tray_icon.activated.connect(self.on_tray_icon_activated) + tray_menu = QMenu() + tray_menu.addAction(show_action) + tray_menu.addAction(quit_action) + self.tray_icon.setContextMenu(tray_menu) + self.tray_icon.show() + + def closeEvent(self, event): + # Minimize to system tray instead of closing the window when the user clicks the close button + self.hide() # Hide the window + event.ignore() # Ignore the close event + + def quit_app(self): + # Terminate core_server.py process + if hasattr(self, 'core_server_process') and self.core_server_process: + self.core_server_process.terminate() + self.core_server_process.kill() + + # Terminate core_client.py process + if hasattr(self, 'core_client_process') and self.core_client_process: + self.core_client_process.terminate() + self.core_client_process.kill() + + # Hide the system tray icon + self.tray_icon.setVisible(False) + + # Quit the application + QApplication.quit() + + # TODO: Quit models The above method can not completely exit the model, rename pythonw.exe to pythonw_CapsWriter.exe and taskkill. It's working but not the best way. + proc = subprocess.Popen('taskkill /IM pythonw_CapsWriter_Server.exe /IM pythonw_CapsWriter_Client.exe /F', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, text=True) + + def on_tray_icon_activated(self, reason): + # Called when the system tray icon is activated + if reason == QSystemTrayIcon.DoubleClick: + self.show() # Show the main window + + def start_script(self): + # Start core_server.py and redirect output to the server queue + self.core_server_process = subprocess.Popen(['.\\runtime\\pythonw_CapsWriter_Server.exe', 'core_server.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + threading.Thread(target=self.enqueue_output, args=(self.core_server_process.stdout, self.output_queue_server), daemon=True).start() + + # Start core_client.py and redirect output to the client queue + self.core_client_process = subprocess.Popen(['.\\runtime\\pythonw_CapsWriter_Client.exe', 'core_client.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + threading.Thread(target=self.enqueue_output, args=(self.core_client_process.stdout, self.output_queue_client), daemon=True).start() + + # Update text boxes + self.update_timer = QTimer() + self.update_timer.timeout.connect(self.update_text_boxes) + self.update_timer.start(100) + + + def enqueue_output(self, out, queue): + for line in iter(out.readline, b''): + queue.put(line) + + def update_text_boxes(self): + # Update server text box + while not self.output_queue_server.empty(): + line = self.output_queue_server.get() + self.tab_server.append(line) + + # Update client text box + while not self.output_queue_client.empty(): + line = self.output_queue_client.get() + self.tab_client.append(line) + +if __name__ == '__main__': + app = QApplication([]) + apply_stylesheet(app, theme='dark_amber.xml') + gui = GUI() + gui.show() + sys.exit(app.exec()) diff --git a/start_client_gui.exe b/start_client_gui.exe new file mode 100644 index 00000000..3d9e4122 Binary files /dev/null and b/start_client_gui.exe differ diff --git a/start_client_gui.py b/start_client_gui.py new file mode 100644 index 00000000..24953454 --- /dev/null +++ b/start_client_gui.py @@ -0,0 +1,134 @@ +import os +import sys +import subprocess +from queue import Queue +import threading +from PySide6.QtWidgets import (QApplication, QMainWindow, QTextEdit, QSystemTrayIcon, QMenu, QPushButton, QVBoxLayout, QWidget) +from PySide6.QtGui import (QIcon, QAction) +from PySide6.QtCore import (Qt, QTimer) +from qt_material import apply_stylesheet + +class GUI(QMainWindow): + def __init__(self): + super().__init__() + self.init_ui() + self.output_queue_client = Queue() + self.start_script() + + def init_ui(self): + self.resize(425, 425) + self.setWindowTitle('CapsWriter-Offline-Client') + self.setWindowIcon(QIcon("assets/client-icon.ico")) + self.create_text_box() + self.create_clear_button() # Create clear button + self.create_systray_icon() + self.hide() + + def create_text_box(self): + self.text_box_client = QTextEdit() + self.text_box_client.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.text_box_client.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setCentralWidget(self.text_box_client) + + def create_clear_button(self): + # Create a button + self.clear_button = QPushButton("Clear Client Text", self) + + # Connect click event + self.clear_button.clicked.connect(lambda: self.clear_text_box()) + + # Create a vertical layout + layout = QVBoxLayout() + + # Add text box and button to the layout + layout.addWidget(self.text_box_client) + layout.addWidget(self.clear_button) + + # Create a central widget + central_widget = QWidget() + central_widget.setLayout(layout) + + # Set the central widget + self.setCentralWidget(central_widget) + + def clear_text_box(self): + # Clear the content of the client text box + self.text_box_client.clear() + + def create_systray_icon(self): + self.tray_icon = QSystemTrayIcon(self) + self.tray_icon.setIcon(QIcon("assets/client-icon.ico")) + show_action = QAction("Show", self) + quit_action = QAction("Quit", self) + show_action.triggered.connect(self.show) + quit_action.triggered.connect(self.quit_app) + self.tray_icon.activated.connect(self.on_tray_icon_activated) + tray_menu = QMenu() + tray_menu.addAction(show_action) + tray_menu.addAction(quit_action) + self.tray_icon.setContextMenu(tray_menu) + self.tray_icon.show() + + def closeEvent(self, event): + # Minimize to system tray instead of closing the window when the user clicks the close button + self.hide() # Hide the window + event.ignore() # Ignore the close event + + def quit_app(self): + # Terminate core_client.py process + if hasattr(self, 'core_client_process') and self.core_client_process: + self.core_client_process.terminate() + self.core_client_process.kill() + + # Hide the system tray icon + self.tray_icon.setVisible(False) + + # Quit the application + QApplication.quit() + + # TODO: Quit models The above method can not completely exit the model, rename pythonw.exe to pythonw_CapsWriter.exe and taskkill. It's working but not the best way. + proc = subprocess.Popen('taskkill /IM pythonw_CapsWriter_Client.exe /IM hint_while_recording.exe /F', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, text=True) + + + def on_tray_icon_activated(self, reason): + # Called when the system tray icon is activated + if reason == QSystemTrayIcon.DoubleClick: + self.show() # Show the main window + + def start_script(self): + # Start core_client.py and redirect output to the client queue + self.core_client_process = subprocess.Popen(['.\\runtime\\pythonw_CapsWriter_Client.exe', 'core_client.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + threading.Thread(target=self.enqueue_output, args=(self.core_client_process.stdout, self.output_queue_client), daemon=True).start() + + # Update text box + self.update_timer = QTimer() + self.update_timer.timeout.connect(self.update_text_box) + self.update_timer.start(100) + + + def enqueue_output(self, out, queue): + for line in iter(out.readline, b''): + queue.put(line) + + def update_text_box(self): + # Update client text box + while not self.output_queue_client.empty(): + line = self.output_queue_client.get() + self.text_box_client.append(line) + +if __name__ == '__main__': + if sys.argv[1:]: + # 如果参数传入文件,那就转录文件 + CapsWriter_path = os.path.dirname(os.path.abspath(__file__)) + script_path = os.path.join(CapsWriter_path, 'core_client.py') + python_exe_path = os.path.join(CapsWriter_path, 'runtime\\python.exe') + args = [arg for arg in sys.argv[1:]] + subprocess.run([python_exe_path, script_path] + args) + else: + # GUI + proc = subprocess.Popen(['.\\hint_while_recording.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + app = QApplication([]) + apply_stylesheet(app, theme='dark_teal.xml') + gui = GUI() + gui.show() + sys.exit(app.exec()) diff --git a/start_server_gui.exe b/start_server_gui.exe new file mode 100644 index 00000000..3d9e4122 Binary files /dev/null and b/start_server_gui.exe differ diff --git a/start_server_gui.py b/start_server_gui.py new file mode 100644 index 00000000..2099aee3 --- /dev/null +++ b/start_server_gui.py @@ -0,0 +1,122 @@ +import sys +import subprocess +from queue import Queue +import threading +from PySide6.QtWidgets import (QApplication, QMainWindow, QTextEdit, QSystemTrayIcon, QMenu, QPushButton, QVBoxLayout, QWidget) +from PySide6.QtGui import (QIcon, QAction) +from PySide6.QtCore import (Qt, QTimer) +from qt_material import apply_stylesheet + +class GUI(QMainWindow): + def __init__(self): + super().__init__() + self.init_ui() + self.output_queue_server = Queue() + self.start_script() + + def init_ui(self): + self.resize(425, 425) + self.setWindowTitle('CapsWriter-Offline-Server') + self.setWindowIcon(QIcon("assets/server-icon.ico")) + self.create_text_box() + self.create_clear_button() # Create clear button + self.create_systray_icon() + self.hide() + + def create_text_box(self): + self.text_box_server = QTextEdit() + self.text_box_server.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.text_box_server.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setCentralWidget(self.text_box_server) + + def create_clear_button(self): + # Create a button + self.clear_button = QPushButton("Clear Server Text", self) + + # Connect click event + self.clear_button.clicked.connect(lambda: self.clear_text_box()) + + # Create a vertical layout + layout = QVBoxLayout() + + # Add text box and button to the layout + layout.addWidget(self.text_box_server) + layout.addWidget(self.clear_button) + + # Create a central widget + central_widget = QWidget() + central_widget.setLayout(layout) + + # Set the central widget + self.setCentralWidget(central_widget) + + def clear_text_box(self): + # Clear the content of the server text box + self.text_box_server.clear() + + def create_systray_icon(self): + self.tray_icon = QSystemTrayIcon(self) + self.tray_icon.setIcon(QIcon("assets/server-icon.ico")) + show_action = QAction("Show", self) + quit_action = QAction("Quit", self) + show_action.triggered.connect(self.show) + quit_action.triggered.connect(self.quit_app) + self.tray_icon.activated.connect(self.on_tray_icon_activated) + tray_menu = QMenu() + tray_menu.addAction(show_action) + tray_menu.addAction(quit_action) + self.tray_icon.setContextMenu(tray_menu) + self.tray_icon.show() + + def closeEvent(self, event): + # Minimize to system tray instead of closing the window when the user clicks the close button + self.hide() # Hide the window + event.ignore() # Ignore the close event + + def quit_app(self): + # Terminate core_server.py process + if hasattr(self, 'core_server_process') and self.core_server_process: + self.core_server_process.terminate() + self.core_server_process.kill() + + # Hide the system tray icon + self.tray_icon.setVisible(False) + + # Quit the application + QApplication.quit() + + # TODO: Quit models The above method can not completely exit the model, rename pythonw.exe to pythonw_CapsWriter.exe and taskkill. It's working but not the best way. + proc = subprocess.Popen('taskkill /IM pythonw_CapsWriter_Server.exe /F', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, text=True) + + def on_tray_icon_activated(self, reason): + # Called when the system tray icon is activated + if reason == QSystemTrayIcon.DoubleClick: + self.show() # Show the main window + + def start_script(self): + # Start core_server.py and redirect output to the server queue + self.core_server_process = subprocess.Popen(['.\\runtime\\pythonw_CapsWriter_Server.exe', 'core_server.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + threading.Thread(target=self.enqueue_output, args=(self.core_server_process.stdout, self.output_queue_server), daemon=True).start() + + # Update text box + self.update_timer = QTimer() + self.update_timer.timeout.connect(self.update_text_box) + self.update_timer.start(100) + + + def enqueue_output(self, out, queue): + for line in iter(out.readline, b''): + queue.put(line) + + def update_text_box(self): + # Update server text box + while not self.output_queue_server.empty(): + line = self.output_queue_server.get() + self.text_box_server.append(line) + +if __name__ == '__main__': + app = QApplication([]) + apply_stylesheet(app, theme='dark_amber.xml') + gui = GUI() + gui.show() + sys.exit(app.exec()) diff --git a/util/client_create_file.py b/util/client_create_file.py index 9ccc4c80..1f039463 100644 --- a/util/client_create_file.py +++ b/util/client_create_file.py @@ -20,21 +20,21 @@ def create_file(channels: int, time_start: float) -> Tuple[Path, Union[Popen, Wa file_path = tempfile.mktemp(prefix=f'({time_ymdhms})', dir=folder_path) file_path = Path(file_path) - if shutil.which('ffmpeg'): - # 用户已安装 ffmpeg,则输出到 mp3 文件 - file_path = file_path.with_suffix('.mp3') - # 构造ffmpeg命令行 - ffmpeg_command = [ - 'ffmpeg', '-y', - '-f', 'f32le', '-ar', '48000', '-ac', f'{channels}', '-i', '-', - '-b:a', '192k', file_path, - ] - # 执行ffmpeg命令行,得到 Popen - file = Popen(ffmpeg_command, stdin=PIPE, stdout=DEVNULL, stderr=DEVNULL) - else: # 用户未安装 ffmpeg,则输出为 wav 格式 - file_path = file_path.with_suffix('.wav') - file = wave.open(str(file_path), 'w') - file.setnchannels(channels) - file.setsampwidth(2) - file.setframerate(48000) + # if shutil.which('ffmpeg'): + # # 用户已安装 ffmpeg,则输出到 mp3 文件 + # file_path = file_path.with_suffix('.mp3') + # # 构造ffmpeg命令行 + # ffmpeg_command = [ + # 'ffmpeg', '-y', + # '-f', 'f32le', '-ar', '48000', '-ac', f'{channels}', '-i', '-', + # '-b:a', '192k', file_path, + # ] + # # 执行ffmpeg命令行,得到 Popen + # file = Popen(ffmpeg_command, stdin=PIPE, stdout=DEVNULL, stderr=DEVNULL) + # else: # 用户未安装 ffmpeg,则输出为 wav 格式 + file_path = file_path.with_suffix('.wav') + file = wave.open(str(file_path), 'w') + file.setnchannels(channels) + file.setsampwidth(2) + file.setframerate(48000) return file_path, file diff --git a/util/client_type_result.py b/util/client_type_result.py index 6a745438..c09549af 100644 --- a/util/client_type_result.py +++ b/util/client_type_result.py @@ -1,6 +1,6 @@ from config import ClientConfig as Config import keyboard -import pyclip +import pyperclip import platform import asyncio @@ -12,12 +12,12 @@ async def type_result(text): # 保存剪切板 try: - temp = pyclip.paste().decode('utf-8') + temp = pyperclip.paste().decode('utf-8') except: temp = '' # 复制结果 - pyclip.copy(text) + pyperclip.copy(text) # 粘贴结果 if platform.system() == 'Darwin': @@ -31,7 +31,7 @@ async def type_result(text): # 还原剪贴板 if Config.restore_clip: await asyncio.sleep(0.1) - pyclip.copy(temp) + pyperclip.copy(temp) # 模拟打印 else: