Skip to content

Latest commit

 

History

History
1574 lines (1031 loc) · 173 KB

日志.md

File metadata and controls

1574 lines (1031 loc) · 173 KB

2022-12-11

确定了项目模块与打包器对应的大致逻辑。

已实现使用 webpack 对 backend 的 Hello world 进行打包,@common 也可打包进程序,并使用 pkg 生成 exe。后期需引入 koa 一系列东西去测试能否正常打包。

正在尝试调试 backend。目前已实现使用 webpack 打包(带 source-map)完成后再使用 vscode 对生成文件进行调试。仍需探索使用 vite devServer 的方式而不是 webpack 输出文件。如果确实不行,这样分两步也没太大问题,不过最好有个脚本可以合并这个操作。

这里有人说可以用 vscode 调试 vite 打包的项目。

2022-12-12

vite devServer 无用了,因为找不到一种方法,可以让 vscode 启动调试任务时,让 vite 启动一个 devServer 进行 HMR 编译。vite CLI 可以启动 devServer 服务,但是它不进行编译。vite Node API 可以编译,但代码逻辑只是让它感知到代码变化后重新 build 到 app/backend 里去,编译输出的文件跟 server 似乎没什么关系。

所以干脆就做成在 vscode 里启动调试之后,先用 vite CLI 进行 build,等完成之后用传统方法启动一个 vscode 调试任务(启动 js,map 到 ts)。同样的,webpack 也能实现这个功能。于是就做了 vite 和 webpack 两套方案。

下一步应该是要先打通引入了 koa 之后的 backend 输出到 exe(不急着写脚本)。然后写使用 vite 进行 electron Hello world 开发的脚本。这一步理论上不太难,因为已经有别的框架打了模板。 如果确实要做 HMR 的话,vscode 的文档里有指南。这里放一个 vue 的链接

2022-12-13

引入了部分 koa 之后,vite 直接成功,webpack 下 formidable 会出运行时错误,需要根据链接手动配置 hexoid 路径才成功,原因未知。

2022-12-16

打开所有了后端代码之后的运行都成功了。vite 依然是一次成功,webpack 则卡在了 ws 库上。依然没去探寻默认值下打包失败的原因,不过这个地方基本包含了全部的解决方案。可以用 resolve 强行把入口指向 node_module 下的文件,也可以换个导入方法(改代码)。

2022-12-17

增加了用于 renderer、preload、main 的 vite 配置,dev-frontend 和 build 基本上都是一次成功。只是稍微研究了下导入路径。已经实现引入 vue3 Hello world 之后使用 dev 模式启动。

目前 vite:renderer 这步只差 index.html 的编译就能导进 electron-builder 了。

下一步就应该去设计 interface 和主控逻辑了。目前还没想好从哪开始做起。目前可知的是,按之前想过的内容,应该要构思一下如何做两边的连通。比如说生产环境下直接 spawn,开发环境下……不知道……

2022-12-18

连通方式大致是:生产环境下直接 spawn,开发环境下启动一个 node --inspect 或者 --inspect-brk。这样,在 FFBox 启动后将自动启动一个 backend,此时只需要在 vscode 使用 attach 模式进入调试就可以了。launch.json 已经增加了 attach 模式,剩下的就是后续在 main 里 spawn 一个 node 了。

index.html 这步也已完成。把 config 中的 lib 去掉就可以了。导进 electron-builder 打包之后能直接启动。

因为该版本需要考虑 preload,所以也进行了 preload 的尝试。目前也已成功将 preload 里的 electron API 引入 renderer 里使用。不能直接把整个 API 对象导出来,需要细分到里面的方法,因为导出整个 API 的话里面就包含了一些不是 JavaScript 上下文的东西。

renderer 方面,引入了 jsx,还没尝试能不能用。

2022-12-19

开始了界面的设计,正在设计整体框架。less 引入直接成功,零配置。

2022-12-20

volar 扩展似乎安装成功了。之前用的一直是 v0.36,即使重装插件也是 v0.36,直到关闭所有页面升级 vscode 后才装上了 v1。此前 .vue 文件是没有代码提示的。

2022-12-23

加入了 256px 无边距透明的 logo,适配了无边框样式的一些细节。

2022-12-24

研究了一下 box-shadow 的渐变机制,发现 W3C 并没有对使用何种算法渲染阴影有规定。因此我测试出来,chromium 的阴影并不线性,只能知道阴影最远向外扩散到多少像素,至于向内,阴影浓度很快就接近 100%,但直到超过了指定像素一小段距离才真正达到 100%。

做这个测试是因为今天给 FFBox 引入了无边框,正在给 FFBox 标题栏增加 tabs,ActionBar 上的那一道亮光导致了 tabs 跟 ActionBar 的过渡并不柔和。

另外,做了一下三大金刚键的一部分,然后就去捣鼓怎样引入 svg 了。默认的引入方式是 img 标签,但这样无法控制颜色,因此还是需要一个 loader。npm 上搜到的第一个便是,虽然看起来挺方便易用,但是我马上发现了一个 bug。vite 对 vue 的社区支持明显是比 react 好很多,之前在做 ttqftech 的 svg 引入时,搜了很久也只能找到一个略有点不优雅的方案。

2022-12-28

确定好了初期 menu 要放什么东西进去。同时也实现了三大金刚键的功能。

另外,electron 已经支持了 window overlay,可以把 Windows 三大金刚键做到窗口上,如果调好了的话就可以使用 Win11 的快速分屏小工具了,缺点是不能移动位置。

目前不打算把它做进去,只是用自己的实现。我的主要诉求骑士是拖动标题栏就能实现分屏等的基础效果。实际上,只要把 transparent 关掉就可以了。按目前的 UI 设计来说,也没有一定需要该窗口透明才能实现的东西,因此就这么用了。

2022-12-29

继续优化了一下 tab 按钮和三大金刚键的阴影效果。

2022-12-30

补充了参数面板的相关设计,包括六个按钮和全局参数的布局关系,仔细调整了面板和按钮的阴影。另外,动作栏也加上了“添加任务”按钮(样式待优化),后期在中间加上搜索框就完整了(4.0 暂时不做提示功能)。

参数盒里的控件是动态渲染出来的,所以稍后再做。下一步需要设计的是任务项的样式。完了之后就该尝试加上 pinia,给界面分文件了。

2023-01-23

任务项要长成什么样子,这个其实想了很久。而且任务项本身包含的东西比较多,如果要像 v1.0 那样,把东西先用 HTML hardcode 进去,也不是不行。但这样后期就得再做适配,时间成本上可能不如我先把工程化做好一点,接入实际数据更好。

另外,由于我对我自己目前设计出来的界面并不满意,也对自己每天排满活的行为并不满意,所以停了一段时间。

git 提交的节奏还是做不到瞬间转变,所以要在一次提交里包含比较多的东西。这次由于已经拖了一段时间了,所以干脆就直接先把 pinia 弄了,做一下工程化。

因为技术栈发生了 Vue 2 -> Vue 3,VueX -> Pinia,时序上有些变化。index.ts 里的 setActivePinia 就是变化的体现。在之前的版本里,VueX 是插入到 Vue 里,在组件函数里引用的;而现在引用的位置放到了顶层而不是函数里,这样就需要在 createApp 之前手动设置一下 pinia 实例,而不能 createApp 完了之后再 app.use。

2023-02-01

正在尝试给 store 添加东西了。

首先添加的是 servers,为此引入了之前的几个 bridge。在以前的代码里,使用了 remote、process 这种在现代 electron、浏览器中不受支持的 API,因此也对相关的函数做了小幅更改。

另外就是遇到了一个 vite 相关的文件引入问题,已修改配置解决。还剩下的一个问题是 vite 似乎读不到 EventEmitter。

2023-02-02

在渲染进程中,因为开启了一些 electron 的隔离功能,所以不能用 node 的 EventEmitter。昨天的问题是这个。引入 events 库就好了。

往 store 中加入了添加服务器和任务的功能,尚未做服务器事件监听,因此服务器中的数据目前传不到前端。得考虑开始做模块化了。

2023-02-08

把 MainFrame 中 container 里的东西拆成四大块之后,终于能“清爽”地进行接下来的开发了。 (其实前几块的铺垫都是为了搞 01-23 所说的任务项 TaskItem,想直接把 store 中的数据通给它做渲染)

这次的主要难点在于引入 jsx。首先引入 @vitejs/plugin-vue-jsx 的作用就是给 vite 引入一个 babel 做转换(大致是这么回事),但对功能以外的东西它并不关心。于是我就遇到了两个问题:eslint 和 ts。 对于 eslint,解决方案是增加 parser 和 parserOptions 字段; 对于 TypeScript,解决方案是 jsx 字段值改为 "preserve"。另外,不能使用 React 特有的“className”之类的属性,这个在 vue 文档中就有说到。

另外也大致明白了 jsx 在 vue 中担任怎样的角色。实际上,vue 本身就支持直接用 js 而不是模板语法写组件,其中就包括了 class component 和 function component。jsx 插件只是把其中的 jsx 转换成 h 函数而已。另外,这个插件不仅对 .jsx、.tsx 生效,甚至对 .vue 也生效,也就是可以在 <script> 标签中写 jsx 组件给 template 用。

2023-02-10

02-02 中写到,目前还没有做后端消息的 handler,所以传不到前端。因此目前在做的就是 serverEventsHandler。

在之前,由于使用 VueX,mutations 要调用另一个 mutation 是不行的,得用 mainVue 这种比较 hack 的方式。现在用 Pinia 之后直接 useStore 就行了。 不过 initializeServer 函数里有个地方需要 as Server。怀疑 Pinia 对它做了点转换,导致这个数据实际上是被深拷贝了一份,但目前不确定是不是这么回事(也有可能只是 ts 做了下转换),因此需要先把前后端通讯打通,验证这个问题。

打通前后端的过程中,因为涉及到具体的运行逻辑了,需要把 nodeBridge 的一些功能也做下适配(包括 preload),目前正在进行中。

2023-02-11

接通后端的过程比较顺利,几乎是一接通就能用了,很好。 (而且还能用老 FFBox 起服务器,在目前还没改后端数据结构的情况下,直接就开箱即用了)

然后是关于 nodeBridge 适配的问题。过往的代码中,renderer 还是带了很多跟 electron 或者 node 相关性比较大的代码,比如说直接在渲染进程 spawn 一个进程、用 remote 去 flashFrame 或者 openDevTools 等。为了实现分离,最好的方法是页面要假设不知道下层是 electron,所以这些功能就得都集合到 nodeBridge 里,而 nodeBridge 对接的 preload 也直接改成了 jsb 这个名字。

下一步就是 taskItem 的设计了。目前完成了 tasklist 的分离,仍需继续制作。

另外,目前的列表刷新机制好像会导致 vue 出现 internal bug,具体还需继续排查。

2023-02-14

今天先写了一下 taskItem 里参数展示的样式,然后为了使用 videoRateControl 这类 computed 值,排查了一下 internal bug 的原因。 猜测是因为我给 v-for 的 key 传了个对象,猜测正确。 于是找了一下以前的解决办法,是另有一个 computed 值,把 id 插入到列表中的每一个项。这个做法,是为了节省一个字段,😂很有“个性”🤔。

另外,如果使用了 computed,console 中出现了 Invalid VNode type: undefined (undefined) 的 warning,可能是由于忘记取 .value 所致。Vue 的错误提示在这点上十分不友好。

目前设计了四项信息展示,但此时的列表宽度已经非常高了,导致文件名没什么位置放。尝试使用老版 FFBox 的多行溢出属性,没成功,得后面继续调。

2023-02-15

继续调整 taskItem 的样式,加入了左侧图标和右侧操作按钮。 同时参照老版 FFBox 的 taskItem 设计重新实现阴影。当年为了实现个阴影套了几层 div,确实是对视觉效果的要求很高🙈。 比现在调半天还丑不拉几的我艺术感好多了。

2023-02-21

继续实现 taskItem 的功能,加入了 dashboardArea 和 cmdArea。Of course 所有按钮都还没做响应,然后根据元素宽度自动开关和伸缩某些字段的功能也还没做,所以现在看起来还是不太顺眼的状态。

途中发现了 CSS :has 选择器,直到去年 8 月才在 chromium 上线。这叫及时雨。

2023-02-22

往 store 中加了几个界面类的属性,还没写功能。因为静态界面开发太久了,想看看它交互起来怎么样。 于是着手去改一下 ParaBox 的设计。首先是分了文件,然后要把里面的 sidebar-icon 都换成 svg。 但就卡在了 svg 这一步:

样式异常

使用 Ai 直接导出 svg,样式会以内部 CSS 形式被放到 <defs><style></style></defs> 里去。而 vite-plugin-svg 将其转换为 vue 组件的时候,由于 vue 不支持在组件中使用 <style>,所以样式会被去掉。在 vite-svg-loader 的 issues 中,有两种解决方案:

  • 链接 中提到,可以在 svgo 设置中启用 inlineStyles 插件,把样式从 <style> 中移动到标签内联。但实际上,阅读文档后发现,vite-plugin-svg 默认就启用了 svgo 的默认设置。而默认设置中就启用了这个插件,但插件有 bug,没把样式应用到全部标签上,只会应用一次。这个 bug 在一个月前才被提出。
  • 链接 处,有人提了一个 PR,把 <style 替换为 <component is="style"。同样是在一个月前才被提出。

vue 的生态可谓是非常奇怪,发展了很久,但总是会遇到些新鲜热辣的问题,得等社区解决。

不过在上面提到的第一点中,下面的评论有提到,可以给 inlineStyles 设置 onlyMatchedOnce: false 解决。你说这是个 bug 呢还是个 feature 呢 = =。

如果排除上述解决方案,从 Ai 本身入手的话,就得在导出时设置样式为内联样式。这样一来会导致文件明显变大,另外要通过修改导出设置来兼容程序,说明这个工程的兼容性不好,所以不这么做。

不清楚把 vue template 换成 jsx 会不会有这个问题,明日再试。

颜色应用

在其他比较常见的 svg 中,整个图形是一个路径,使用 fill="currentColor",就可以实现外部使用 CSS 自定义颜色。

而在 Ai 中导出 svg,它是带了一个颜色的。有如下几种解决方案:

  • 在 Ai 里清除掉这个颜色。这当然不考虑,因为这样的话在 Ai 里直接就看不到了。
  • 对每个使用颜色的地方使用 CSS !important。彳亍,只是我不太喜欢。(要是解决了 style 标签被 vue 吞了的问题,就不需要这样做了)
  • 修改每个 svg 文件,把 stroke 手动改为 stroke:currentColor。麻烦点,可以减少文件体积。

2023-02-23

jsx 依然无法解决 style 标签被 vue 吞掉的问题。另外,jsx 若要给一个包在 {} 中的组件设置样式等属性,就需要用到 h 函数了,没有 vue template 那么“原生”。所以还是暂时换回 vue SFC。

另外,解决完上述所有问题之后,“输入”和“输出”图标依然是不正常的。这个锅我认为得让 Ai 背。因为这俩图形是使用路径 + 填色做出来的,Ai 导出后对应的填色标签上并没有任何属性,需要手动写一个 .cls-3{fill:currentColor;},给标签加上这个 class 才行。另外,这种图形在 Ai 里也无法设置复合路径。

解决了 svg 的问题后,继续调整 devider 的样式,加上了全局参数的展示器及展开/折叠按钮,给 devider 加上了拖拽调整高度,顺便做了少量读屏器适配。

2023-02-26

补充了一些内容,包括 TaskItem 的“预计剩余时间”占位、其 cmd 选项卡的点击响应及样式优化、Parabox devider 的切换。做到这一步,就要考虑引入 Parabox 了。

Parabox 的引入是个大项,因为一下子就要把许多东西引进来,包括子参数盒页面、所使用到的控件、控件里所使用到的 Tooltip。普通控件需要对它做 vue2 选项式 API 到 vue3 组合式 API setup 的转换,Tooltip 则还需要去看如何凭空创建一个 Vue 组件出来。 本打算按照老经验去看一下 Element Plus 是怎么写的,结果 npmmirror 服务器 502 了装不下来,便去 GitHub 直接看代码。发现它的设计似乎比以前复杂了许多,还没时间去弄懂它,只看到了一些最基础需要用到的代码。 然而,引入之后,一方面是遇到了文件引用未找到的问题(暂时通过移动文件解决),另一方面,解决完其余问题之后,Tooltip 依然是不出现的。还得继续去找原因(有可能是漏了某行 context 的代码)。 已经找到了文件引用未找到的问题的原因。vite 配置中 resolve 漏了 tsx 的文件后缀。

2023-03-06

上次的问题,Tooltip 不出现的原因是,vnode 的 props 是个只读属性,直接修改是不行的,需要使用 vnode.component.props 进行修改。简单来说,在 DOM 上新增元素挂载 Vue 组件的方法分为三步:1. 使用所需组件创建 VNode。2. 创建 DOM 节点。3. render。 显然是对 Vue3 的原理不太熟悉,还需后期继续学习。

另外,解决问题的过程也没有一帆风顺,其中就遇到了 dev 模式弹不出来窗口的问题和编译失败的问题。dev 模式出不来窗口是个低级问题了,把 debugger 语句留在了 first render 里;编译失败则依然是 vite 的问题,在编译模式下,尽管跟开发模式使用同一份 config,但生产模式下,resolve alias 读不到,需要使用 path.resolve。

还发现了 Vue 2 到 Vue 3 的一处不同:Transition 组件所使用的 CSS 名称发生了变化。变换起始的类名后面增加了“-from”。解决了组件迁移后只有部分动画的问题。

2023-03-09

最近一段时间在迁移 Parabox components,目前已经基本上迁移完成了,不过由于还没打通前端到后端的参数传输,所以看不到实现效果对不对。回看日志可知,做这块工作的目的是为了尽快完成软件主要界面的功能。为了继续开发,下一步就需要去写参数传输到后端的这些逻辑了。

正在做参数的保存。因为有了 Pinia,所以简化了一下设计,直接修改 globalParams 然后通知 store 去做应用就行了。此时就遇到了需要使用 electron-store 的场景。 由于打开了安全设置,在渲染进程中,getEnv 始终会返回 browser,故考虑在 preload 引入。 但 preload 引入其实也是不行的。根据这个 issue,preload 只允许导入少量 electron 功能。所以存储的功能还是得让 jsb 去做。 同时,nodeBridge 也是可预见到的要大改。因为 nodeBridge 是按照原来可以直接在渲染进程获取到 node 模块的方式设计的。现在很多东西都要用主进程去做,所以得改。 另外,做 electron-store 的适配也并非一帆风顺。比如中途就遇到了调用 ipc 的时候报错 An object could not be cloned. 的问题,这是个无厘头的错误,解决方案是刷新一下页面。彳亍,vite 的 HMR 还是有点 bug 在的。

2023-03-12

为了测试参数盒的运行效果,还需要让任务可被选择。因此最近给它补上了任务选中的相关逻辑,设计了一下样式进行适配。可喜可贺,这块工作一次便成功。

2023-03-15

完成上述工作之后,下面就该把参数盒的东西补全了。目前正在进行第一个模块 VcodecView,做到 rateControlList 的时候,涉及到一些逻辑操作,而目前的 vcodecs 还是 js,需要把它转换成 ts 才好操作。同时也进行了一些 var -> let/const 以及可选链等等的优化。 AcodecView 也照猫画虎照葫芦画瓢做好了,由于东西比 VcodecView 少,所以 acodecs 可以不转 ts,只会产生 1 个错误。不过,这种做了一半的不对称开发必是我不接受的,明天就把 acodecs 也转 ts ╮( ̄▽ ̄)╭。

And,在做第二个参数盒之后,我顺便也把 transition 给重做了。现在看 Vue 文档显然比 4 年前看文档要少吃力,所以终于会用 transition 做条件动画效果了,做了点细致调整。主要是由于把上下动画换成了左右,跟阅读方向平行,如果还按原来那种速度和幅度左右渐变,似乎没那么舒服(感觉在抽动视线),所以对进出的动画动单独调整了。

另外,之前遇到的 An object could not be cloned. 现在可以稳定复现了。这次是出现在 set 的时候。Proxy 无法直接 ipc 到主进程,需要 JSON.parse(JSON.stringify) 一下。

除此以外,目前也发现了 Slider 组件不能按预期工作(估计是坐标检测问题)。后续会把它的问题修正。

2023-03-16

完成了剩余参数盒的设计,包括 acodecs 的 ts 转换。ShortcutView 我也想到了 RadioList 的设计。

修正了 Slider 和 Checkbox 的 bug、三个 View 没写对参数的地方。

列完待办项后,打算做添加服务器。然后在思考界面设计的过程中,发现了全局参数的 TextField 是写死的数据。将其转换为真实数据的过程中发现 upath 故障。经过对它源码的查看后,发现它里面有一些方法是直接调了 node 的,在 web 环境中就会失效。所以考虑使用 path-browserify 给它做一下 polyfill。

结论是:upath 并不带有浏览器环境的支持。虽然可以通过安装 path-browserify 在浏览器中使用 path,但 upath 并不具有任何可以传入 polyfill 的设计,其内部调用都是假设能 require 到 path 来设计的。在 npm 里找到的没有文档的 upath2 也是同理,均无法使用。 为什么要用 upath 呢?因为它附带了 trimExt 等等 path 里没有的函数。所以我在 utils 里手动拷贝了它的源码,改了一部分,才成功启用。 只不过这样并不优雅。本来能用 upath 一个模块解决的事情要分到 path-browserify 和 utils 里,而且类型检查也不完备了。 但在目前 upath 作者停止维护的情况下,只能先这么做,以后再考虑把它收拾干净。

待办项:

  • TaskItem 的多种视图模式。其中详细模式根据参数自动控制显示列,根据运行状态显示 dashboard(可考虑完成之后这里变成图表,后续的事情了)
  • ShortcutView 的存储实现
  • 按名称搜索功能(粤语)(后续加上按分辨率、格式之类的筛选功能)
  • 任务开始、任务上传
  • 大按钮菜单(打赏中心、设置(包括夜间模式、单位))
  • 预计剩余时间
  • 添加服务器按钮(包括本地服务器未加载出来时的样子)
  • 关闭软件二次确认
  • 底栏状态信息(加入网速信息)
  • msgbox、popup

2023-03-18

关于添加服务器的登录界面要长什么样式,我想了一个大致方案:做一个蒙层放在 ListView 的上面。同时,服务器断线也要有这么个蒙层在上面。 那既然都是蒙在这个地方,不如就顺便重新设计一下 ffmpeg 未找到的样式好了。 一开始是想到了一个 ffmpeg 图标后面加几个点和问号。但这个设计方案总感觉有点太过正经,作为一个个人软件没必要这么拘谨,便想用一些表情包的图放上去。然后想到了“快乐,啪,没了”,用 AI 做一下描摹之后发现根本看不出来形状了,于是改用小蓝。小蓝并没有一个官方图库,只能在网上搜集尽可能清晰的图片,然后做放大,再交给 AI 描摹。预计在这个 commit 里会提交一版彩色的,在下一版去掉一些 fill 再提交。

另外,在思考蒙层形态的时候,我打算把它做成 Msgbox,不过 container 可以自行指定而不是默认全屏。这一步就涉及到设计一个适用于 Vue 3 的模态弹窗组件了。 一开始我没看 Element Plus 的源码,就按自己的认知来写,因为关于 button callback 要如何回传并且关闭弹窗这步,是要自己想的。 重点是关闭弹窗,~~老版 FFBox 实际上并没有做好相应的设计,因为当时写的 close 函数就是把组件内的 v-if 关掉,然后把之前被放进列表的 instance 从列表中去除。这其实就是当年 js 功底不过关的表现了,因为这个列表是自己声明的,组件卸不卸载并不取决于这个自定义的列表。换句话说,根本就没有 unmount。~~老版 FFBox 是先把组件内的 v-if 关掉,然后在 transitio 的 afterLeave 里 $destroy,然后找 parentNode removeChild。这个销毁自身的方式我想为什么在 Vue 3 被去掉了呢?我认为是,这个组件应该由控制创建它的地方去控制它的销毁。否则,如果它在节点树里出现,但它又被销毁了,那就有点奇怪了。 另外,老版弹窗没有异步操作设计,点完按钮就 close,这个设计也并不够现代。

为了解决卸载问题,我去搜了一下 Vue 3 如何卸载组件。得到的结论是 render(null, container) 就可以了。我总觉得不放心,这样解绑是解绑了,会不会把 container 里的所有组件全给清掉?VNode 还在不在? 我在组件里加了个定时器,对比点击卸载前后的 props 变化。结论是:没有变化。 我尝试在组件里读取 context,看下点击卸载之后里面的 context 会发生什么变化,然而 script setup 本身就不太能拿到完整的 context(也就是说拿到的可能至少也是被裁剪的 context,而不是原来的 context)。 最终在组件里面写了个 onBeforeUnmount,发现点击卸载的时候这个函数会被触发。那彳亍,既然你 Vue 说卸载了,那我也就认为卸载了吧。 至于 Element Plus,我后来看了下它的源码,感觉相比于 Tooltip,它的改动没有那么剧烈,还是能看到以前的一些影子的,比如说那个 instance 列表。至于它是怎么 unmount 的,因为它用了 Element Plus 的 hook,那块我没细看了。但估计也是控制里面的 v-if 关闭,等动画结束之后就 unmount 吧,不清楚了,反正现在可用了,可以去设计它的样式了。

2023-03-19

昨天在做 Msgbox 的时候,又遇到了尤雨溪生态的一个问题,服[抱拳] 这个问题是,在 <script setup> 里使用 defineProps 指定类型的时候,不能使用导入的类型,或者 type,否则 vite 报错。如果不指定类型,那么 vue 层面上也接收不到 props。 就很神奇,为什么 js 层面上运行的东西要去管用于代码提示的 ts 呢? 根据这个issue,装上 vite-plugin-vue-type-imports 之后可以解决使用导入类型的问题。但这种东西,竟然到现在还没被正式纳入 vue 中,太奇怪了。

今天就遇到了另一个应该跟尤雨溪没有关系的 bug。在 devTools 选中元素的时候,概率(按组件)性触发像 [31216:0319/183752.527:ERROR:CONSOLE(0)] "[formatjs Error: MISSING_VALUE] The intl string context variable "REASON_PROPERTY_DECLARATION_CODE" was not provided to the string "请尝试移除 {REASON_PROPERTY_DECLARATION_CODE} 或更改其值。"", source: (0) 这样的报错,同时样式窗口不更新,没法调节样式。谷歌上完全找不到类似的问题。但根据它报错的位置来看,应该是 electron 的问题。把 electron 版本更新到 23 后问题解决。

说没又有了。vite-plugin-vue-type-imports 虽然能解决部分问题,但那是在 vite dev server 启动成功后新写的内容可以用,要是把 dev server 关掉再打开,vite 就卡死了(Ctrl C 都没反应)。直接 build 也会卡死。 于是试着不要用 SFC 了,换用 tsx。问题解决。 这还是头一次遇到 template 完全无法解决的问题,还得借用 React 的魔力。 有一点变啰嗦了:Transition 组件目前我还没找能较好适配 less module 的方法,不能像 template 组件那样传个 name 进去就能自动生成所有类名,而是要对每个类进行单独指定。等以后有空了再看看有没有简洁一点的。

2023-03-20

虽然 03-18 的开发日志里写了想把蒙层做成 Msgbox 的形式,但其实只是个样式上的设想。从逻辑上来说,我还是打算把它设计成一个普通的 div。先开发 Msgbox 主要是基于样式考虑,完成之后可以直接把样式拷贝过去。 因此周末主要时间花在了调 Msgbox 的样式上。调出来的结果我还是比较满意的。

另外,今天我也把 Msgbox 里的 Button 独立出来了。目前正在开发的是登录页面。

2023-03-21

做了些代码修改,以支持添加服务器的按钮,然后去做登录界面。 然后发现做出来的样子比较丑。于是给按钮加了点功能,支持小中大三种按钮尺寸。依旧没有解决丑的问题。

2023-03-22

给两个 InputBox 加上了边框,上面加上标题之后,丑的问题就解决掉了。 但在目前功能尚未完善之前,无法从界面上看到目前的状态。于是转而去做 TitleBar 的逻辑和样式了。 花了点心思去做背景颜色和阴影之类的细节,以及新增和关闭标签页时弹一下的动画。

大致思考了一下,FFBox 还需要一个掉线界面,于是我给它新增了一个 loading.svg。这个 loading 是我从 iconfont 上能找到的最均匀的了。什么意思呢?就是说我第一次找的 loading 让它转起来之后发现它有些杠画歪了 = =。另外,从网上找到的 loading 不会有转圈效果,我需要的是像秒针那样一格格动的效果,于是又手动改 svg——把它放到 AI 里释放复合路径——导出 svg,改颜色。然后在 animation 里加上 step 就行了。

2023-03-23

登录窗口和掉线提示这个花了好几个小时去做,因为没想好服务器的生命周期、几个窗口之间的显隐和堆叠关系,来来回回挪腾了好多遍才让它在正确的位置出现。再加上 vue template 非根组件不能用 Fragment,做到现在也仅是让它能渐变出来对应窗口而不能渐变退去。但是不弄了,就这样吧。

FFmpeg 缺失的提示窗很快就做出来了。感觉目前的样式还挺好看。

2023-03-24

完成了全局任务开始暂停的控制,及 TitleBar 上 Tab 进度条的样式。不过,那块的代码还没有用心去做,理论上应该把每个 Tab 独立成一个组件的,目前的逻辑我感觉有点低效。用性能监视器也能发现这个问题,就这一个 Tab 就把每秒的样式更新数从 20 升到了 60,同时 DOM 节点数还在不断增加(内存泄漏)。等有空了再去优化这里。

同时顺便给开始按钮加了个灰色的状态,在不能操作时给它禁用掉。

另外还需要做任务项里的进度。在做这一块之前,我打算先把 TaskItem 的样式先搞一搞,不要让它像现在那样显示一大坨。在我空闲的时候,我其实想过这块要怎么做:遍历任务列表,只把有修改的值显示出来,以节约横向空间。同样,我感觉 Vue 不一定能按我的预期工作,于是我用 console.log 在计算的时候打印一下。好家伙,TaskItem 的每次进度更新都会把这个值重算一遍,这个造成的性能开销就有点大了,到时候再看看有没有方法把这个计算函数放到别处。反正这个函数的目的是为了更改 appStore.taskViewSettings,并不是要把它做成 computed 值。

2023-03-25

今天的工作量比较多。

首先是试了一下怎样才能让前一天所述的 changedParams 仅在实际发生变化时重算。使用 computed 或者 watch 的方案应该是走不通了,因为估计是任务进度更新涉及到 task,一串下来,vue 可能认为它邻居也发生了变化,于是重算。不过这仅是猜测,我目前还不清楚 Vue 是怎样认为依赖列表里的东西发生变化的,这个问题就有点像“react 子组件更新是否会导致父组件重渲染”一样。最后的解决方案是在 store 里新增一个 recalcChangedParams。至于这个函数什么时候要被调用,是挪腾过几次的。原来所认为够用的 boolean 也被改成三个值的枚举,因为要适配“禁用”和“不更改”的情况。 得益于可以隐藏部分参数,参数列表终于可以做得不那么紧凑了。

接下来,就去改了是否显示仪表盘的逻辑。以前定的是参数、仪表盘、命令行三个都可以单独让用户开关,现在看了一下,这样做会导致各种高度计算场合变复杂,所以就让仪表盘随任务状态开关,只留一个命令行让用户选择好了。

有了这两块改造,文件名终于可以不那么憋屈了,可以有更宽的空间。所以我给它加上了计算可用宽度的逻辑,以及是否放大文字和折行省略号显示的逻辑。

然后突发奇想让它适配超窄宽度屏幕(手机)。幸运的是因为以往写的时候就注意把平台特有功能分开,所以基本上搬到浏览器上直接就能用。不幸运的是发现移动端还有挺多问题。 比如说在 Chrome 里,画面底部被切掉了。可能是 Chrome 认为它的标题栏能随页面滚动被顶上去所以预留了这部分空间所致。 在夸克里,画面底部没被切掉,但是几乎所有颜色都不能显示了。看来是它版本太低,不支持 hwb 的缘故。 然后还有不论任何浏览器都有的一个问题:参数盒的拖动器不能用了。加上触屏事件监听之后会发现它能拖了,但是又点不了。touch 相关的事件里 preventDefault 似乎会屏蔽掉 click 事件;但不 preventDefault 的话,遇到夸克这种具有下拉刷新逻辑的浏览器,就会被浏览器行为给覆盖掉。至于 touchend,虽然能用,但是 css 按下的效果就会因为被 preventDefault 而不触发。最终决定用的是夸克不友好方案。

完成以上优化之后,终于能做任务项的进度条了。因为 linear-gradient 不支持动画渐变,所以使用多个进度条叠在一起控制透明度实现(跟前一天做的 TitleBar tab 同理)。这个并没有增加什么性能负担。另外,老 FFBox 的阴影样式直接搬过来之后有点丑,所以我又调了一下阴影,加了一些细致到 0.75 px 的阴影项。

最后试了一下整体效果之后,还想优化一下命令行那里,让它在用户滚到底时能保持最底。但是这个目前实现失败了,vue 监听不到命令行的文本变化(尽管渲染出来了)。初步估计是因为 cmdData 是个普通的 string,而不是响应式 Proxy 所致。我打印了一下 task,发现有好多属性是 Proxy,好多属性是普通值。这下有点难搞了,为什么有些值是 Proxy,有些值是普通值呢?响应式是在哪一步赋上的,在哪一步丢掉的?我感觉这又是 Vue debug 难以捉摸的一个点。

待办项:

  • TaskItem 的多种视图模式、闪烁提示
  • ShortcutView 的存储实现
  • 按名称搜索功能(粤语)(后续加上按分辨率、格式之类的筛选功能)
  • 任务上传和下载
  • 大按钮菜单(打赏中心、设置(包括夜间模式、单位))
  • 预计剩余时间
  • 关闭软件二次确认
  • 底栏状态信息(加入网速信息)
  • popup
  • 标签页性能优化
  • 命令行界面自动滚动到底
  • 各种 utils 整理
  • 后端 progressLog 信息改造(前端自由选择截取多长的数组,以及数据无变更时不 push 列表)
  • 通知机制改造(脱离 task)
  • 重连服务器后要刷新列表

2023-03-28

03-25 那天,我花了 12.5 小时坐在电脑面前,其中大部分时间都在写 FFBox。这种程度的努力,也就只有大学时期能达到了吧。

实在不是很愿意在开发日志中写入这种个人的内容。可是我已经有不知多少年的时间,都没有一天在电脑面前坐超过 12 小时了。不同于当年的我在努力地追赶因学校课程和 onestop 被浪费的时光而去坚持做的事情,这次的 12 小时,更多的只是在如机器般消磨时光而已。

世间万物,仿佛来到我身边的,都陷入了持久的沉寂;或是经历了漫长的路途后,喜悦地来到了终点,然后发现是个断头路,前方再也没有去向。

整整两周,我都在埋头干。干公司的活,干 FFBox 的活。有限的任务就摆在眼前,我只想赶紧把它们干完,早点熬过这周,就能去广州玩一下了。

我每天都在等。每天都在等。

原来,哪怕希望再小,也是能破灭的。只要我在的地方,都会得到上天安排的神罚,阻止我或者我身边任何一个人去实现计划。

不止是广州,不止是一次。事情不多,可是,很多,很重。大至几百万,上千万的人,会因为我引起的暴雨、疫情而损失了我所看不到、统计不到的项目;小至一个人、几个人,会失去他们心心念念的旅程。

我不知道我要做什么。我的梦想,不是面对这些电脑,而是那些与人、与世界的一点一滴相关的事。FFBox,只是我“拯救世界”的一环,可面对这繁杂、无尽、野蛮发展的现实世界,它的存在变得十分地渺小。

还未复活的个人网站、尚未开始的播放软件、早已失去兴趣的直播升画质存档……哪个不是我心心念念的?

可我不是 CodeGPT,我是人类。可我只是被命运困在了一个半透明的空间内,只能偶尔能羡慕一下人类的生活。暂时,还不像一个人类,也看不见,什么时候,能当上一个人类。


最近两天,做的内容是文件的拖拽上传和下载。这块内容同时也会涉及到 dashboardTimer 的引入,也都做了。整体上还是比较顺利的。

2023-03-29

做了上传状态的 Dashboard 适配,增加了“文件大小”和“传输总量”这个仪表盘项,调了一下尺寸。下载的事件还没正确传回,所以能下载但是界面上没反应。

2023-04-03

下载事件传回代码迁移在 03-30 做到一半,就跑去广州了,简单解决了一下地球危机,2333。

剩下的内容在今天补完了。主要是需要在 App onMounted 里挂载事件。做了一点工程上的事情,将相关函数转移到了 eventsHandler 里面。另外,03-28 的日志里也没提到一点,就是将 dashboard 的相关函数整理到 dashboardCalc 里面。

另外,在进度那里增加了 size 一项。现在可以在转码过程中看到输出文件大小了。

2023-04-04

昨天在即将完成的时候,FFBox 突然出了点问题,导致前端 hwaccel 那里报错,后端无法开始任务。前端修复代码、把配置文件清空均无法恢复。结果拿老版 FFBox 运行一下就解决了。╮( ̄▽ ̄)╭

今天开发的内容是“预计剩余时间”的显示。不过,为了开发这个功能,做了一些额外的改进。 首先,为了计算预计剩余时间,之前没做好的进度显示就需要优化,也就是需要在进度计算那里加一个通用函数用于计算按时间剪裁后的输出时长。 在做这块的过程中,由于涉及到时间表示字符串和时间数值这两者之间的转换,也需要做一个通用函数去进行。此前的 getTimeValue 只能处理一种时间格式。现在改名为 parseTimeString,按照 ffmpeg 对时间的识别逻辑进行了改造,且应用到 Inputbox 等地方。这样前端的计算结果就能与 ffmpeg 同步。 另外,老版 FFBox 可以显示媒体时长,新版我还没找到地方放进去。于是,我在 taskItem paraArea 那里开了个项,这样就能在一个地方显示输入时长和输出时长了。在做这里的时候,同时也优化了一些操作按钮和 paraArea 的位置,还有 divider 的插入逻辑。 以上内容开发完成之后,看到效果,感觉不错。看起来,软件的功能已经完成了 70% 了。

最后一点想实现的是 dashboard 的“时间”和“帧”按窗口宽度决定是否显示。由于涉及到计算文件名可用宽度的逻辑,窗口宽度需要在 js 层面上监听。监听是好解决的,但解除监听就难办了。TaskItem 使用 Functional Component 实现,它不能使用 onBeforeUnmount 这类钩子,因为它没有生命周期。 这就导致了我需要通过在 TaskItem 里另外创建一个组件,监听该组件的卸载事件,以进行卸载操作。 但 unmount 能用了并不代表事情结束了。在 DOM 里移除监听器需要传入添加监听器时的函数,而这个函数竟然是不能保证拿到相同的。 什么意思呢?这是我第一次发现 Vue 的 render 函数是每更新一次就调用一次的。此前我认为 render 函数是只会触发一次,收集依赖,后续直接在里面的 VNode 更新。好家伙,用了这么久的 Vue,这竟然是第一天才知道这一点。 那我把函数装进 ref 里总该行了吧?实测是不行的。mount 时拿到的装进 ref 里的函数,跟 unmount 里拿到的不是同一个,而这是连使用 watch 都监听不到的引用变化。 至于把函数丢进组件外面这个方法,其实也不可行。因为要在函数里使用一个组件内的 ref,这个没法丢出去。 目前实在是想不到什么好的解决方法了。网上说函数式组件不常用,它一般出现在比较简单的组件上。那后面考虑把 TaskItem 改用 class 组件实现一下再试试行不行吧。class 组件应该会有生命周期了。

2023-04-06

今日完成了 popup 的迁移,顺便微调了一下它的动画。

然后做了退出前检查。在这过程中也添加了一个小蓝表情包图片。然后把之前写的 ButtonRole 改成 ButtonType,这是为了把 role 留出来做 Msgbox 的键盘事件响应。不过现在它不能响应 Escape 键,还得查查原因。同时,由于 FC 组件的问题,现在应该也是做不了 unmount 事件的。

除此之外,在做的过程中尝试整理一下 trimExt 这个函数,但是发现了问题。这个函数在 common 里被 getFFmpegParaArray 调用到,而这个函数同时会在后端和前端使用。而又因 path 这个东西好像没法在编译时决定引入哪个,所以后面还得搞搞它,不然编译不起来。

2023-04-19

稍微做了一下整理操作。

比如说上面提到的 trimExt 之类的东西。我把实现改成了无论浏览器还是 node 都统一使用 path-browserify;同时为了简化引用,我把 trimExt 和一些 path 相关的功能独立到了 path.ts 文件中。这样一来,就不需要 upath 了。

另外,把 vcodecs、acodecs、formats 都移动到了 params 文件夹,并把共同的 types 独立成文件。formats 现在也转换成了 ts。

global.d.ts 和 types.ts 里去掉了一些未引用的东西。

最后想尝试一下改了这么一轮后现在的后端还能不能正常编译。结果是——vite 可以,webpack 不能。 webpack 把 renderer 的 ts 错误都找了出来。这就非常奇怪,因为我找了一轮,后端里并没有引用前端代码的地方,就连 webpack config 里也没涉及到 renderer 相关的东西,但它就是关心。 目前认为应该是 webpack 在处理前先把代码给了 ts,tsconfig 里配置了 include,然后就一股脑全编译了。 anyway,把那些错误修正好之后,webpack 就能编译了。

2023-08-14

很久没拿起来过 FFBox 的代码了,一上手其实并不太记得要做什么。

之前遗留下来的一个问题是 FFBox 后端不能正常使用,只能把旧版的 FFBox 启动起来给现在的前端调试。由于这里涉及到项目重构,所以出现这种问题有可能是由于重构时某些代码没改好所致的,不好定位,容易沮丧。如果说为什么前面的开发工作中断了那么长一段时间,这个也可算是其中一小点原因。 但其实调试起来并不困难,一下子就找到问题所在了。ws 这个库在更新之后,接收消息的 api 发生了变化,这里便有人提出了这个问题。改一下就好了。

另外在做这个之前,我想随便放点任务进去跑一跑主流程,看看哪里出问题。我打开了控制台,看见了 InputBox 的 warning,便给它修了。另外也把 typeCheck 和 notNull 这些比较散的校验逻辑做成了更现代一些的 validator,另外还加上了 fixer,是一些「本想放到小版本,但既然都想到了自己也在用那就做了」的优化。

不过最重要的还是把后端搞能用了。所以等把前后端关联启动做好了,就可以直接自己先把这软件试用一段时间了。

2023-08-17

我是一个有强迫症的人—— 比如说在分支管理上,我就喜欢整洁、一致的。 FFBox 刚开始做的时候,我还不会用 git,所以一整个版本下来其实才一个 commit。 工作之后,这个习惯肯定就不是这样了嘛。 那要怎么反映到 FFBox 上呢? 作为一个有强迫症的人,我可不接受东西做着做着突然就变了。得等到新版本开始,在 git graph 上用另一种颜色的线来呈现。 所以直到现在,在 FFBox 上还是使用着做两三天内容才发一次 commit 的习惯 /doge

上面是题外话了😂。只是在做的时候发现,有些东西我本该一年前就做了。咕到时代都变了还没发得出来一个版本,有感而发。 这句“有些东西”,就是指今天在做的由前端启动后端的机制。 公司的项目恰好也使用 electron 前端 + 某语言后端的机制去做(并不完全是“恰好”,不细说)。那众所周知,“工作只是用时间和生命换金钱,摸鱼才是赚到”,要是能在工作中学自己要用的东西,那也是赚到。在设计这套前后端模式的时候,我就把 FFmpegInvoke 里的一些函数抽了出来,做了个 processInstance 用于把 node ChildProcess 包装成一个更易操作的对象,同时附加输出解析功能。一年过去了,这东西终于可以引进 FFBox 了。不过这样就跟 FFmpegInvoke 有些功能重复,看后期有没有方法让 FFmpegInvoke 也用上它。目前的问题在于暂停进程功能需要用到 osBridge,这玩意就并不是在后端和前端主进程都有的东西。

然后就可以着手把启动后端的功能引进代码里。看了一下 3.0 的做法是由渲染进程通知主进程启动,现在也可大致这么做,方便当后端崩了(虽然一般不会崩)之后由前端主动点击重启。

2023-08-18

昨天做的时候就遇到一个问题:后端在打包后会放在哪里呢?想到这点之后我开始打包。结果——打包失败。 ——挺搞心态的,就不能让我好好写个程序😇

第一个问题是:启动 exe 后主进程报错:cannot find module 'conf'。谷歌上似乎完全找不到这个问题。 回退一些版本之后出现了第二个问题:渲染进程报错:Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".

第二个问题总结出来的原因大致是在 js module 里不带相对路径引用“vue”找不到,于是我试着把 node_modules 复制到生成出来的包的各个目录下,还是找不到。 关于这个问题,有人说是 pinia 的原因,但我实测不是。实际上,我把版本一路倒回去,发现“完成项目初始配置(目录结构、配置文件)并打通编译打包流程”这个 commit 其实并没有完全打通全流程,只到了编译出来一个白屏 exe 之后就停止了😓。 于是我今天开始找之前用第三方框架弄的项目。然后发现草鞋没号electron-vue-vite 打包出来同样不能用,只有 electron-vite 是能用的。 问题出在哪呢?我开始对比 vite config。但实际上我没去“对比”。因为我发现了配置项里的 external 把 package.json 里的 dependencies 全排除掉了。用不了 vue 有没有可能就是因为生成出来的包里没它呢?我试着把它注释掉,成了。版本往前几个之后又遇到了找不到 events 的错误。把那句 ...builtinModules 注释掉也解决问题了。 笑死,原来以前从来没真正跑通过。

解决第二个问题之后我就继续去解决第一个问题了吗?笑死,并没有。 因为近期遇到了班尼特事件,各种电脑硬件损坏。我把自己电脑的外存移动到公司电脑上用,然后今天就出现错误了——后来 chkdsk 给它扫出了上万个错误,编译打包什么的跑不通,因为 node_modules 里的东西出了问题,pnpm 缓存也出现了问题。我只能说,幸好我自己写的代码没出事😇。 6,半夜不下班搞这个。

完成修复之后就继续去解决第一个问题了。解决方法跟第二个问题类似,反复切换以前的 commit,看下从哪开始有问题。 定位到开始有问题的 commit 是“nodeBridge 直接调用改 jsb 调用;任务项支持选择;acodecs 和 vcodecs 转 TypeScript;参数盒动画效果、各页面实现并支持参数存储”。罪魁祸首其实是 electron-store,其实直接在报错信息里就能看到了,只是有点困,一开始没去看。 解决方法也非常类似——把 vite config 里的 ...Object.keys(pkgJSON.dependencies || {}) 去掉就行了。 俩问题都是同一个原因

所以说,以前究竟是碰到了什么问题要在 vite config 里把依赖排除掉🙈给自己挖了个大坑。

2023-08-19

今天要解决的一个剩余需求就是渲染进程初始化后通知主进程启动后端。

首先的操作是修改了一下 electron builder 的配置,让它可以不把 .map 打包进产物里,然后对后端 exe 进行一个移动和重命名。这步没什么问题。

然而,当我回到前天的进度,在代码里接通前后端的时候,就遇到了一点小问题😓——(你说为啥写个软件能出这么多问题呢 因为我的设想是前后端进程分离运行,前端或后端崩溃不影响另一方。然而,当我设定启动参数为 { detached: true } 后,会遇到启动时瞬间弹一个黑窗的问题。到 google 上搜了一下,这个问题竟然已经放了几年都没人修复。详见 issue。那只好暂时先按保持原逻辑,让它以不分离的形式运行好了。

至此,我这几个月以来的第一个 commit 终于完成了。虽然实测编译出来的东西并不能正常进行转码,还老是崩,但不重要了,这个 commit 主要负责把编译启动流程打通就够了。

2023-08-20

解决了一个会导致任务跑不起来,ffmpeg 提示 Invalid Parameter 的地方:反斜杠问题。因为之前把 path 的引用统一为了 path-browserify,而这个库似乎不支持 Windows 的反斜杠,所以要在引用前加个转义。 理想的方法是在后端加转义。若要在后端加的话可以加在 getInputParam 里,对 inputParams.files[0].filePath 进行转义。但是这个函数我感觉的位置可能得有点深,应该在浅一点的位置加?所以我就给它加在 appStore.addTask 上了。反正 Windows 用反斜杠作目录分割符实在有点另类,不如直接在入口处给它统一了。

2023-08-21

今天凌晨睡觉前,用 mac 试着跑了一下。整体还是比较顺利的,不过还是发现了问题🤷‍♂️。 这个问题跟平台无关,因为实测 Windows 也这样。具体就是上传文件会失败,原因是依赖库升级了,interface 变了。改过来就好了。

解决了另一个问题:远程任务进度不更新。原因是在第一次连接不上再次连接等场景时两次调用 initializeServer,挂载了两次 eneity.on 响应事件,导致 handleTaskUpdate 那里把 dashboardTimer 创建又删除了。解决方法是把事件监听的时机放到连接后,并且在断开后清除事件监听。至于 handleTaskUpdate 的逻辑,暂时不动。

修复了 mac 下 forceKill ffmpeg 导致崩溃的问题:没区分平台使用了 taskkill。

给 vcodec 增加了 videotoolbox 的编码器,然后做了些其他调整,比如说加个 level 参数。人傻了,ffmpeg 说明里有些地方参数是 int 类型,有那么两三次报错我以为传字符串过去不行,于是把一堆参数改成了 int。然后一想,这些 int 的参数以前是怎么通过并起作用的?有没有可能 ffmpeg 自带了 string 到 int 的解析器?彳亍,改回去吧。 另外,-profile 也根据 ffmpeg 的推荐改成了 -profile:v。 nvenc 的 preset 补充了 p1 ~ p7。

2023-08-22

FFBox 差不多该进入功能性验证的阶段了。所以最近主要都是在修可用性,包括跑通流程这些。今天打算把自用的 FFBox 快捷方式换成 4.0+ 的,结果那个后端有问题,还是在很久之前写的那种一秒一刷的状态。原来是 pkg 命令输入的是以前用 webpack 打包出来的产物,现在改用 vite 了,改过来。

生产环境下的一些图片会打不开,比如说画面中间拖入文件的提示。没有这个的话体验可挺不友好,所以还蛮容易注意到的。经过排查,目前得出关于资源引用的结论是:

  • 在 style 里通过 url() 引用的图片
    • 使用 ./src 的相对路径引用
      • 开发环境:从 renderer/src 目录出发查找,由于其本身就在 dev server 的根目录所以能引用到。
      • 生产环境:会从产物中开始找,拼接代码的原路径,所以找不到资源,就不会放入产物中,引用也引用找不到。
    • 使用 /src 的绝对路径引用
      • 开发环境:从 renderer/src 目录出发查找。
      • 生产环境:查找方式不变。打包后图片和 CSS 中的引用均会加上 hash,放在打包产物的 assets 文件夹(与 js chunk 同级),然后在编译产物中的对应位置替换路径为 ./图片
    • 使用 src 的路径引用:与 ./src 的相对路径引用表现相同。
  • 在 script 里通过行内 CSS 通过 url() 引用的图片
    • 与上面的逻辑相同,但编译器扫描不到,因此仅在开发环境可用;生产环境下不会拷贝文件,无法引用。
  • 在 script 里通过 src 引用的图片
    • 使用 ./images 相对路径引用
      • 开发环境:会从代码所在的位置出发查找。
      • 生产环境:会从代码所在的位置出发查找,打包时加上 hash 作为代码相关文件放入,然后在编译产物中的对应位置替换路径为 ./图片
    • 使用 /images 绝对路径引用,
      • 开发环境:会从 renderer/public 目录出发查找。
      • 生产环境:会从 renderer/public 目录出发查找。
    • 使用 images 引用
      • 开发环境:会从 renderer/public 目录出发查找。
      • 生产环境:vite 编译会报错。Rollup failed to resolve import "images/node.png" from ".../components/Combobox.vue?...". This is most likely unintended because it can break your application at runtime.
    • 使用 @renderer alias 引用(需要按之前的经验加上 ?url 后缀)
      • 开发环境:然后根据指定的路径进行查找。
      • 生产环境:然后根据指定的路径进行查找,打包时加上 hash 作为代码相关文件放入,然后在编译产物中的对应位置替换路径为 ./图片
  • 在 script 里通过 :src 引用的图片
    • 开发环境
      • 使用 ./images 相对路径引用
        • 开发环境:会从 renderer/public 目录出发查找。
        • 生产环境:会从 renderer/public 目录出发查找。
      • 使用 /images 绝对路径引用
        • 开发环境:会从 renderer/public 目录出发查找。
        • 生产环境:会从分区根目录开始寻找,无法使用。
      • 使用 images 引用
        • 开发环境:会从 renderer/public 目录出发查找。
        • 生产环境:会从 renderer/public 目录出发查找。
      • 使用 @renderer alias 引用
        • 开发环境:由于是 js 上下文,编译器无法转义 @renderer,所以无法使用。
        • 生产环境:由于是 js 上下文,编译器无法转义 @renderer,所以无法使用。 生产环境下 public 会以原文件名拷贝。

总结出来就是:

  • 在 style 里通过 url() 引用的图片需要以 / 开头,这样会从代码目录开始扫描,被编译器扫到并将素材打 hash 进输出代码中,然后在编译产物中的对应位置替换路径为 ./图片
  • 不可通过行内样式设置路径,因为产物的引用位置是从代码目录开始,而 js 上下文无法被被编译器扫到并将素材打 hash 进输出代码目录中。
  • 在 script 里通过 src 引用的图片,使用 ./images 可从代码所在的位置查找,使用 @renderer 可从指定位置开始查找,使用 /images 可从 public 开始查找,不可使用 images
  • 在 script 里通过 :src 引用的图片,都是 js 上下文,会从 public 开始查找。

2023-08-25

总结上面的东西其实有点烦人,再加上这几天状态不好,所以弄了挺久才弄出来这个总结。

待办项:

  • TaskItem 的多种视图模式、闪烁提示(意义不大)
  • ShortcutView 的存储实现
  • 按名称搜索功能(粤语)(后续加上按分辨率、格式之类的筛选功能)
  • 大按钮菜单(打赏中心、设置(包括夜间模式、单位)、通往官网和仓库)
  • 标签页性能优化
  • 命令行界面自动滚动到底
  • 各种 utils 整理
  • 后端 progressLog 信息改造(前端自由选择截取多长的数组,以及数据无变更时不 push 列表)
  • 通知机制改造(脱离 task)
  • 气泡支持关闭
  • 双击打开文件,在文件已移走的情况下更改提示
  • 后端崩溃重启时恢复上一次任务
  • 状态栏显示 FFBox 和 ffmpeg 版本、CPU 占用/CPU 占用总数,点击出现全局任务优先级和 CPU affinity;通知中心依然在状态栏;加入当前页网速信息
  • 更多参数(long InputView)
  • 全屏展示输入输出
  • 全屏展示编码器说明
  • InputAutoSize 自动 focus
  • 改用 localStorage
  • 输入/输出/曲线面板
  • 下载时恢复 basename
  • 参数面板下增加自定义参数
  • cmd/通知/进度的全量/增量改造
  • FFBox Service 版本获取
  • 四大金刚键的左一键根据当前画面自动变化
  • taskItem 的“帧”、“时间”独立刷新
  • taskItem hover 显示全文件名

2023-08-29

继续踩 vue 3 的坑。

前几天列了一下想要做的事情。想先去实现 ShortcutView 的存储功能。这需要新建一个 RadioList 组件。 RadioList 本体倒是没啥问题,问题就出在了引用 RadioList 的 ShortcutView 上。当我噼里啪啦一顿代码写下来,发现 RadioList 得到的值固定在了我用 ref 给的初始值,怎么 change 都没用。 排查发现,每次 render 所得到的 ref 变量,连引用都不是同一个。那以前的功能又是怎么通的?于是我试着开始改一些以前写的组件,然后发现,好像在某种写法下会正常,某种写法下不正常。试了下在 mac 上的表现、升级 vue 之后的表现,均是不太正常。那就得通过一点点增删代码的方式去排查。 坑爹的一点是,造成影响的代码甚至收缩到了 console.log 上。还好我大致知道 vue 的一些原理,得出的结论是:vue 需要通过 Proxy 的 get 收集依赖。如果在 render 之前就读了一下它的值,就会导致不正常。 这样还没好。使用了这个值的 RadioList 也得去掉才能正常使用。也就是说,不仅不能在 render 之前读它,就连读取它作为子组件的 props 也不行。 感觉是函数式组件的问题。经测试,模板式组件无此问题。编译器可能做了一些语法糖方面的操作,在 script 里还是个 ref,在 template 里就是个 value 了。 怎么解决呢?我试着直接把变量放到 pinia 里,然后就好了。 很迷惑,怀疑是依赖收集的原因,但不清楚。先记下潜在故障。

潜在故障代码

  • 2023-04-04 TaskItem 函数式改类式组件以实现 unmount 监听。
  • 2023-08-29 函数式组件中使用 ref 是不行的,目前是用了 pinia 来做。具体情况为:1. 若向子组件 props 传输 ref 值.value 会导致渲染故障,每次拿到不同引用的值。2. 若向子组件传输 props 使用 ref 值,则数据类型不对。

2023-08-31

上面的故障解决后,就继续开发 RadioList 的功能了。然后今天又遇到了问题。而且很玄学…… 公司的电脑重装了,我在上面跑 FFBox,渲染进程启动会失败,会返回 -18 或者其他错误码,而且每次启动返回的不一样。 试着把主进程的代码删减到 Hello world 级别,依然启动不了。很奇怪,旁边同样使用 electron 的程序能启动起来,我的 Hello world 启动不起来。 不过目前我的电脑修好了,能在我的电脑上面开发就行。

2023-09-01

完成了预设选择器(包括 RadioList 等)的开发。没有遇到什么困难。 很难得。

2023-09-05

开发到此时,想起来前面做的很多事情都是为了让软件能顺利跑起来。在 3.0 的时候,大概也做过这种事——完成这一块之后,打个 tag。于是今天给前面的 commit 打了 tag,然后从现在开始进入 beta 的开发。

此时发现“让软件能顺利跑起来”还有一环一直被忽略的——网页端。 网页端目前不能正常运行。在 console 里很明显能看到是“jsb.electronStore”不存在。调查了一下 electron 应用进行本地存储的方法,对于 FFBox 比较好的是 localStorage(实际上一早就想换了,依赖能少一个是一个),因此就着手做了存储方式的改造。 为了预留以后换存储方式的能力,业务代码里的逻辑没动,只动 nodeBridge 层,把 localStorage 模拟成一个 electronStore。 但这个改造没预想中的简单。localStorage 存储的值是字符串型,JSON 要做一下 stringify 才能存。这还没完,因为 key 可以用 . 进行层级引用,而直接用带 . 的变量去取 Object 的 key,js 是会把 . 视作普通字符,而不是层级分割。所以,这个层级分割递归存取的逻辑要自己做,得花点时间。

另外,今天还进行了的一个改造项是给各个面板增加“自定义参数”。这个比较好做。另外我也终于给 StatusBar 加上了 ffmpeg 和 FFBox 版本显示了。本来我还想把通知中心机制改造给做了(因为按钮做出来了),但做到一半又去优先解决在网页上跑起来这事了(主要是没想到存储方式改造会这么复杂)😂。

还有个小插曲:今天在进行 combobox 改造(尝试让窗口可拖拽区域不影响 combobox menu 点击,但发现简单改动不行,要改成把 menu 独立创建一个 div 而不能常驻,所以后来就没有做)时,因为一些 git 的误操作把 stash 给 pop 了。根据网上找到的资料,用了 git log --graph --oneline --decorate $(git fsck --no-reflog | awk '/dangling commit/ {print $3}') 把 stash 还原出来了☺️

2023-09-06

因为上次说到了通知机制改造嘛,所以这次就去改这个好了。 在改造的过程中,对一些以前就打算做但因工程还没能顺利跑起来所以还没进行的类型迁移工作进行了一个进行。也做了一些函数参数的修改,比如说 id 改成具体时间什么 id 之类的。 正在构思通知面板该长什么样。大体有了思路。

2023-09-07

今天按照昨天的设想把通知中心做出来了,大体功能能用了。目前还不能读取服务器上已有的消息,因为这涉及到全量数据传送,但目前的设计没有区分好全量和增量。预计会在下一次 commit 集中解决 cmd、progress、notification 上的增量/全量问题。另外这也涉及到数据交互方式是使用请求/回应还是 RPC/事件响应的问题了,所以后面还可以考虑下用请求/回应的方式获取 FFBox Service 版本。

另外,做通知中心的时候,新建了 FixedButtons 组件,把常驻在窗口上的大 logo 和三大金刚键放进去了。留了个“四大金刚键的左一键根据当前画面自动变化”的待优化项。

2023-09-12

前几天试着做一些小小的优化项:三大金刚键支持 Win11 的 Snap Layout,结果是并没有做成。 方法是:electron 在新版中增加了 titleBarOverlay 选项,可以通过此项控制 Windows 三大金刚键的颜色和高度。23 版本的 electron 有透明度问题,升级到 26 版本就好了。但问题是,它会遮挡 DOM 内容,导致我手动实现的金刚键没有办法触发。 看了一下其他软件的实现,大多数具有自定义风格的软件都不支持悬浮 Snap Layout。有些软件似乎可以主动调用,因为呈现出了一种自定义样式,但 electron 似乎是不支持的。 所以直接放弃了这个功能。看起来社区的人(https://github.com/electron/electron/issues/31372)已经大体上满意了,没什么改动的动力。

2023-09-20

全量/增量改造具体是怎么个改造法呢?我想了一下,大致思路是:原有的【cmd/通知/进度】事件保留,作为增量更新的方式,当然后端需要全量刷掉时也支持;全量的获取方法由发送 rpc 等待事件改为直接请求返回。 以前的实现方式其实比较有问题:当服务器只连接一个客户端的时候还好,但当服务器连接多的时候,其中一个客户端想获取 task 或者 taskList 的时候,会把 task 和 taskList 的更新事件向所有客户端都发送一遍。这种显然改成请求返回式是更优雅的。

于是今天做了一下 task/taskList 的相关改造。还算比较顺利。做好这项之后,感觉【cmd/通知/进度】其实没有必要改动了。 另外遵循 RESTfulAPI 语义,把添加任务的 url 和 method 改了一下。

然后去做了一下通知的全量获取。顺利。

最后顺便更新了一下 FFBoxService 版本的获取。本想着这个搞定就完事了可以提 commit 了,但卡在 package 之后后端仍然显示环境为 dev 的问题。感觉要改一下 vite 配置。不过,困,先睡觉。

2023-09-21

这个版本信息虽说是“顺便”,但一做起来完全就不是“顺便”的样子了,完全可以独立拎出来一个 commit……_(:з」∠)_

起因是,我发现 version 字符串的生成规则其实是不对的,甚至从 3.0 版本开始就是不对的。原来的判断方式是检查 process 是否存在,以及 process.env.NODE_ENV 是不是 production,但其实根本没有地方给它赋一个 production 的值。虽然能用但语义是不对的。来到 4.0 版本,渲染进程就没有 process 了,自然得通过别的方式给它赋值。 效仿我给公司做软件的经验,我让前端和后端在编译时注入一个常量,用于表示当前是否在开发环境,顺便也注入 git 版本。这个操作就需要比较多的改动了。 详细思路我就不在此列举了(本身已经够困的了,再重复理一遍思路实在折寿🥱),我已经把日志顶部的流程用 mermaid 流程图的方式替换为了现在的模式。

终于能用 build:everything 一次性走全套打包流程了,舒服。

2023-09-22

今天想把大按钮菜单的功能给做了,这样才好做四大金刚左一键的逻辑统一。

首先要关注的是 menu。因为左上角大按钮汇聚了 Windows 窗口菜单、弹出一级菜单等功能,窗口菜单是 Windows 特有的功能,而 mac 上的一级菜单首项则有 mac 的特有逻辑。因此就要了解一下菜单相关的 API。

Windows 窗口菜单除了最小化、最大化这些功能以外,软件是可以添加自定义功能进去的,我在小部分软件里发现了这点。electron 里似乎没有这种 API,如果要自己实现的话,需要调用 Windows API GetSystemMenu,然后对获取到的 menu 引用进行添加项的操作。这种脱离 js 上下文的操作显然不符合我的需求,因此这个方案去掉。 (另外 vscode 可以做到窗口可拖动区响应自定义的菜单,不知道是怎么做到的,不考虑。)

接下来要尝试的是自行触发 system menu。根据找到的资料(比如这个),是可以使用 GetSystemMenu + TrackPopupMenu 实现的。我把 WindowsHelper 带了回来,然后进行一番改造…… 但是,不行。 getSystemMenu 是能得到东西的。但 trackPopupMenu,无论我怎么改它的 uFlags 参数,无论是给坐标输入 (0, 0) 还是先 getWindowRect 再赋值进去,无论是直接在终端里输入还是生成之后放到 FFBox 中实际运行,无论是普通权限还是管理员权限,它都会报错误码 87 ERROR_INVALID_PARAMETER: The parameter is incorrect. 那究竟是什么 parameter incorrect 呢?为什么别人用就行呢?知条铁。弄了很久都不行。

后来我就想着用低级一点的方法:直接向窗口发送键盘快捷键。然后我找到了这个。里面提到了一个我没见到过的 SendInput API。点进文档之后不怎么能看得懂,但是微软竟然给了个示例,非常简单易懂!我直接把示例的 Sends Win + D 代码复制下来放进 helper 里跑,成功了! Win + D 能成功,这也就意味着,触发 Snap Layout 的 Win + Z 也能成功。不出所料成功了。 那触发 system menu 的 Alt + Space 呢?在虚拟键定义里,我找不到直接按下 Alt 键的定义。我去网上找了一下,有说 Alt 键不能正常生效,得用 scan code的,但实测无需这么麻烦,ALT 键可以用 VK_LMENU 表示,能成功触发 system menu。这就成了,直接开工!

至于今天的开发中涉及到的常规改动——

osBridge 从 renderer 移动到 main 了。因为 4.x 的渲染进程就不能自己启动进程了,所以管理 helper 的工作被移动到了主进程,而渲染进程对其的调用则移动到了 nodeBridge。

C++ 方面,因为不熟悉,写起来是有点难度在的。特别是调用 Windows API 的操作一开始给我整得有点绕。为什么当时写这个 helper 的时候需要先 typedef 一下需要的 Windows API 的定义,而这个定义需要到微软官网找文档抄下来?为什么调用时需要先用这个类型从 dll 里获取这个函数,然后才能调用?不懂就只能照抄然后改。后来想了一下,这是动态从 dll 里找函数的方法,而 Windows 开发套件里本来就有 Windows.h,啥都准备好了,不用这么搞的呀。反正后面就直接用了,而以前的代码就放着呗。

2023-09-25

今天做了下大按钮菜单的整体逻辑,包括鼠标按键响应、菜单中心和消息中心的显示逻辑、应用菜单的 UI(暂未实现功能)等,调整了一下两个中心和大按钮在画面中的层级关系,做了第四金刚按钮的功能。

2023-09-27

今天做了下自定义菜单。你会发现我弄了一个 .vue 一个 .tsx。前不久我还在思考为什么 Msgbox 弄了两套,这不,现在 Menu 又被迫弄了两套。踩坑了呗~ 就是 2023-03-19 的那个问题,defineProps 所使用的类型不能是复杂类型。我按着那个 issue,找到了一个 PR。虽然修复了这个问题,但是没修复全。它说:Do note that complex types support is AST-based (not using TS itself) and therefore not 100% comprehensive. For example, you cannot use conditional types for the props type itself。恰好,我类型里用了 extends,它不支持。虽说可以把 extends 换成 &,但算了,还是换成 tsx 吧🤷。

然后就是常规的改造了,新的菜单样式上跟原来的有所不同。最大的还是参数的不同吧,新的 Menu 偏向于多功能,既能作为参数菜单支持上下切换即时看到效果,也能作为普通菜单支持命令、选择、子菜单。

2023-10-08

解决了顶部菜单打开之后由于被覆盖了一层 mask 导致需要先把菜单关掉才能切换菜单的问题。方法不一定很好,就是在打开菜单的时候先记住几个菜单按钮的位置,然后挂个 mousemove 的监听在 body 上,鼠标移动到指定的位置就切换菜单。

参考过其他软件的菜单。不同软件之间没有一个统一的标准。一般是右键菜单会把整个画面都蒙住,而应用菜单则不蒙住。其实这里还有个键盘焦点的事情。总之还得再考虑下要怎么做。

另外,解决了销毁菜单时控制台会报错的问题。原因是 onClose 被调用了两次,其中一次是 onClick 触发的。vue 默认就把这个事件监听并且发送到 onClick 了,把它改一下名就好。

2023-10-09

事情稍微有点复杂。因为 MenuComponent 需要用到 mounted,然后今天发现组件里的一些东西会在我每次变更 ref 值的时候重新渲染一遍。究竟 render function 在什么时机才会执行呢?这是挺久之前就遗留下来的问题了。明天再搞搞。

2023-10-10

其实昨天遇到的问题就是 08-29 做 ShortcutView 时遇到的问题之中的一个。当时给出的结论是:函数式组件可能是由于依赖收集的原因,ref 变量如果在 render 之前就读了一下它的值,或者读取它作为子组件的 props 就会导致不正常。今天又复现了一下,发现了更多规律:ref 变量放入子组件的 props 或者 DOM 中会不正常,而放入子组件的插槽里则正常。 看了下 Vue 文档中深入响应式系统的部分,有介绍到可以用 onRenderTracked、onRenderTriggered 追踪是什么东西导致 re-render。我把它放进函数式组件里,不能用。它提示好像是没有实例还有生命周期什么的。

粗略地看了下关于 functional component 的一些文章,似乎又得出了一些新的结论—— 函数式组件因为无状态,仅适合简单组件的快速渲染,所以每次 render 的时候可能都会新创建组件而不进行 diff,于是每次 render 时拿到的 ref 都不是同一个。 在这种情况下,上述的“不正常”反倒是正常的表现。 而放入插槽中可以正常工作的原因,我猜测是:依赖收集到这个 ref 变量时,所在的层级实际上已经在子组件里。所以当 ref 值更新时,本组件并不执行 re-render,而是子组件 re-render,因此 ref 本身并不会变化。 至于 console.log 会导致不工作,则是因为依赖收集阶段收集到了当前层级的引用,所以当其更新的时候,触发了 re-render,导致 ref 变化。

这样就比较明晰了:凡是有状态的组件都不应该使用函数式组件。目前正在使用函数式组件的组件有:Button、Menu、Msgbox、TaskItem、各种 View。其中 Menu、Msgbox 都是有状态的,应该把它做成函数式以外的组件。 另外,之前有提到使用 pinia 可以正常给 ShortcutView 工作。这个就比较好解释了:pinia 没做什么特别的操作,只是把值作为像 props 那样的方式传入了,那么这个状态就是在 store 里而不是在函数式组件里了。 那么为什么 Msgbox 具有 3 个 ref 都能正常工作呢?我猜是因为它们有可能是被放在了 Transition 组件的插槽里,因此 re-render 层级实际上下降了。算是侥幸逃过。 Menu 是要继续侥幸放进 Transition 里继续用函数式组件开发好,还是回归模板式组件呢?还得思考一下,我比较倾向的是第二种。

2023-10-11

今天让菜单组件回归 vue template 了。按照 09-27 所述更新了 vue 版本之后,大致就可用了。为什么说“大致”呢?因为 vue 类型导入走的是 @vue/compler-sfc 而不是 vite,所以如 PR 里 comment 所说,alias 之类的功能也用不了。在我这里就是必须加 .vue 后缀才能导入,但加了之后 ts 又不认了,所以只好用 vue 和 ts 分别导入两次,然后 as 过去🤷。

具体到菜单组件功能的开发上,今天确定了菜单“选中”和“悬浮”的逻辑——“选中”完全来自 props,“悬浮”可由鼠标或键盘控制。然后写了下键盘响应(及所需用到的 getMenuByItem、getMenuAndItemByValue)用于与 DOM focus 联动。子菜单虽然没有用到,但是也写了相关的逻辑。

2023-10-12

加上了 MenuCenter 的菜单左右切换功能,加入了菜单项点击的 console.log。目前基础的菜单功能已经可用了。一个菜单开发了好几天,确实有点复杂性。 接下来要做的是动态更改菜单显示内容,这就需要主进程的菜单可以根据实际情况变化。这里的设想是 MenuCenter 监听所有会变化的项,变化后重新生成菜单,并使用 JSON.stringify 发送到主进程,在 common 里加一个转换函数,主进程将这些字符串转换为 menuTemplate 并更新应用菜单。

做了一下 convertFFBoxMenuToElectronMenuTemplate 函数。dfs 模式,边做边改。主要是 electron 的 Menu 跟用于构建菜单的 template 还不一样,本来想着 ts 至少能提示一点类型,然而看起来现在 input 和 output 都得是 any 了🤷。 然后实现了从渲染进程发送菜单给主进程,主进程构建菜单,用户点击后把事件发送到渲染进程的整个流程。

2023-10-13

大体上成功实现了主进程和渲染进程的菜单联动。渲染进程菜单更新到主进程、主进程点击触发渲染进程寻找点击项响应都做好了。 想用通知中心和菜单中心两个面板的开关状态测试一下,但没有 Checkbox 组件可用。 因此等下要去做个 Checkbox。

2023-10-15

Checkbox 比较简单,很快就做完了。目前是直接放在了 components 文件夹,以后再思考可以挪去哪。

上次跑通了菜单的主流程,于是今天主要就是写菜单各项的响应了。目前实现了页面的放大缩小重置、官网和源码跳转、版本信息弹窗、一些跟服务器相关的按钮的 disabled 状态。

2023-10-16

今天实现了 MenuCenter 的大部分预定的剩余的功能(主要是任务处理相关的),还差一个添加任务没有做。因为原来的添加任务是用户把文件拖进浏览器,这样是可以直接拿到文件二进制流的;但是如果是直接用字符串路径的形式,就需要由主进程去读二进制流做 md5 读取,或者在页面里加个 input 什么的,这个还得再想想怎么做比较优雅。

2023-10-17

今天做了一下菜单的一些剩下的功能:

做了 Radio 组件。

做了子菜单。 子菜单在做的时候就感觉到目前的设计有点不太好的地方:不应该去做打平菜单,而是应该在 hover 之后再进行一个 showMenu。这样的话菜单就少一层嵌套,可能会方便点。另外在子菜单悬浮位置上也可以传个向左或者向右,比较好处理些。不过目前做都做了,就把现在的先做好吧。

做了菜单项的 tooltip。

打开文件的功能最终是用了 input 来实现的。目前的缺点是使用 electron 菜单指令打开文件窗口后,如果 DOM 没被用户操作过那就不响应。虽然可以通过特例的方法打开 electron 自带的打开文件窗口,但是没必要,先 bug 着吧。

还差把 combobox 换过去。

2023-10-18

成功把 combobox 的弹出菜单换过去了。 好像没什么感想🤪。 可能是因为在感冒的原因?功能一股脑做过去了,脑子空空…… 看浏览器打开的标签页得知:我搜了一下 vue3 父组件调用子组件的方法。实现此功能的关键词是 defineExpose,这是 vue 3 script setup 组件向外暴露方法的方法(约等于 React 的 useImperativeHandle + forwardRef)。至于父组件那怎么调用,百度没给我好答案,我是自己试出来是使用 vnode.component.exposed 方法。作用就不赘述了,代码里注释应该写明白了。

剩下的注释清理工作,还有个 mask 鼠标事件关闭菜单的逻辑明天再做……

(有了 submenu 支持之后,顺便加了一下 gitee 仓库和 ttqftech 的链接……

2023-10-20

上面的“mask 鼠标事件关闭菜单”指的是当 showMenu === 1 时松开鼠标就把菜单栏收起来。这个功能实际上不好做。因为它串联了很多部分的逻辑。

首先要解决的问题是,打开菜单后,菜单栏上的几个按钮还能保持 mouseenter 之类的效果。但由于 menu 已经在上面铺了一层 mask,所以就要想个方法把事件透过去。 我去了解了一下 mouseover(会冒泡)和 mouseenter(不冒泡)的区别。但这里其实并不能实现所需效果,因为这里的穿透冒泡之类的,是对父子元素生效。这里俩东西并不是这样的关系,所以透不过去。 我想到过一个比较 hack 的方法,就是在打开菜单之后,先把几个按钮的位置记下来,然后给 body 挂个事件,当鼠标移到那位置时就展示菜单。这就带来了另一个问题:因为在直接点击按钮的同时也会触发 menu mask 的 mouseup,就变成了点击打开菜单瞬间又收回去了。(好像也能改成鼠标移到那位置后马上控制 mask 可穿透) 然后今天又想了个比较 hack 的方法,就是在打开菜单之后,在 body 上创建几个不可见的跟按钮尺寸位置一致的元素用来接收 mouseup 事件。此时发现了新的问题:单个菜单关闭之后,并不意味着马上要把菜单栏收起来,因为有可能是要打开另一个菜单,但是先触发了前一个菜单的 onClose。

2023-10-22

梳理一下:

当打开菜单栏时:

  • 需要实现悬浮不同按钮切换不同菜单,可通过 4 种方式实现:1. 记录菜单按钮位置,在 body 上挂监听;2. 记录菜单按钮位置,在同样位置创建元素;3. MenuComponent 设置 disableMask。4. 向 MenuComponent 传入子组件,让按钮的副本传进去监听。
  • 需要实现在空白区域鼠标弹起关闭菜单,可通过 3 种方式实现:1. 使用 MenuComponent 中 mask 的 mouseup 触发的 onCancel;2. 设置 disableMask 并使用 MenuCenter 自己的 mask 监听鼠标弹起。3. 大按钮弹起时自动关闭菜单栏,监听此值。

当打开菜单中心时:

  • 需要实现鼠标弹起时不要把菜单关掉。上述的在同位置创建新组件或者 disableMask 都可。也可监听 click 而不是 mouseup。

2023-10-23

睡觉之前,我在想,菜单这玩意,设计起来就有点像是在玩华容道……动了 A,B 就不行;动了 B,就变成 C 不行;动了 C,那 AB 都不行了……就是在弄各种排列组合让几个组件能配合起来运行。

最终打开思路的地方是把 MenuComponent 的监听改为 click。打开菜单中心时的菜单交互就变成最普通的了;打开菜单栏时,切换菜单靠 body 上加元素,关闭菜单靠【ItemSelect 手动触发 document mouseup 或没选中直接触发 document mouseup】→【showMenuCenter = 0】→【selectedMenuIndex.value = -1】→【menu.close()】。

没想到菜单竟是 FFBox 里最复杂的组件🤣。

今天想给组件加个 mounted 后自动在子菜单里找到对应项的功能,然后写半天发现键盘控制这个基础功能跑不通。大致原因是 indexInFlattened 这个值的意义没搞清,应该是序号跟 key 混用了,导致切换子菜单的时候有些东西找不到。

但是写了这么多了,我想先 commit 一下前面的功能再搞了。


睡觉之前贪笔修了一下(老毛病了)indexInFlattened 这个问题,子菜单直接就好了。

然后把【mounted 后自动在子菜单里找到对应项】的功能做了一下,把 flattenedMenu 分出一个 filtered 的版本,稍微改下 calcSubMenuPosition,也行了。

然后清了一波菜单栏功能实现的草案。发现 Combobox 那里还有动画和滚动条样式没弄过去。

然后贪笔也做了这块的功能。六点钟睡。🤷

预计把菜单中心的设置和打赏页、分辨率菜单做完之后,加上 Alt 键响应,打一个 tag,进入 gamma 的开发。

2023-10-27

这几天继续做 Combobox 的开发。

主要工作是把 Combobox 的 list prop 类型从 BasicMenuOption 换成 MenuItem。这个操作需梳理一下上层有哪些组件依赖 Combobox。结果还不错,转码参数完全不依赖这些 list(因为是直接用 value 传输的),依赖项只有 vcodecs/acodecs/formats 以及 VcodecView/AcodecView 这些。改动不难,换一下数据类型就行,就是改动量大,几千行改动 = =。 让这么大量的参数项依赖 Menu 的数据类型,这个设计好吗?我觉得不是特别好,但以前其实不也是那么用嘛 = =。这次改动之后,数据类型会稍微比以前合理一些。 另外,在做这个的时候学到了 ts 的一些功能:类型守护、类型收窄,以及从一个联合类型中提取其中一组搭配的 Extract 高级类型。

做好这些之后,终于能继续实现分组分辨率菜单了。

还是遇到了一些跟设想不同的问题的——键盘事件响应。 我之前对菜单组件的设计是:当按下键盘左右键时,根据当前是否有子菜单的一些状态判断是由菜单组件处理这个事件还是抛出到外层。来到 Combobox 之后,键盘事件是先由 Combobox 响应,再丢给 MenuComponent 做判断的。那这里就需要让 MenuComponent 在一些情况下再把这个事件丢出来。 死循环这个问题比较好处理我就不讲了,主要是重新丢出来的事件不可以再来一次 dispatchEvent(一个事件只能触发一次),所以尝试自己创建一个 KeyboardEvent。然后根据这篇文章所说,自己创建的键盘事件不能用来输入。那只好自己判断按键,控制输入光标的移动了。还是能完美实现所需功能的😄。 ↑补充:其实没完美,选中功能没了,暂时不打算加上去。

最后新增了一下 readme.md,把仓库上的主分支切换到 4.0+。

最后的然后……又贪笔做了一下分辨率列表,把 1.0 时代长长的分辨率列表回归了 _(:з」∠)_

2023-10-29

做了一下隔行扫描功能和 MenuItem 的 tooltip 改动。顺便把 Combobox 也加上了 Inputbox 那样的校验功能。

2023-10-30

做了一下打开菜单时自动 focus 到对应项。 梳理了一下 MenuComponent 的函数,加上注释。然后省略了 onMounted 的一个步骤。

2023-11-04

做了一下菜单中心侧边栏和内容的 UI,把打赏面板放了进来。

其中做 Ko-Fi 图标的时候就遇到了一个问题——给 svg 指定 viewBox 不管用。经过了手动修改 svg 去除多余的标签、把其他 svg 内容粘过来改路径尺寸等操作之后,最终发现问题应该出在 svg loader 上:如果 svg 上配置的宽高跟 viewBox 的尺寸一样,那么 viewBox 属性就会直接被去掉。去除宽高属性即可。想不到现在还能遇到 svg loader 的坑。

另外,做好一部分之后,目前发现菜单中心存在性能问题——打开面板时显存爆炸帧率爆炸。估计是动画导致不断布局重流。明天试下固定内容宽度改善这个问题。

2023-11-05

今天把昨天说的固定宽度的事做了。确实明显改善性能。

然后优化了一下菜单中心侧边栏的 UI,加高了一个平面,解决了看起来左右不对称的问题。

最后做了一下菜单中心侧边栏切换时的动画。

本来想着解决机器码的事的。想了一下昨天就已经觉得打赏面板的东西比我预想中要多了,这个 commit 的量显然已经非常充足了,那就先把现有内容提交一次 commit 再说。

2023-11-06

加入了启动时检查 localStorage 以显示“欢迎使用 FFBox”并生成机器码的功能。 然后终于把激活的后门做好了。 其实这个激活后门的交互想了挺久来着,主要是想把鼠标的中键利用起来。以前想到的方案是在大按钮上做文章,现在的方案实现起来会简单一些,只在“支持作者”面板做改动就好了。

2023-11-08

做了一下打赏面板的激活功能。

给应用菜单和效果面板都加上了 ffmpeg 的链接。

2023-11-09

今天做了 Alt 键菜单响应的功能。

HTML 那边的 Alt 键功能比较好做,唯一有点例外的是 macOS 在按住 Option 键时出来的 key 不是英文,换成 keyCode 就能解决。这个好弄。 涉及到系统菜单的,基本可以说是完全无法实现了—— 我试着通过检查菜单项名称里的括号英文字母来给 electron 菜单加上 accelerator 属性。但是实测它只能触发菜单项的 click 行为,而无法打开菜单。 这个功能主要是根据 Windows 的操作习惯加入的,所以我主要在 Windows 上去做。周围看了一圈,我发现 Windows 里已经很少有用 Alt 快捷键唤起菜单的设计了,包括 Windows 自身的东西。而 electron 本身似乎也没提供这种操作方式。至于 macOS,似乎就没人想到过用 Option 快捷键唤起菜单。这样看来,FFBox 这个比较独树一帜的功能就没有必要一定要实现了。

此外加了一些菜单面板可拖动窗口、Msgbox 只有一个按钮时键盘默认响应等小功能。

最后做了一下能看不能用的设置面板。

好!终于做完了 v4.0 beta 版本预期加进去的所有功能了🎉!竟然离 FFBox v1.0 三周年已经只剩 1 天了呢。

最初,设想的 v4.0 发布日期是 6 月 23 日。经历了命运的消磨,我便知道这个日期无法完成。 当八月重新启动开发的时候,便直接设定了 11 月 11 日这个长远的目标。可是心有余而力不足啊,多年的前端经验并没有给开发带来什么提速,只是扩张了能力的范围,能实现更多功能。于是便不断地提出需求项,又花时间去完成……我只想让我的软件更完善,可以有更舒服的交互、实现更多有用的功能、代码也更通畅。好多东西都要琢磨,进展只能一次推一点点、推一点点…… 或许,当我在“暂缓更新”迟迟不复工的时候,便是受到了这种无力感的影响吧 ╮(╯▽╰)╭。

多的话也不必说。接下来会在什么时候发布 v4.0 版本呢?我前一次的预估是 11 月 19 日。现在看来,大概率也是完成不了的了。我还想做个视频去介绍 v4.0 的功能呢,这来不及。

因此,v4.0 发版,我预期是定在 1 月 24 日。这是我的命运在 2023 年新刻下的印记,又或是命中早已注定的坐标。

FFBox,亦是一本只有封面和页码,没有内容的纪录书。

2023-12-18

前几天做了深色主题。因为大体上都是在做 CSS 修改,小部分需要改 js 里的内联样式和 computed,而且是看哪里不顺眼就改哪里,所以就没有每天都写日志。

大体步骤是先在 appStore 里加个 frontendSettings,尝试并跑通了下使用 var() 并放入全局样式后被其他组件读到的流程,还有使用 data 属性做组件内的主题适配。然后就是创建了全局的 theme.css,先统一把所有搜索到的浅色背景色系做成 bg90 ~ bg100 的 var,然后针对一些没改过来的地方看用加什么全部 var 顶上,比如 hoverHighlight(已经被改成别的)和 hoverShadow。中途还直接给底栏加了个按钮切换主题,方便快速对比差别。然后要把各处彩色组件的浅彩色降成深彩色,比如 taskItem 和 titleBar 上的背景色。最后是细节上的调整,比如说把 Button 组件的彩色按钮、ParaBox 上的彩色按钮、开始按钮、底栏颜色都做了一下随主题色进行细微的变化。

基本上都是在做 CSS 上的调整。技术上倒是没什么阻碍。我愿意这么形容 vue 的生态链:只要把大量的坑都踩遍,vue 的开发是无比丝滑流畅。

剩下需要实现的则是 RadioList 的值与标签分开显示了。这个大致可以认为是为多语言做准备,也是更靠近正常的实现逻辑。

另外,上周看到影视飓风将他们的节目帧率从 25 切到了 29.97,于是我也构思了一下对于邪教帧率的优化方案。具体来说就是利用多级菜单的功能,把邪教帧率放进更深的层级里,以加大操作耗时,同时也体现出我设计了个多级菜单()。此前多级菜单只考虑到了 2 级的情况,帧率那个选项菜单虽然往往偏右但也不至于重叠,但现在层级达到了 9 级,菜单就会叠在一起。关于这一项,我当时是觉得要把菜单设计成 shouMenu 嵌套调用而不是打平才能较好地实现。现在是想到了新的方法:在 openedSubMenuItemPos 里再加一个字段用来表示子菜单期望的打开方向,碰到边缘了才反弹。实现起来倒也不太麻烦。可以说菜单组件是在打平方案下越走越远了吧。

希望能尽我的微薄之力给那些因循守旧、抱残守缺的日本厂商还有那些将错就错、指鹿为马、积非成是的国产厂商制造一些蚊子咬的痛觉。我在设置里放入“数据量进制和词头”亦是此由,这种理科的东西不像文科那样可以随着人的使用、时代的发展、文化的演变而产生变化,而是需要有一致性的,特别是在已经有标准的情况下,就要去遵守。

2023-12-20

RadioList 的值与标签分开显示做好了。做这个的时候,顺带把 InputAutoSize 的自动聚焦也做了(也修复了触发重命名但不改名保存会导致项被删除的问题)。InputAutoSize 的自动聚焦其实是个很简单的改动,但有时候写代码就会考虑维护代码的低庞杂性,比如说这里已经有一个 ref 了,再加一个 ref 会不会有点冗余之类的……精神状态不好的时候就会差那么一点点没思考过去 _(:з」∠)_

然后终于就能把主题切换引进设置面板里了。把主题的 value 统一换成 themeLight、themeDark,加点存取逻辑就搞定了。这个 commit 的功能也可以告一段落。

下一个 commit 预估会去做毛玻璃主题。主打一个做都做了,想把心心念念的毛玻璃也试一试。

昨天 QQ 给我弹窗建议我升级 QQ NT 了,那 QQ 团队心心念念了这么久,就升呗。有一说一,这流畅度比我预想中的要好,不愧是 electron + 腾讯黑魔法。更令我在意的是它用 electron 实现毛玻璃了。 这玩意不是挺难搞的吗?微软又没有把半透明模糊背景的 API 开放给 Win32 应用。那它是怎么做到的?注入 dwm 不断截取它窗口后方的画面贴上去?从流畅性来说好像是(拖动窗口跑不满屏幕刷新率 75fps)又不是(几乎没资源占用)。于是我又上百度和谷歌搜了它是怎么实现的——没人讲,而且 electron 与毛玻璃相关题材的文章往往都很老。那它咋搞的? 后来我在 electron 官网上看到它把半透明模糊背景的 API 开放出来了,仅限 Windows 11 22H2 或者更新的版本能用。得,又是跑长途刚好碰见班车开通,这做起来就简单多了! 那么以前看的那个 electron-vibrancy 现在看来也不用继续看了。主打一个能用就行。❤ 下一个 commit 再做。

另外 QQ 的 VIP 主题刚好回应了我对大片背景加纹理的想法。它的图我是不能拿,毕竟有版权;不过倒是可以考虑用 AI 生成一些,可以以后做。

顺便祝贺一下 FFBox 开发日志迈入 1000 行!🎉

2024-01-02

首先,恭喜大家成功活到了 2024 年!活着就是最棒的!

2023 年对于我来说,不能说是波澜壮阔吧,也能算是一事无成了。3 月 28 日的开发日志呈现出来的也只不过是乱世之一隅而已,更多的还藏在我那已经写了 9000 字但只写到三分之一不到的 2023 年总结里,但是那玩意我是不会放进开发日志这种正经地方的。本身那次把开发以外的东西写进开发日志里就已经够大逆不道了,FFBox 本不该掺杂这些感情进来的(后面发生更糟糕的事情没写进来,是因为我已经发现 2023 年的命运如此了,而且开发 FFBox 这个行为已经在某种程度上成为一种电子香烟了)。就算开发者归西了,讣告都没这么长。写这些太占空间。

但有一件事与 FFBox 相关的是,不再以 1 月 24 日作为新版本的预期发布日了。俗话说新年新气象,那新气象究竟是好是坏呢不知道,踩点在新的一年是有好处还是有坏处呢不知道,做计划能是带来更高成功率呢还是会导致失望更多呢不知道,成功率高是成功率高呢还是成功率低呢不知道,许愿是能带来目标实现呢还是能带来目标远离呢,是能带来好运呢还是带来坏运呢,自己许愿自己、自己许愿他人、他人许愿我各会有什么效果呢不知道。2023 年往往就是这样,做这个不好,做那个又不好,不做这个又不好;许愿不好,不许愿又不好;有想法不好,没想法又不好。顺着走不好,逆着走也不好。有时候又会好,不知道为什么。反正你就排列组合吧,总结下来大体上就是一个“不好”。

我不是无神论者,我自己一个神,我没给它起名。我一般不想信这个神,但是如同上面所说,信了不好,不信也不好。

从高维空间来看,无论我往低维的哪个方向走,那结果往往都是往更坏的方向去的。而我无法控制高维空间,所以也就无法回退。不过,虽然顺着走逆着走都不好,但顺着走不好的经验更多。所以,随便试着逆着走吧。 今年元旦我也许了个愿:“希望新的一年,可以过得比 2023 年更糟糕❤️”! 1 月 24 日的预定发布日就取消了。

2024-01-03

去年最后的一周,在看毛玻璃特效相关的东西。

简单来说,微软在这一条链路上做得就是一坨屎。

历史

从 Windows Vista 时代开始,微软搞半透明模糊特效,那时候 API 就很简单,就用点 DwmExtendFrameIntoClientArea、DwmEnableBlurBehindWindow、SetWindowLong 之类的就能用了。而且也很漂亮。后来甚至有人也给 Windows XP 也加上了这个功能,比如我在用的 TrueTransparency。 从 Windows 8 时代开始,微软不知道裁了多少美工,美名其曰“扁平化”、“节省资源”,把 UI 改得又割裂又丑不拉几的。半透明模糊的代码听说是还保留着,所以直到 Windows 10 几年前的某个版本,依然能通过一些 hack 的方法(比如我在用的 AeroGlassforWin10)启用这个特效,观感是明显提升一个层次。可惜 hack 始终是 hack,它不被默认支持,于是一些软件检测到是 Windows 8 或者相关条件之后就直接不启用毛玻璃特效,hack 方法就只能用在老软件上。并且随着 Windows 版本的变化,这种软件也要随时更新符号文件,然后到某个版本了不能用了,又要换别的,比如 WinCenterAero 之类的。但总的来说,只有标题栏能生效,又容易有 bug,效果跟现代的设计不和谐, Windows 10 在某个版本终于把 Mica 这种特效加上了,这种微透大模糊的风格跟原来的毛玻璃很不相同。但更关键的是,这个 API 它不开放给 Win32 应用程序,只能给自己的框架(比如 WPF 还是什么之类的)用! 后来我在做 FFBox 3.0 的时候,发现了大家在用 SetWindowCompositionAttribute 这个未公开 API。于是我用 C++ 的方式进行了调用。不完美,但是能用。也就是窗口没了阴影、缩放没了动画、不能用 Snap 贴靠而已,但是它实现了毛玻璃。这时候还勉强说得过去是吧,毕竟能选择几种样式,所以 API 跟 Vista 时代的不一样了,能理解。 然后来到 Windows 11,随着 22H1(大概是这个时候)发布,我这 FFBox 就崩了——窗口拖动变得十分迟钝,若是你用的是一张性能很差的显卡,那就酸爽了。于是 FFBox 的 issues 列表里,就出现了这个

就算没人给我提 issue,这个 bug 我也难以接受。代价太大了。4.0 版本的设计理念里也没有窗口半透明的部分。

electron 开始支持(有 bug)

来到 22H2,用于 Win32 的半透明模糊特效 API 终于开放了。应该是开放了吧,这是我从 electron 的新功能里猜的。BrowserWindow 里多了一个函数:setBackgroundMaterial。意味着微软终于打算把这套链路打通了。 于是我把 electron 从 23 版本升到 24 版本。API 有了,但它只对标题栏管用,客户区完全无作用。

我去搜了一下。这个功能早在 2021 年就有人提出过 [Feature Request]: Use new 'Mica' material for BrowserWindow vibrancy in Windows 11。里面有人使用 node 模块调用 Windows API 实现了一定程度(也就是有 bug)的半透明模糊效果,也有人用 flutter 的框架实现了几种样式。另外,里面提及到了可以用 mica-electron 这个第三方库。

electron 开始支持这个功能应该是始于 2023-05 的这个 PR:feat: support Mica/Acrylic on Windows。里面有人说 cool 的,但更多的是反馈只有标题栏生效的问题。

2023-09 的这个 PR:fix: frameless mica/acrylic windows 说是修复了无边框窗口相关的问题。我看它应该是只要更新到 27 版本就可以了,而且下面被关联了一堆 issue 都被解决了,我觉得会好用。于是我把 electron 版本升到 28。结果:

transparent: true,
frame: false,

在这个组合下,有效果,但阴影圆角双击最大化 Snap 都没了,而且需要调一下窗口大小才能生效。失焦时会触发一个恶心的天蓝色标题栏。 没阴影没圆角的问题,我设置了 thickFrame: true hasShadow: true 都不管用。

transparent: false,

transparent 关了就什么效果都没有

transparent: true,
frame: false,

这个组合有效果,但多了个不要的标题栏。另外窗体本身是有颜色的,透明只是透给 mica,不会透到窗体以外的地方。

titleBarStyle: 'hidden'

等效于 frame: false

也就是说,electron 依然没修好。

最后我看到的一个 PR:fix: titlebar incorrectly displayed on frameless windows 是还没完成的。这个 PR 在我写日志的当天才变成 merged 状态,也就是说还得等一段时间才能看到效果。

第三方解决方案

于是我去试了一下 mica-electron 这个第三方库。这个也不是特别顺利。

首先,我试着按照它的 readme 进行 node 模块的编译。它代码里写死了编译进 src 目录里。然后,编译没成功,它把我整个 src 文件夹删掉了。我谢谢你🙃。还好我是用 git 做代码管理的,而且 vscode 也有很多个 tab 开着。不然临时改的 CSS 又得我重新改一遍。

然后我发现它自带了一个 .node 文件。于是我就想着直接用它编译好的去试一试。

然而并没有这么简单——vite 不支持引入 .node 模块。

根据 issue: Cannot bundle .node files,vite 目前并没有集成 .node 的支持,而且好像也没啥动静(最后一次动态是 2023 年九月)。但是 issues 发起者提到它试过几种插件。虽然他说没用,但是我自己去试了一下,勉强能用——

目前试了一下 vite-plugin-native 这个插件,不能正常使用。首先它没有 esm 支持,vite 里不能用 require,我只能把它的源码拷出来转换成 esm 用。第二,它的路径拼接有问题,我进去它的源码,改了一点东西(比如把 path.posix 换成 path),最后我直接写死了它的 root 拼接规则为 ./ 开头,然后就能用了。

最后就是回到 electron 那边继续调了。electron 28 搭配 mica-electron 使用依然是有问题的,比如说缩放动画没了、失焦时会出现一个很丑的蓝框。解决方法是把 electron 回退到 24 版本。尽管如此,mica-electron 新建出来的窗口依然会有尺寸缩到最小这个 bug。

总结!

屎山!

从半透明模糊特效来说,相关 API 有 Windows Vista、Windows 10、Windows 11 三套。上微软官网开发者文档的桌面窗口管理器 里还能在示例里看到早就已经不支持的 Windows Vista 系列 API,而却并没有说明这功能在新的操作系统里已经不生效。

从生态链来说,electron 作为 GitHub 的作品,GitHub 作为微软的子公司,electron 跟 Windows 少说也得表现得亲如兄妹互相配合才对啊,然而这一个功能搞了几年?几个 electron 大版本过去了都没搞好。

屎山多了,微软修不动了。

那接下来的方向是什么呢?是根据 issues/14289 尝试另外两个 .node loader?是根据 issues/29937 里 Brouilles 所述试一下用 Windows API 的方法实现?是根据 issues/29937 试一下把 mica-electron 换成 electron-acrylic-window?

2023-01-04

今天结合之前看的东西大致摸出了一些实现毛玻璃的方法。

首先 Windows 10 和 Windows 11 的毛玻璃特效是分开的。Windows 10 能用那个未公开 API,但是在 Windows 11 使用会导致窗口拖拽迟缓。而 Windows 11 特有的 mica API 仅能在 22621 版本或者更高的才能使用。由于 Windows 10 始终是要被时代抛弃,所以我也不打算用那个特效了,虽然它更自由 ╮(╯▽╰)╭。

那 Windows 11 的 mica 特效要怎么做呢?

首先是要安装 Windows SDK 22621。Visual Studio 安装器目前只能给到 22000 的 SDK。更高版本要手动下载安装。然而 Windows SDK 也不是像 Visual Studio 安装器那样就一个项,而是进去之后还能选好多个子项。目前并不知道有哪些子项是需要勾选的,我第一次选择就没成功安装上。 安装完成之后,在 Visual Studio 里引入 dwmapi.h,然后在代码里写一个 DWMWA_SYSTEMBACKDROP_TYPE。如果 IDE 能找到定义,说明这步成功了。接下来要去项目设置里添加 dwmapi.lib 文件的引用(得亏我作为一个前端程序员知道 C++ 的这些)。如果编译成功的话,那么这一块就解决了。 接下来就是逻辑。首先,窗口需要设置成非透明的(透明窗口会怎样我也不知道)。然后需要用 DwmExtendFrameIntoClientArea 传个 -1 的 margin 进去(issues/29937 和 mica-electron 源码中均用了这个方法)。然后,用两次 DwmSetWindowAttribute 分别开启背景主机画刷和 mica backdrop。此时被开启特效的窗口会呈现 mica 背景色但不透明的效果。根据 mica-electron 那边的做法,需要先隐藏一下窗口再打开,此时就有效果了。但如果只做这些,窗口缩放的时候就会失效。所以还得加事件监听,在窗口缩放的时候再应用一次效果。除此之外 mica-electron 还做了一些其他 frame 相关的处理,作用未知。

没错,就是这么啰嗦。不是应用了就完事,而是还需要加监听。

2023-01-05

今天把逻辑部分做了。 具体来说是新建了一个 mica.ts,专门负责管理事件绑定、定时器之类的 stuff,其他功能就由 C++ 部分去完成。C++ 部分有三个功能:开启效果、关闭效果、开启负边距。

目前观察到的问题是:启动一段时间后,helper 好像就不响应我的操作了。这个还需要后续再看下。

具体的半透明模糊主题,就放到下一次 commit 去做。Windows 上的这个效果分为浅色模式和深色模式,浅色模式的半透明略微偏亮,而深色模式的半透明略微偏暗,因此我还得做两套皮肤,这多少是有点劳苦。另外,我想要用这套模糊 API 在 Aero 拟物化和扁平化之间找到一个平衡,这好像没见人做过,不太好找这个平衡。到时候试试弄一下。

2023-01-16

毛玻璃不弄了。 首要原因是,我现在没有这种心情去做这个主题。一方面,这样做出来很可能就不会好看;另一方面,做没有心情做的事效率太低了。(最后一方面,实测在 macOS 里没有办法让界面在失焦状态下仍然展示一个比较通透的背景。) 看着以前保存下来的毛玻璃软件截图,我动不了一点。现在的状态就跟去年三月份那样,苟着,但我肯定比当时更没动力。 我想,当中的原因就不需要在开发日志里说了吧。这个 commit 本身就是干这个事情的——

软件使用许可和条款。

中文简称“使用条款”,英文称“LICENSE”。随便吧,二合一得了,反正这俩词都是有效的。我自己的软件,想咋弄就咋弄。

做这个的大致思路有两个。 第一个思路是把 LICENSE 写成一个 html 文件,在 FFBox 里用 iframe 引用它。好处是方便,不用引入什么别的东西,没什么开发量。但这样就是不太规范,所以优先不选择。 第二个思路是写一个 markdown 文件作为 LICENSE,然后在软件里读取它。 markdown 怎么读取呢?首先我想的是调库,引入一个 Parser,生成 AST 给我弄,毕竟 markdown 也不算是“极其简单”的格式,自己用 if 嵌套搞半天效果还不一定好。后来当我找库的时候,就想着直接让它给我生成 HTML 算了,毕竟 AST 转页面渲染的步骤也不少。 最终选定的库是 simple-markdown。我选此库的第一要义就是要够“简单”,毕竟只是用来展示一个 LICENSE,不要引入那么多东西进来。所以我在找的时候,就会关注 npm 上显示的 Unpacked Size。 这个库也不算是很优秀的方案,因为它里面含有了我完全不需要的 react 相关的代码,同时它把 @types/react 放进了 dependencies 里。看它只是个 @types,行吧,反正也不是很大。 剩下的操作就基本一路通畅了。给 nodeBridge 和主进程加点功能,找个适用于“使用条款”的图标、调一下色、微调一下外观,写一下使用条款页面的样式,就好了。

有学到一个知识点,跟 Vue 样式相关的。因为 markdown 转 HTML 之后是直接用 innerHTML 的方式丢进去的,这样做会导致加了 scoped 的样式对 innerHTML 里的东西不生效,原因是 scoped 是在 css 选择器的末尾而不是前面加上了 data 属性选择器,而 innerHTML 里的元素没有 data 属性,就选择不到。解决方式也是现学的——在 innerHTML 里面那层的每个选择器前面都加上 /deep/,这样 Vue 就会让 data 属性选择器放在前面。另有用 >>> 标识、:v-deep 之类的用法。我这实测不行,估计是 less 不识别的缘故。anyway 能用就好。

最后讲一下 LICENSE 正文的部分。这块写了几天,其实也没什么好说的,反正就是“语言大模型立大功”,在写下半部分的时候给我的用词提供了很多不错的建议。不过我可以确保,条款里提到的几种行为,都是我本人所认为的最不可接受的几种行为。至于其他的违反基本道德和伦理原则的行为,语言大模型当然可以列出来一堆,但是我如果列在我的条款里,那么没有人能够用上这款软件,列为“行为准则”也占据了不必要的空间,所以就不列了。

最后把文末的时间定在了 2023-02-03。我在“最后修改日期”和“生效日期”之间纠结了好久,也问了不同的语言大模型我应该用哪个日期,最终我选择了一个未来的时间点。这个日子,很重要,但是我不能预测未来,我也没法说出“但愿万事皆所盼”,因为我已经不盼什么了。希望我人还好吧。

2023-01-31

在做软件开屏。 花了非常多时间。 其实这是一项很早期就想做的工作。就是在 FFBox 主程序启动之前,先用一个轻量级的程序显示一个开屏,可以展示启动进度。毕竟 FFBox 主界面启动起来要将近 2 秒,这个速度多少是有一点点慢。3.x 时期做过开屏,但那其实更多是个花瓶,在现在做窗口不透明的前提下,这个花瓶就没法用了,始终要做个真的。

首先我的拿手好戏 VB 当然就被排除了💔。主要原因是它不支持半透明窗口(以下“半透明”均值带 alpha 通道的窗口)。想要实现那种窗口肯定是得从窗口内容的渲染方式上更改的,这个在古老的 VB 里已经没法改了。 VB.NET,或者更直接一点,C#.NET 呢?假设它真的可以做到半透明窗口,它最致命的问题还是需要使用 .NET 框架。虽然 Windows 8 已经自带 4.5 版本了见表,不用担心用户没有的问题,但它始终要引入框架,多少是要占点启动耗时和内存的,所以暂时搁置。

我考虑看一下我很久以前就在头条听说过的语言 aardio。 aardio 的 IDE 安装包压缩包整个只有 6.5 MB,非常的 impressive。作者自己用微信公众平台做了个入门教程,非常地亲民,而且也在里面介绍了 aardio 能实现的各种功能。无论是从微信上放的文章,还是 IDE 启动页的那一堆链接,都可以很明显地看出来这个作者心中的小骄傲——用尽可能小的包体积,实现很多很多的功能,帮助编程爱好者实现一些很 fancy 的小程序。真的很 fancy,相比于用正规的、庞大到几 GB、几十 GB 的编程套装,花大量精力实现一些小功能,这玩意确实简单易用。 他还提供了语法速览。我是用最低速度的自动滚动模式去看这篇文章的,因为有过几年的编程经验,各种概念理解起来都很容易。我可以说,我一边阅读着文章,一边就能感觉到我的大脑在疯狂地写入——真的好久都没有过以这么高的速度往我脑子里灌入知识的感觉了,灌得我脑子发痛🤣。 语法是很现代的。倒是 IDE 我感觉有点古老和不适应。它用了不知道是自己实现还是框架自带的老 ribbon UI,在高 DPI 下的缩放非常奇怪,性能也不怎么好。代码编辑器方面也没有什么提示,上手还是要适应一段时间的。 不过我当天就决定暂时搁置了。我要做的事情很简单,用它的其中一种模板替换一下,显示自己的 logo 就好。但我试着用最基本的无边框窗口生成了一个 exe,就已经有将近 1.5 MiB,启动耗时也超过了 0.6s,这样的话就暂时不考虑了。

回到 VS 提供的开发方式上。除了我使用过的传统 C#.NET 以外,还有好多种框架。M$ 是真的搞了好多这种东西。 在 编写首个 Windows 应用 - Training | Microsoft Learn 里,微软介绍了几种创建 Windows 应用的框架。

  • 首先是 Win32,纯原生开发,最传统的方式,窗口通过调用 Windows API 创建。这是目前最终选择的方式。
  • 然后是 Windows Forms。这就是上面所说的 C#.NET 了。我尝试用 .NET 4.7.2 和 .NET 6.0 创建了两个工程。其实也就只有使用 .NET 4.7.2 老框架的是正常的,生成出来的程序只有 8 KiB 不到,启动也是半秒就能完成。而使用 .NET 6.0 的工程,连工具箱都是空的,生成出来的空窗口应用就有 146 KiB,关键是启动实在太慢了,足足 3s,比我 FFBox 本体还慢,无法接受。
  • 接下来是 UWP。这是微软从 Windows 8 开始搞的一个框架。在我印象中,这玩意的启动速度也是非常 impressive 的,可能 0.2s 就能把窗口建立起来了,毕竟窗口本身有可能是依托在一个系统进程上的(我也不懂,猜是如此)。但是现在似乎已经不能再创建新的 UWP 应用了,在我用 Visual Studio Installer 安装好什么“通用”什么的东西之后,Visual Studio 创建工程最多也就有个“Windows 应用程序打包项目”,没有“空白应用”。而且,微软也在不少地方提到,UWP 算是 WinUI 2,微软更推荐使用 WinUI 3。那么 UWP 就暂时先不考虑了。
  • 然后是 WPF。这玩意在我尝试的时候只能发现了 .NET 6.0,直到写到这里的时候才发现可以用 .NET 4.7.2,而且启动速度跟上面用 Windows Forms 的差不多。那么是不是可以认为,WPF 只是用 XAML 代替了 Form Designer?以后有机会可以研究一下。
  • 最后是 Win UI 3。但这玩意最大的阻碍是什么,是必须要上架微软商店才能以发布模式运行,而且它不是独立 exe,而是附带了超级多一堆 dll 在旁边,直接 pass 掉。

最终是选择了 Win32 原生开发。主要考虑的是,Windows 本身是用 C++ 写的,那么 Windows API 所提供的函数使用 C++ 当然是最原汁原味的,理论上找教程、帮助都好找一点,而且自由度方面应该是最大的,方便改出自己想要的效果。另外,它不使用 .NET 框架,理论上加载负担会变少一些。 只是,它并没有想象中简单——

要解决的问题有两个:第一个是怎么用 Windows API 创建窗口,第二个是怎么绘画图像。 创建窗口这步比较简单,我问了一下 Copilot,又搜了一下如何在控制台程序里新建窗口,再对比了一下新建的 Windows 窗体应用我几年前做过的 MFC 应用,很容易就解决了。 绘图方面,Copilot 让我用 GDI+ 来实现。这个也比较简单。 那如何实现透明窗口呢?Copilot 让我创建一个分层窗口(WS_EX_LAYERED)。透明窗口这玩意我从初中开始玩,到这时我才第一次领略到这玩意是有多诡异——

首先,如果只指定了 WS_EX_LAYERED 样式,那么窗口会直接消失。一般来说大家会调用 SetLayeredWindowAttributes 这个函数,传入 alpha 和 colorKey,让整个窗口半透明,窗口才会出现。但我想要的不是整个窗口半透明,而是带 alpha 通道让我自己绘图。 继续寻找发现了 Creating a transparent window in C++ Win32 - Stack Overflow,里面贴出了代码以实现创建半透明窗口画一个基本图形。 经过了我对什么是 DC HDC CDC hBitmap bitmap 之类奇怪的概念的了解之后,再经过各种常量的反复开关,我大致是实现了一个基本想要的效果,并且了解了以下的一些原理:

当窗口被指定为 WS_EX_LAYERED 之后,原窗口就不显示,不再使用 WM-PAINT 事件响应绘画了,而是需要通过 UpdateLayeredWindow 去把图像贴到【分层窗口】上。而 SetLayeredWindowAttributes 这个函数大致是微软做了一些小动作,让它虽然是分层窗口但显示的还是原窗口的内容,继续沿用旧的渲染方式而不需要使用 UpdateLayeredWindow。

分层窗口是怎么个诡异法呢?比如说窗口内容的绘画似乎是基于屏幕内容去定位的!也就是说,我在创建窗口时指定的位置没有用,更新画面的位置是在 UpdateLayeredWindow 时指定!但是当你以为画布的范围是全屏幕的时候,又不对了,任务栏缩略图里是可以看到这个窗口的区域只有你设定的图片的大小!而且如果图片是镂空的话,鼠标在镂空区域会有穿透效果! 另外还有个诡异的点是,如果我不设置分层窗口,而是在创建普通窗体的时候把 hbrBackground 设置成 0,那么窗口的背景会从屏幕画面里截出来 pia 一块到我的窗口上!因为我没有设置高 DPI 感知所以这个特别明显。 更诡异的事情来了:回到分层窗口那里,如果我想在图片上再画一条线、一个矩形显示进度怎么办?我问了一下 Copilot,它给了我答案。然后,那块绘制出来的形状,以“叠加”的方式跟窗口背后的东西混合!我从来没见过这么奇怪的窗口,图片部分是正常显示的,矩形、线条、文本这些我自己绘制出来的东西是以“叠加”方式跟窗口后面的东西合成的!同一个窗口还能有多种不同的混合方式?除了 RGBA 以外还有一个通道记录混合方式?我不理解,Copilot 也不理解我的不理解。

而且 DC 这些东西的使用方法还是太奇怪了。函数一会儿在这个头文件,一会儿在那个头文件;调用 HDC 的功能有时候是直接调函数传引用进去,有时候是使用对象的成员运算符调用,DC 和 bitmap 的用途傻傻理不清…… 我问了一下同学,他说 GDI+ 这东西太古老了。我觉得也是。有没有什么现代点的方法呢?Direct2D / Direct3D。

这个方案对于我的缺点也很明显:太过复杂。我找到了一篇文章:借助DirectX的窗口半透明(●'◡'●) - 知乎。虽然文章里的语气挺轻松活泼,但代码量足足有好几个屏幕,难以入门。 于是我又找到了一篇文章:你的第一个 Direct2D 计划。我照着它的代码改写(因为它用了类,而我没用)到我现有的工程里面,还算比较顺利地实现了预期的效果。 但这个仅仅是实现了非透明窗体的绘制。真正想要实现半透明效果,还得用 Direct3D。此时 Copilot 其实已经不会了,所以主要代码还是得从上面的知乎文章中弄下来。 弄这个可花了我不少时间。因为对 C++ 不熟,所以弄了好久,连编译都过不去。弄 template 语法,弄 std::format,弄 unichar;弄 throw;因为有句 #undef interface 导致上面和下面的 include 有冲突;因为窗口尺寸没指定对导致 SwapChain 创建不成功;搜 ComPtr 是啥……各种东东搞了好久,终于实现了在一个透明窗口上绘画一个矩形。

最后是基于 DirectX 的图片绘制。在这方面,我参考 【Direct2D】绘制位图 的代码去绘制图片。在 DirectX 上绘制图片比 GDI+ 要麻烦一点,因为要自己处理解码之类的事情。在这块,up 主并没有提供一个关键函数 LoadBitmapFromFile,是后来评论区有人问他才补充的:如何从文件加载位图 - Win32 apps | Microsoft Learn。我最后再捣鼓一下,终于实现了在透明窗口上显示图片、文字。

此时把应用生成一下,好家伙,200+ KiB,启动耗时得 0.3s。然后我又对比了一下内存,好家伙,内存 18 MiB,提交 68 MiB,比 C#.NET 版本还大一倍。好家伙,那我为啥不用 C#.NET? 其实这就涉及到我关于 exe 之间调用关系的想法了。我的想法是把这个窗口做进 FFBoxHelper 里,让它来启动 FFBox,所以才要用 C++。虽然很可能有更好的方案,但是不管了,做都做了,先这样吧。

技术可行,接下来就得实现功能了。 写了 5000+ 字日志,快吐了。

另见一些可能有用的网页:

2023-03-18

踩进了大坑里。

前一篇的调研只是解决了绘图问题。剩下的进程间通讯问题依然非常棘手。 我设想的计划是,用 FFBoxHelper 做开屏,那么就涉及到 FFBox 主进程通过进程间通讯的方式来告知启动进度了。

electron 主进程那边的代码就不用说了,一切都很简单。然后 C++ 那边把之前 demo 的代码搬进来调整一下,在 main 里先创建个 std::thread 去显示窗口,然后启动 FFBox,创建个 std::thread 去 WaitForSingleObject 等待 FFBox 退出的时候把自己也退出,然后主线程进入状态机循环。一切看起来都很简单对吧?

这样是不行的。C++ 那边根本就接收不到 electron 主进程那边发过来的数据。 原因是很傻逼的,而且不是我傻逼,是 electron 傻逼。electron 故意把代码里的 stdout 重定向到 console,而不是标准输出流。这是个上古问题了,在 issues 里提到,然后 electron 里的人说 won't fix,就没有下文了。这是问题的根源。

issue 里有人说,可以通过设置 ELECTRON_NO_ATTACH_CONSOLE=true 这个环境变量来让 electron 不要自作主张乱搞。 那么问题来了,如何把这个环境变量传给它呢? 根据 Copilot 的回答,CreateProcess 函数可以用于创建一个新的进程,可以通过 lpEnvironment 参数来设置新进程的环境变量。 并且根据它贴出来的代码,连编译都过不了。为什么呢?因为 lpEnvironment 的类型是 LPVOID,TCHAR 转不过去。而且别说是 Copilot 给的代码编译不过,就连网上博文给的代码也是编译不过的。我不知道这种人发技术文章是什么心思,又想拿流量又故意写错不让你一帆风顺? 那么这个 lpEnvironment 的类型究竟是什么玩意,我不知道。我继续问 Copilot 如何在原有环境变量的基础上附加环境变量。它告诉我通过 GetEnvironmentStrings 函数获取 LPVOID/LPCH 类型的原有环境变量(问了两次才出来,中途它改了代码)。同样的,LPCH 这个类型也是不能被 GetEnvironmentStrings 接收的。而且 std::string 处理 \0 字符我觉得不行。 于是我直接把 GetEnvironmentStrings 的返回搞了个 auto 然后去看,结果它输出的结果是 =::=::\。 于是就有人问:What are these strange environment variables?。根据里面的回答,这个函数是微软拉了又不清的屎。这罪证很清晰明了的,在微软的文档里,根本就没说这函数用不了了。坑👍! 至于 GetEnvironmentVariable,它只能做单个查询;SetEnvironmentStrings,似乎不起作用。如果 lpEnvironment 里传了些不正确的值,那么就连用来测试的 mspaint 都无法启动。 那老老实实想怎么自己拼接环境变量传进这个 lpEnvironment 里呗。这个问题给出了很好的回答,通过 C 语言的内置魔法,extern char **environ; 就能在 environ 里取到一条条的、char* 类型的环境变量。好用得不得了。然后怎么办?自己拼接字符串呗,搞两层循环,strcpy,用 \0 分割不同的环境变量,用 \0\0 指示结束,然后把这块 char* 的东西丢进 lpEnvironment 里,搞定! 定个屁,C++ 那边还是什么数据都接收不到。也就是说 electron 还是没把数据发过来。 我干脆手动把这条环境变量加入到系统环境变量里。好家伙,真起作用,但是起的是什么作用呢?是直接无视了我的 stdout/console.log,然后该不连接的标准输入输出流还是不连接。 好家伙,fuck you Electron! 怪不得 issue 里有人说没用。就是不知道那些 issue 里说能用的是怎么回事。

那么标准输入输出流被 electron 拿来喂粪桶了,还剩什么方法可以进程间通讯?命名管道。

2023-03-19

经过了一番调研之后,终于是把命名管道通讯搞好了。

首先,命名管道分为服务端和客户端。创建这两种对象的方法,C++ 中是 CreateNamedPipe 和 ConnectNamedPipe,node.js 中是 net.createServer 和 net.createConnection。一开始我没搞懂这俩,于是给两边都上了服务器,这样当然就会有冲突。 根据这篇文章的方法,我创建了 C++ 的服务端。不过,他用了 FILE_FLAG_OVERLAPPED 这个常量。我执行到这一步依然没法建立连接,而且它每发送一次数据就要把连接关掉重新打开,这太奇怪了。所以我去微软找了官方文档,它说这个常量仅用于跨计算机之间的通讯。好家伙,看来这篇文章说的还是有问题(但谁知道是微软文档有问题还是 CSDN 的文章有问题呢)? 于是我跟着 Copilot 的代码去修改,终于实现了管道连接。并且它不需要我手动分割,它自动就会把每次发送的内容分割出来。看起来终于是实现了这块的功能。

但是当我以为“理论可行,开始实践”的时候,我把 client.write 指令放到 Promise 完成之后去做,就不行了。排查之后,发现管道连接仅在连接成功的那个回调函数中可用,一旦出去了,比如说 Promise 完成、setTimeout,这个管道都会马上被关闭。 我怀疑过是自动 GC 的原因,但是我无论把 net 丢哪,都会这样。我把 C++ 那边的超时时间调大,也会这样。通过 net.Socket 事件,了解到这个关闭管道的操作似乎是 node 自己产生的。我又试了下把 initPipe 丢到 app.whenReady 之后,同样不行。 后来我怎么解决的呢?我在 C++ 那边把 pipeMode 从仅输入改成双工之后,就没问题了! 就这么一个常量,花了我多久去 debug🙈。 似乎理解了为什么公司的后端有时候要来来回回发好多个 dll 才能把一个功能调通。因为有时候就是会有些这么奇怪的问题出来😂。


捣鼓了一段时间,把 FFBox 和 FFBoxHelper 之间的启动和进度告知关系、进度条绘画给捣鼓出来了。 正当我满心欢喜地把编译好的软件传给公司的电脑,准备看一下那台电脑的启动慢在哪里时—— ——开屏弹窗它根本就弹不出来! 公司的电脑环境确实是有点问题,但这回是我自己写的 C++ 程序了,不是这都能出问题吧?

虽然开屏弹窗弹不出来,但是它确实可以启动 FFBox,而且从 console 里可以看到它确实是能正常输出管道消息的。于是我开始加 cout,看问题出在哪。 我怀疑是 initSplashScreen 这个函数执行到一半 crash 掉了,所以我开始找如何能在线程结束之后触发回调函数,像 js 的 Promise.then 那样。但是这又要涉及到一些我不懂的概念,比如说 std::future、std::packaged_task。弄了几下之后跑不通,于是我选择另辟蹊径,再创建一个线程用来等待这个 initSplashScreen 线程结束,然后 cout。得出的结论是:initSplashScreen 这个线程似乎卡在一半不动了。 于是我对 initSplashScreen 里的各个关键动作加上 cout,看是哪一步出问题了。 结论是:ShowWindow 这步卡住了。我想,如果把 WM_SHOWWINDOW 里的动作先去掉,它还会不会卡住?实测这样就不卡住了。 也就是说 WM_SHOWWINDOW 里有东西失败了。进一步发现是 zhihuPaintPrep 里的东西有问题。但怎么才能知道是哪一步出错呢? 我没有再一个个 cout 了,我看上了原有的 TryThrow 函数。为什么发生错误之后没有报错呢?我不知道,但是我把 throw 改成了 std::cout。这样一来,就看到问题了——CreateDXGIFactory2 这步出现了 0x887a0001 错误。在这个 页面里可以查到错误码对应的消息:应用程序提供的参数数据无效。 那我又不会 DirectX 这套东西,我鬼知道什么参数无效啊🙈~ 我不知道,但是我看到“DEBUG”这种字样,感觉不详,就试着把 DXGI_CREATE_FACTORY_DEBUG 改成 0,结果,嘿!窗口创建成功了! ヽ(‘ー`)ノ 真的是波折……


最后试一下把开屏画面做好看一点吧——先试着让进度条发光。

我直接把我的需求告诉 Copilot,它告诉我 ID2D1DeviceContext::CreateEffect 一个 CLSID_D2D1Glow 的效果。 我不知道它从哪里找到 CLSID_D2D1Glow 这种东西的,微软文档上没有,搜索引擎也搜不到。 在微软的阴影效果官方示例里,指出了阴影效果使用 CLSID_D2D1Shadow 实现。然而它的示例代码多少是有点复杂,我看不太懂,于是继续请教 Copilot,以此示例为基础创建一个带有投影的矩形。然后通过代码摸索了一阵之后,得出了以下结论:

  • D2D 的绘图目标是 ID2D1BitmapRenderTarget,简称 renderTarget。可以在这个 renderTarget 上进行各种操作,比如说 FillRectangle、DrawRectangle、DrawImage、DrawBitmap。
  • renderTarget 需要绑定一个 ID2D1Bitmap,简称 bitmap。在 renderTarget 上调用绘图函数只是将操作存了起来,这个 bitmap 才是用来存储图像的东东。renderTarget 的所有绘图操作都需要包在 BeginDraw 和 EndDraw 之间。
  • 最外层也有一个 renderTarget 和 bitmap,不过它们是特殊的,分别用 d2dDevice->CreateDeviceContext 和 dc->CreateBitmapFromDxgiSurface 创建得。而内层的这俩玩意只需要 dc->CreateCompatibleRenderTarget 和 bitmapRenderTarget->GetBitmap 即可获得。

而至于阴影效果,它不是作用于单个矩形或者画刷什么的,而是通过 SetInput 作用于整个 bitmap,然后在 renderTarget 里通过 DrawImage 把它画出来。

2024-03-20

最终决定是:不鼓弄这玩意了。

这玩意实在是很难懂。什么 Bitmap、Image 的,实现单个元素的绘画还好,多重的搞起来完全不知道是怎么回事。 我给“画一个带边缘发光的矩形”做了一个单独的 drawRectWithShadow 函数。函数里会新建一个 renderTarget,在上面画矩形,施加特效,然后画回到外面的 DC 里。 这会发生什么问题呢? 比如说,两个矩形的绘制操作之间如果不加上 EndDraw、BeginDraw,那么画出来的东西会叠加好几次,甚至会把第一个矩形的画刷颜色应用到第二个矩形上。 那么 EndDraw 的作用是什么呢?把绘图操作应用到 bitmap 上?并不是。 如果在新的 renderTarget 上画了矩形,然后在 DC 上 DrawImage,然后把 renderTarget 给 clear 掉,那么 DC 上也不会画出来图案。 我的想法是先画矩形和阴影,然后对整个画面做一下锐化,再贴到 DC 上。如果我直接画矩形、添加效果,然后再以当前的 bitmap 添加效果,会发生什么呢? 答案是,“画出来的东西会叠加好几次,甚至会把第一个矩形的画刷颜色应用到第二个矩形上”。

行。 太难搞了,不搞它。C++ 搞界面的性价比太低了。没那么好看就没那么好看吧。

最后我研究了一下 FFBox 启动过程中代价比较大的操作,然后把启动进度分成了 5 段。代码里有,我就不复述了。


另外,这个 commit 也对一些别的地方进行了一些小改动,比如说取消了语言选项,以及一个极其简单的 LICENSE 防篡改机制(后面还会完善)。

当初设计一个语言选项的目的是为了给粤语增添一份人气,说得难听点就是保护粤语。而取消这个选项的原因则是出于对语言或者方言之间的一些动态和模糊的关系的考虑。具体原因有 2000 多字,可以详见 B 站的动态,这里就不赘述了。

至于 LICENSE 防篡改,我知道你们想到什么。 对,这只是个形式大于实际意义的东西。 没人能真正阻止一个人在亵待他人的感情的情况下使用我的软件。就像我也无法避免把感情投入到一个亵待我感情的人身上那样。 当了一回 Joker ╮(╯_╰)╭。💔

人对待他人,能不能做到真诚? 我不知道。可是若想要对方做到真诚,首先自己就必须要做到真诚。 这是我的态度,也是我的第一品格要求。

所以,你用我的软件,能不能做到真诚? 你与我做朋友,能不能做到真诚?

FFBox,欢迎愿意构筑美好世界的大家来使用。❤️

2024-03-25

上次关于软件开屏的工作,一做就做了两个月。 实际上并没有那么久。只是有一点波折。 虽然 FFBox 是我人生中一个重要的作品,但众所周知的是,我的主线任务并不是写这个东西。 当然我也不会在这个日志里透露我的主线任务。总之,一月份整个月都在承受着很大的心理压力,而二月份先是心被伤了一刀而后又生理性地被伤了一刀(莫名其妙地生了一次十几年都没有过那么长的病)。直到三月份才开始干活。 而我希望拾起那十年前就想做的那个软件。它等我太久了。 我花了半个月时间,给它开了个头。龘字写下了第一个点。

回来探望我的 FFBox。 此时看看需求池里的那些功能,再看看我的开发意愿和速度,似乎离完成遥不可及。而这个版本,已经快做了两年了。 从本地运行的体验来说,FFBox 目前已经做好了。那么,这个版本,已经基本上是可以发布了。 所以,我打算把剩下的一些小项修补好,然后就发布。时间定在 3 月 23 日。 但我后来打算放到 4 月 1 日再发布。

因为突然说发布就发布,太急匆匆了😂。 首先,Windows 那边是好的。但是 macOS 那边,我没按正常流程跑通过。macOS 有沙箱机制,所以要调用像 ffmpeg 那样的二进制文件,不能像 Windows 那边那样简单地写个“ffmpeg”就能让系统自动找到环境变量和同目录下的 ffmpeg 那么简单。这个问题在 3.0 时代还能勉强解决,就是把包的内容打开,直接执行 Contents/MacOS/FFBox,就能调用到系统的 ffmpeg。现在除了 ffmpeg,还多了一个 FFBoxService 要单独调用。那它的路径去哪找?

经过一些调研之后,得出的结论是 macOS 要自己拼接完整路径。FFBoxService 是随包放进去的,可以用 resourcesPath 拼接。而 ffmpeg,我打算找两个位置。一个是 /usr/local/bin/,一个是 Resources。同时也把未找到 ffmpeg 的提示改一下就行。 但这样想就错了 (≧▽≦)。FFBoxService 是跑在 pkg 打包出来的环境的,不是 electron,哪有 resourcesPath 这种东西🤷‍♂️。 进一步发现,通过 pkg 打包出来的 node 应用,__dirname 和 __filename 都会呈现出 /snapshot/FFBox/app/backend 这样的路径,也就是说在 pkg 管辖范围内的路径都是它虚拟出来的。如果要在生产环境使用 FFBoxService 同层级的路径,应该用 process.execPath 进行拼接。 于是这么一来,macOS 那端调用 FFBoxService 和 ffmpeg 的问题都解决了。剩下的工作就是把代码给码整齐。后面还再加多了点功能,在 ffmpeg 缺失的时候根据服务器的操作系统显示相关信息。

接下来还有一点工作:把服务器断线重连的状态加上,与第一次连接的状态做好区分(已经做了一部分);适配一下网页端;如果有机会的话可以弄个 Linux 虚拟机做一下相关适配。 以及 FFBox 官网。如果一个软件官网都像现在那样连个下载链接都找不到,那就太糟糕了😂。因此周末的时候花了几个小时思考新的官网应该怎么设计。

2024-03-26

“第一次/第若干次连接本地/远程服务器相关逻辑优化”说白了就是标签页关闭按钮和添加服务器什么情况下显示、什么情况下 IP 必须是 localhost 而且不能改、什么情况下 localhost 自动改 127.0.0.1 之类的东西。 “浏览器运行相关支持”就是把连接服务器——上传——转码——下载这条链路跑通。

2024-03-28

最后一个操作系统支持项是 Linux。 总的来说,我的个人感受是,*nix 系的系统,想要做到适合普通人使用,还是任重而道远。 就个人体验,用这种系统,就像开个汽车,还得去了解电池没电的时候怎样手动把发动机拉着、爆胎的时候怎样买轮胎换上……

我选择的是 Deepin 系统。支持国产势力。四年前做 FFBox 1.x 版本的时候,拿它来用过。 当我把几年没用的虚拟机搬出来用的时候,真的是问题不断…… 首先是 vscode 太旧了,很多功能应该都不好用了,我就想去更新它。我把官网的 deb 包下载下来,然后安装。报错:依赖关系不满足:visual-studio-code。当然,用终端执行 apt install 也是不行的:无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了包间的依赖关系。 网上有人说是因为它跟商店的版本冲突。于是我就打开商店——网络错误。 我想着更新商店吧,deepin 的官网首页竟然什么相关链接都没有。我就去找其他方法。包括编辑 /etc/hosts 加上 36.248.208.254 cdn-package-store6.deepin.com,然后 apt updateapt update 经常会遇到无法获得锁 /var/lib/apt/lists/lock 的故障,此时也没别的 apt 在跑。我用 service lightdm restart 把桌面环境重启了也是有这个锁。反正把它 sudo rm 了就好了。 这样 apt update 能运行了。但是它又会报没有公钥,无法验证签名的错误 NO_PUBKEY 3B4FE6ACC0V21F32。 有一个解决办法是在 /etc/apt/sources.list 里把无法验证的源去掉。这么一去就把所有源都去掉了,依然不行。 另一个解决方法是 apt-key adv --keyserver keyserver.ubuntu.com(这个地址可以换别的) --recv-keys [上面那串东东]。然后它会报错:failed to start the dirmgr '/usr/bin/dirmgr' 没有那个文件或目录。还好,apt install dirmgr没报错,然后导入成功。 但这样还不行。漏了一句:gpg -a --export [东东] | apt-key add -。咱也不懂,反正就一个人指出要加这句,然后就行了。 继续执行 apt update。会报错 无法获得 /var/lib/apt/lists/partial/[几十条东东] 的状态……。我把 partial 文件夹删掉,好了。 网上有说用 apt dist-upgrade 更新全部依赖关系的。我想这样也好,把那些几年前的依赖都升级一下。好不容易执行了几十分钟,到安装流程的时候报错 E: Sub-process /usr/bin/dpkg returned an error code (1)。 咱也不懂 dpkg 是什么。apt 让我 apt --fix-broken install 再去弄。那我就 fix 了。然后这个 dpkg 还是报错。 上面那两条指令来来回回弄了几次,于是电源选项打不开了。我 reboot 了一下,连桌面都进不去了。 彳亍。

装个新的 Deepin 吧。花 5 分钟下载镜像,又花 5 分钟安装。真快,重装比修复快多了。 新的 Deepin 更像 UOS 了。当然现在并不是讨论这个的时候。回到 FFBox。

Linux 适配,核心问题依然是两个:FFBoxService 和 ffmpeg 调用。直接出总结吧。

  • 【macOS service】无论是打包成 .app 还是 .dmg,FFBoxService 都被塞在 macOS 独有的文件结构里,用 process.resourcesPath 就能访问到 service(哪怕是打开包内容双击运行也可以)。
  • 【macOS ffmpeg】系统目录模式下,固定访问 /usr/local/bin/ffmpeg。因为 /usr/bin/ 有安全保护塞不进去,直接用 ffmpeg 又不会自动指向 /usr/local/bin/。程序目录模式下,.app 和 .dmg 沙箱机制没什么区别,把 ffmpeg 塞进 Resources 之后用 path.join(process.execPath, '../ffmpeg') 就能访问到真实路径然后调用。
  • 【Linux service】通过终端直接执行 ffbox 的话,'./FFBoxService' 就能访问到 service。在 unpacked 文件夹双击执行的话,无论有没有终端,用 path.join(process.cwd(), 'FFBoxService') 就能访问到。有沙箱情况下,AppImage 模式会解压到 /tmp/ 里的某个目录执行,deb 模式则会安装到 /opt/FFBox/。两种模式下,path.join(process.execPath, '../FFBoxService') 可以访问到 service。
  • 【Linux ffmpeg】系统目录模式下,可以像 Windows 那样直接使用 ffmpeg 调用。程序目录模式下,AppImage 是便携包,用户把 ffmpeg 放到 .AppImage 同级目录,那么用 path.join(process.cwd(), 'ffmpeg') 就能调用。deb 安装后会来到 /opt/FFBox/,用 path.join(process.execPath, '../ffmpeg') 就能访问物理上的同级 ffmpeg。

[秃头] 这几天都是天亮了才睡觉。楞个麻烦哟,这多平台适配做得。

另外,在开发过程中,也顺带修了个小 bug:FFBoxService 启动速度赶不上 renderer 启动速度,导致第一次登录显示失败。 另外,在 deepin 开发环境下,无法正常启动渲染进程,会报错。这个错误后关闭程序的功能以后再去做了。 另外也增加了文件大小 100MB 的上传限制,避免 CryptoJS 把页面搞崩。

2024-04-01

最后的几天,都在忙着做 FFBox 官网的页面啦~

最后把三个平台的图标配置补充了一下(其中 deepin 的 Linux 不支持 png 图标,需要用 icns),另外发现了 Linux 和 macOS 上 LICENSE 读取不正常,修了一下,完事啦~

祝大家使用愉快!

2024-06-04

最近一段时间,去了两趟旅游,并且在这段时间里出了好多 bug,其中又有好多是从 2023 年继承过来的……伤疤叠伤疤给我整得有点易感、记忆力减退、懒……然后因为欧卡 1.50 的原因,又开始沉迷赛博开车了……这样总归不是个好事。 等我想好要说什么再更新到 LICENSE 里吧……我实在是不懂,我从来不去害人,怎么还能惹到人;为什么人可以随便说一些话,可以随便做一些事情,不用考虑对方在不在意……以及我为什么有这种行为,是以前的哪几道或是几十道伤疤所引导的处事方式或者是 PTSD…… 真复杂。

回到 FFBox。继续开发 4.1 版本,我目前最想改进的东西是什么呢?可能很难有一个“最想”,但有些大致的吧。 我打算做一下跟“数字单位”有关的事情。一是“预计剩余时间”并不需要显示得这么精确,二是“时间”和“帧”旁边的小蓝条现在看来不太需要了,因为已经有比以往更多的元素能表达任务在进行中,三是希望鼠标悬浮在某些显示项时能给出另一个单位的显示。然后我又想把 utils 给整理好(因为要做单位切换就得搞搞格式化函数)。 那最终想法是先得改改目前的参数项。因为让 common 里 utils 的东西去读 setting 可能不太合适,倒不如把格式化交给控件去做,然后以前把 preset 这种东西做成数字格式的,现在也得换成文本格式。

2024-06-07

首先要做的就是把 common/params/types 里的定义改一下,使 SliderOptions 支持文本输入,并转换为内部数字。且 valueToText 由以前的传入函数改为同时支持根据 type、min 等配置生成字符串的配置对象。OutputParams 和 defaultParams 也得改一下。 然后根据这个类型把 vcodecs 和 acodecs 里的项都改了一下。在这过程中还顺便更新了一下参数项的定义,比如说 nvenc 如何使用 crf 模式(之前一直是不成功的)。 然后开始更新 getRateControlParam。先是写死单位是 1000,后面再改 然后改 Slider。给它加一些转换函数。途中遇到了 js 的经典老问题:两数相加的时候会因为浮点精度问题出现超长的小数。我直接在我的 type 定义里加个“integer”解决,统一转换为整数🌚。 改 Slider 时还得同时改 VcodecView 和 AcodecView,适配 props。 此时我发现还缺一个函数把滑块的值转回文本值,于是又给定义加上,参数项加上。 上面这些基础操作做完,就可以正式更新功能了。appStore 加上进制的项,然后往 window 里塞个 frontendSettings(为了避免太多东西接入到 store,导致它身上负担太重)。 最后是改 taskItem。有好几处文本需要进行适配,我都一一测试了最合适的小数位数。 最后我想实际测试一下效果,用很高的参数输出很大的视频文件。然后我发现 crf 或者 qp 调到 0 会是什么样子的这个事情我一直都没研究过,所以又翻出 ffmpeg 的说明看了一阵,确认现在 5.0 版本的 ffmpeg 没有哪个地方又说明把这参数调成 0 是无损。要么是自动,要么是最高画质。我就依此更新了参数项。

这个 commit 大体先放这么多。


对了,上次没提到我为什么又开始更新 FFBox 了。 或许是因为,现在六月份了吧。年初所想到的一些想法又因为上述的一些原因需要冷静(主要是懒)而没有执行,这个就另说了。只是在没有更新的这段时间里,我似乎没有为这个世界留下什么痕迹。最近一段时间发生的事情又因为需要冷静(主要是懒)而至今没有记录。那这几个月以来差不多是一事无成了吧。那别的事情没有动力,FFBox 更新这种常规的东西总可以吧?毕竟,开发日志现在也被我当一小部分日记来用了🌚。

另外,说个大家所不知道的事情——当时在完结 4.0 版本的时候,我就已经打算把工作重心转移到另外一个方向——我的音乐软件上了。这个计划我看了一下,竟然从来没在 FFBox 的日志里提到过。太可惜了,本来 FFBox 日志可以只写 FFBox 相关的东西,由于受到的挫折有点太多,外溢了,所以来这里了🌚。 但其实后来也没有动过那项目了。至于原因嘛……emmm……实在是有点复杂…… 嗯……终于还是等来了这一天——2024 高考。祝愿将要踏入考场的朋友们都能挥洒自如、妙笔生花,满意地踏出考场~高考愉快!

不要辜负我的期望哦~💪💪

2024-06-15

前几天打算做一下 TaskItem 相关的优化。大致思路是鼠标悬浮在某些元素时可以给出更多信息(比如说不同进制啊,原始值和预测值啊,剩余时间和已用时间啊等等,还有不完整的参数给它显示完整啊之类的,后面再想)。不过在做这个之前,先得做一下 TaskItem 本身的优化。

很早之前(2023-04-04)提到过,要把 TaskItem 换成类组件,以解决 unmount 问题。我并不喜欢用类组件,一方面是它长得有点像组合式,无论是 Vue 还是 React,有种很多东西都要套多一层的感觉;另外是最致命的一点:Props 没法用正常人类能想出来的方式去声明。每次我想换一种 Vue 组件的写法,都得打开我自己写那篇对比不同组件写法的文章,看着那令人挠头的 value: { type: String as PropType<Props['value']> } 发愁。 几天前开始做的时候我就在想:能不能在 template 里搞 tsx?于是我搜到了一篇文章【Vue3 干货👍】template setup 和 tsx 的混合开发实践,介绍了这种形式。他提到的方式非常简单:把 <script> 上的 lang 改成 tsx 就行了。但我这么干之后,ts 会报错——return 出去的第一层 div 没事,第二层 div 就提示不能将类型“Element”分配给类型“ReactNode”。全网似乎都没这个问题。我想过一些方法来解决,比如说配一下 @vitejs/plugin-jsx 的 config 之类的,但其实这样没用,因为报错的是 ts。为什么在 .tsx 里 ts 能正常工作,来到 .vue 里就不行了呢?我没搞懂。 我继续看知乎文章的评论,结果让我发现了一种新的写法。查阅文档得知,有一种自 3.3 起的新写法,可以在 defineComponent 里按像 <script setup> 的方式来写。有了这种方法,那么只需要把 TaskItem 的形式从一个函数变成一个 defineComponent 里的函数,就可以了。 操作很简单,一下就搞完了。然后,问题就出来了:组件不更新。 这个问题同样是全网都没有的。我感觉不太好排查,搁置了几天,中途去给我的毕设诈尸更新了一下(

具体是怎么排查的呢? 首先我试了一下在 TaskItem 里直接 console.log。它只 log 了一次。不过这个我后来想了想,其实有可能我当时(2023-10-09)就没搞懂 render function 在什么时机执行这个问题。在原来的 TaskItem 里,task 每更新一次就执行一次,原因应该是这是一个函数式组件,没有状态。而在有状态的组件里,工作方式应该是我之前所说的:跑一次 render function 收集变量引用,后续就直接更新对应位置而不是重新跑一遍 render function 做 diff。这样的话,Vue 的工作效率应该是比 React 高的, 回到刚才的问题。不更新的原因是出在 TaskItem 上还是 ListArea 上呢?我试着在 TaskItem 旁加了个 div,用定时器去刷新。生效。那它塞进里面是否生效呢?如果是用 appStore 里的值塞进去是否生效呢?用 computed 出来的 task 和直接用 appStore 的 task 相比是否有影响?直接传 task 进去在里面读值相比传 task.值的表现是否有区别? 结论是,上面这些操作都没有影响。因为我把这些条件都列出来,最终发现,有两条的条件是相同的,但是结果不一样。 区别在哪呢?直接说答案: 出问题那个,我在 props 上使用了解构赋值。 我是直接想到了这点的。因为在很久以前我就看到过,Vue 里用解构赋值会导致失去响应性。之前只是一直没遇到这种场景而已。

这篇文章里,博主解释了这一事情。我粗略看了一下,也大致懂了一些响应式的原理: Vue 3 响应式的实现原理是 Proxy。比如说要用 ref 或者 reactive 定义一个对象类型的变量,那么 Vue 就会把它用 Proxy 包一层。get set 都是读写原始值,但是就多了个机会可以搜集变量引用和变更。 但如果要定义的变量是个普通值,不是对象呢?这就没法 Proxy 了呀。所以 Vue 其实把你要包的变量都包成了一个 class,统一用 .value 去读原始值。 使用 ref 或者 reactive(我现在还没看这两者的差别)定义的变量,Vue 做的事情是包了一层 Proxy。以下称被包的东西为“本体”,包裹后的东西为“RefImpl”。如果本体是个对象,那么 Vue 会递归把里面的引用也包上 Proxy。 那解构赋值会有什么坏作用呢?解构赋值后,就拿到了一个不带响应性的原始值。这时候,这个值无论在哪里使用,Vue 都搜集不到了。 所以 TaskItem 里把 task 提取出来之后,响应性在 task 这层就截止了。后面 task 里面无论发生什么变化,Vue 都不知道。

最后在写日志的时候,看了一下之前 TaskItem 残余的没解决的难题,其中包括一个 cmdData 没法用 watch 监听更新的问题。这个问题放到现在就比较明确了:函数式组件每次 render 都是新的,不在同一组件里当然没法 watch。

这下妨碍 TaskItem 开发的问题大致就解决了。再回去看那个尝试用 tsx 的 .vue 组件,发现类型不报错了。不知道是我中途 debug 时给 vscode 升级了 TypeScript 的缘故还是我把 Vue 从 3.3 升级到 3.4 又改了 tsconfig 的缘故。反正它类型不报错了。 以后再试试用 tsx 写 .vue 文件。


做好上面这些基础工作之后,今天做了一下 TaskItem 的优化:命令行输出支持自动滚到底、剩余时间数位更改、秒和帧取消横条值显示。

2024-06-16

TaskItem 的优化还在进行中~ 今天把之前一直在想的鼠标悬浮展示详细信息做了。这个功能我一开始是想做在仪表盘上的,但是还没想好要展示什么信息,就先把两个容易遮挡导致显示不完全的项给做了:任务名和参数一览。

我的想法是沿用 Tooltip 组件去做这事,不过得稍微改进一下它。 这个组件渲染文字使用的是 v-html。显然,当年开发的时候,还不会用 h 函数、<component :is> 之类的功能呢,对于需要换行的内容,只能用 innerHTML 这种馊方法解决。现在会了,让它支持直接传 VNode 进去就行。 另外,样式本身就是传 CSS 对象进去,所以也把 position 改名为 style。 然后,在 .tsx 里的用法就跟 react 一样,直接传进来就行了。

除了用 Tooltip 展示空间不足的元素的内容以外,还有个地方我是突然想到的:可以把参数一览浓缩成一项。 浓缩成什么好呢?我一开始想的是“查看配置”。后来想起来,这里放“快捷”里面的预设不就好了嘛。 这样一来,当年的“参数预览功能暂时无用,将在未来版本中更新”和“预设存取功能暂未开发”就终于(误打误撞)(不经意间)做好了。 然后,再修改一下 TaskItem 各元素的定位规则,就实现了同时解放任务名可用空间和浓缩参数一览这两个痛点。 🍺 最后还留了一个 shouldHandleHover 没实现,还有 appStore.taskViewSettings 没理清。这堆东西就等后面想扫垃圾的时候再弄吧。现在只想多欣赏一下新 TaskItem 的外观,看它长得多好看……又想看下这么改一番后性能会改进多少。

2024-06-18

这次来更新一下激活系统。

为什么更新这个呢? 契机是我找到了新的捐助平台,我在里面给我正在期待的欧卡 mod 打赏了几十块,然后也顺便看了一下别人的项目,自己也弄了一个。这种看起来就小小的、很像“个人项目”的平台给我一种熟悉的感觉,没有大公司的距离感,仿佛团队里的每个人都能与用户近距离交流。后面我发现,尤雨溪也在这个平台设置了赞助通道,这就更踏实了。 爱发电里可配置的最低周期赞助金额是 5 元。虽然可以用自定义的方式来实现更低的金额,但为什么它要做这个限制呢? 道理很简单:怕作者或者客户设了太低的金额,所以给了个价格锚点。 观察一下过往给 FFBox 进行打赏的记录,确实会有普遍金额很低的这么一个情况。大家最喜欢给多少呢?1 元😊。 对比之下,国内原地进出一次地铁站,就要 2 元。换句话说,这 1 元表达的更多是对作者辛勤产出的尊重、对付费购买服务模式的一种支持……因为它能给到的价值是让地铁闸机进行一次开关动作。

中国没有给小费的习惯。现在有了互联网这么个不用碰面就能交流的工具,自然是方便了不少人进行“给小费”的操作。相比于在部分外国,想要享受别人的服务时,还得纠结要不要给、要给多少、给少了会不会不乐意、有没有什么潜规则这种糟心的玩意,自然是舒服得多。您不用担心给得太少。就算是不给,我也应该会回复您的咨询;就算是给了,我也未必能及时给到答复。这是完全自愿的。 但这种情况就有个缺点:由于缺乏价格锚点,且国内用户普遍适配“先免费白嫖,直到受不了广告或者功能限制再给钱”的模式,国民普遍都没有较好的性价比观念。大家会选择争着花几百、几千去抢一场演唱会的门票,却甚少人愿意为一曲花费数十、上百小时制作的音乐花出 2 元给它买断;有些还没开始赚钱的学生就买上千一双的鞋子,有些高收入人群则会为买到两元一斤的打折菜而自得其乐……

我是个考虑性价比的人。但我选择让利。做这款软件花费的时间高达上千个小时,假设时薪 100,有 1000 个用户会使用(目前 GitHub 上只有 157 个⭐),那么每人也要花费 100 元去购买此软件。这显然不符合国人的消费观。 因此我按爱发电的标准设立了价格锚点。5 元对应阿萨姆,9 元对应煲珠公,15 元对应喜茶,19 元对应酱香拿铁,25 元对应公司餐补标准,50 元对应“麦当劳疯狂星期一”。119.85 元对应……2023-02-03 那天在“MEN WAH BING TENG 730”吃的那顿饭。希望以后有人能给我个更大的。

我并不需要通过打赏这种形式获得收入,因为在大环境下,拥有一份工作会比这性价比更高。但我希望我的用户跟我一样,是讲究“性价比”的🌚。


回到技术上。

首先现有的激活机制得改掉了。就算我再怎么不重视这个东西,也不能一直是第一次打开软件时随机生成机器码吧?获取机器 id 是比较容易的,我按 node-machine-id 这个库做了一下机器 id 的获取。 然后,激活操作也不能是由前端把机器码也扔给后端了。只要有一个用户在它自己的跑前端的机器上激活,那后端就也激活了,这多傻。所以改成了由前端请求后端的机器 id,放在服务器信息里。 最后一个事情就是 SponsorPanel 的变动了。为了让界面不那么死板,我把 TaskItem 那第一次应用的悬浮信息也应用到了这里。那这么做就涉及到了要把 CSS class 样式放在参数里传过去的知识点。在 .tsx 的组件里,样式用的是模块化 CSS,直接明文写着 import,那搬到 template 后要咋引入?这就涉及到 useCssModule 这个新东西啦。用起来还是挺方便的。

2024-06-20

很遗憾,Chromium 开发者工具的实测表明,TaskItem 改为 tsx setup 函数前后,性能几乎没有任何变化。唯一能看到的变化是,新版 TaskItem 的火焰图里看不到那些 dashboardCalc 之类的我自己写的函数了,时间都是花在 Vue 组件的更新上。 几个小时前我主动看了一下知乎上关于 Vue 和 React 性能的争议,有人指出 Vue pub sub 机制的耗时不一定比 React diff 的耗时少。我持保留意见,毕竟我没读过这两家的源代码,但是我感官上依然认为 Vue 的执行速度要快多了,因为我做过的 React 项目至少有 2 个给我卡的感觉,而 Vue 项目,我指 FFBox,我从来没觉得卡过😏。

2024-06-23

不知不觉,又到了 6 月 23 日了。时光流逝得飞快呀,可是却似乎什么都没做…… 不久前才想起来快到 6 月 23 日了呢,就如同前两个星期那样,直到周四晚上才想起来下周一是端午。去年差不多也是这样啊,我想腾那么点时间出来,去我的大学探望一下我的同学们——那些今年毕业的、大三的,其中就包括了那个她。 “那个她”早已离去。剩下的今年大四的同学们,其实认识得也没有多少。我要去见证他们的毕业典礼吗?似乎不用了。我似乎只是把高中代入了他们而已,却差点忘记大家(包括我)对这大学并不留恋,毕业典礼根本就不算什么事。 所以啊,这事错过就错过了。“可惜”已经永远刻在历史里了。

那今年呢? 关于我的大学,已经不太会有更多延续的事情发生了。而我的高中,记忆早就已沉淀完成。6 月 23 日,是“毕业”日,是 onestop 分镜 7.5 日,是第五部的发布日,是消砖块视频的发布日和高数重考前一日,是 onestop 的发布日,是故日,是我离开大学的前一日,是 FFBox 3.0 的发布日。 原来我或主动或被动地给这个日期赋予过这么多意义🙈。在 7 年间,仅有 2023 年的 6 月 23 日是空白的。 这个日期早已并不具备什么现实意义了。但在这枯燥的生活中,还是多创造点意义吧。 那么,虽然这不是我大四同学的毕业日,但是我祝你们,还有她,毕业快乐!


回到正题。我几天前就定好了 4.1 版本在这天发布。那 commit 的内容呢?按照我以往的惯例,一个 commit 会包含不少的更新。但这次我并没有想到还要做什么更新上去。接近发版日了,最坏情况就是更个版本号完事。

结果突然就被我发现了 bug:本地模式转码完成后双击无法打开输出文件。 那这就得看看是从什么时候这功能开始失效了。结果,我倒回 3.0 版本,发现也是不行的,代码也没变过。好家伙,这功能已经失效这么长时间了。 问题出在了两个点上:第一点是主进程里打开文件用了 child_process.exec,而正确的方法应该是 shell.openPath。这个好改。第二点是打开文件的路径没对,当初加了“输出文件名”之后,并没有把相关的规则应用到 task.outputFile。 越是早期写的代码,现在越难看懂。我这次要改的代码,连注释都不是特别能看懂,命名也是奇奇怪怪的,path、dir、name 都分不清。我大致是把这部分工作留到后期加多输入功能的时候做了吧。现在临发布,先勉强修好它。 实测结果是,Windows 是 OK 的,但 mac 和 Linux 上改了照样不行。mac 上返回的错误信息是 Failed to open path,deepin 上就更牛逼了,开发模式下连窗口都打不开,Linux 生态我实在搞不来。那就不适配了。

然后还修了个 TaskItem 刚创建时没有检测窗口宽度的问题。 还加了个服务器与客户端版本不匹配提示。这种提示我想做成弹窗样式的,给它配个小蓝的图片。配什么图我都想好了,但是现在临近发布,先不做,下个版本再做。

祝各位使用愉快!

2024-07-04

下一步要做的是图表。 原因有很多。比如说这很酷,比如说是因为在某次使用 FreeFileSync 的时候看到它的图表使我决定尽快做出来,比如说这是任务项可交互中重要的一环。各种因素都促成了我优先做这个。

很快就决定好了它的大体样式。用 Msgbox 的壳,把内容塞进去。后续输入/输出命令行都可以塞进去这里,一次性解决两个需求项。 直接把东西塞进 Msgbox 里是不行的,它只会渲染一次。比如说我在 TaskItem 里用了一个 appStore 里的值,塞进了 Msgbox 的参数里,它就是静态的了。为什么呢?因为塞进去的东西是一个 vnode,由 Msgbox 组件负责另行渲染出来,跟主 Vue 就不在同一个 context 里。虽然具体原因我不清楚,但很显然这有点绕了,pinia 很可能做不到监听那么远的引用的。 解决方法也很简单,把它做成一个 Component 而不只是个 vnode。这样一来它可以自己去读 appStore 里的东西,二来可以塞更多东西进去,computed 什么的。 这种需求,大概前端经验不够的都写不出来了吧🌚。往 Msgbox 里塞入动态内容,这么做的地方并不多。

做好这个之后,稍微做一下窗体里的内容(RadioList 什么的),下一步就是做绘图部分的工作了。 这块我使用原生 canvas 实现,不使用框架。我直接就不考虑用框架,因为我在写公司代码的时候已经摸过 canvas 不少时间了,在做多少熟悉了些🌝。 那绘图要绘什么图呢?我设想了四种图表,前两种表示累积量,另外两种表示分布,我就列了个表格去写这几个表都需要以及显示什么数据,然后就一路做。

然后就出问题了🌚。 还记得需求池里有提到的要对 progressLog 改造吗?这回是不改不行了,而且是改两项🌚。 第一项是需要把全量推送改成增量。此前是由服务端裁成 5 条记录发过来前端全量替换的,这个好理解,因为这个设计应该是前后端分离之前弄的,渲染依靠的就是 5 最后条数据,所以在记录的时候就顺便把最早的裁掉了。但是这逻辑在后面都没改过来。现在要整个图表了,后端自然就不能把前面的数据丢掉了。 第二项也是历史遗留,就是 progressLog 里存的转码时间是系统时间,而不是转码所经过的时间。每次暂停恢复后,都会把算一下暂停掉的时间,然后把前面的系统时间记录全改了🌚。这……也倒是能理解,毕竟目的是为了用一次函数里的 k 和 b 算结果嘛,直接把当前时间代进去就好。但有一说一要是直接用 elapsed 时间,其实根本就不麻烦。只能说在面对数学问题的时候,我的脑容量就是软盘级别的🌚。 往前看了下代码,这竟然是 1.0 版本遗留的设计。那时候的代码啊,在那 calcParaDetail 用字符串组装 html,然后手动绑 Vue……看不懂看不懂🌚。

2024-07-06

之前就有过通知做增量的操作了,所以把进度做成增量并不难,照猫画虎即可。 涉及到仪表盘计算的地方,做一下 elapsed 时间的计算,也不难。

做的时候也把一些以前遗留下来的细节上需要修改的东西,或者不修就会看起来是 bug 的东西,也修了。比如说:

  • 任务状态变化到开始的时候,清理 dashboard_smooth 的东西,这样每次开始任务时仪表盘的数据都是 0。
  • 任务刚开始的时候 speed 是 0,此时预计剩余时间是 Infinity。这次修了。
  • ffmpeg 状态机里读取状态的条件把“Lsize”排除了,这是之前用 scanf 函数时留下来的东西,但这样判断是不对的。这次修了之后视频任务结束时就不会多出一条到处都是 NaN 的数据,导致仪表盘结束的时候数字发一下神经 (. ❛ ᴗ ❛.)
  • 结束任务时,后端把 elapsed 记录下来,否则前端不知道总耗时。
  • acodecs 和 vcodecs 里检查编码器的 parameters 和 ratecontrol 没有判空,会导致修改到“默认编码器”的时候出错,导致整个接口都不返回,前端表现无法添加任务,接口 404。

2024-07-09

上次的进度是成功绘画出了“进度”表,并以打点的方式在画面上呈现。 这次先把“打点”改成了画折线,然后去做“数据量”的表。

首先要算数据量的最终值。它的计算方式是当前已产出的量 + 剩余媒体时长 * 当前码率。ffmpeg 给的尺寸单位是 kB,所以我这里也全都在用 kB。types 处的注释不太够,花了一阵子时间想这个事情。 做出来图表之后,发现最终值一高一低的。这是因为 size 并不是在每一次报告 status 的时候都会变化。这除了会导致根据最新码率计算的最终值一高一低,还会导致图表里全是锯齿。 我首先想的是把最终值计算那修改一下,给 size 做个去重。后来觉得既然做都做了,那就把图表也用上去重的数据。 progressLog.size 虽然能算出来 k 和 b,但它其实是不能直接用的,因为它的 0 号元素指的是转码时间,而不是媒体时间,所以在 dashboardTimer 里还把 sizeK 除以了一下 timeK 才拿到 bitrate,不是很科学。于是,我把去重的函数返回直接做成了三个数据:[转码时间, 媒体时间, 尺寸]

另外一点小改动就是把坐标刻度的宽度做成了代码可配置的,显示文字也加上了 TaskItem 那边的 filter(直接复制代码)。 最后改了一下颜色。改完之后才发现,我做的这个颜色,跟 Windows 任务管理器的性能页可真像🌚。

2024-07-11

码率分布和速度分布做起来就并不复杂。只需要把 size 和 time 分别求一下两次之间的差值和均值之类的操作就行了。canvas 那边弄一下 x 坐标的起点啥的也简单。

剩下的时间主要是在欣赏我的劳动成果🌚。我把我过往的视频作品拉进了软件里看它们的码率分布。我还新学会了一个参数:-re,它可以使得转码速度与播放速度一致,就像是播放实时流一样的输出文件。这样我就能用 copy 编码模式,一边看视频一边看码率变化了。 实测把输出格式设置为 MOV 或者 MP4 比较好。MKV 的进度反馈粒度太大了,不够准确。另外也不能什么格式都不指定。 然后发现码率这种东西还是有点神奇,特别是 Adobe Premiere 输出的那些。我多年以前的作品是编码为 H.264 的,那个是设定为 VBR,可以指定平均码率和最高码率。但它的码率分布曲线完全就不像是蹭着某个码率走的,而是像是二次编码,在编码的时候就已经知道了这里要分配多少码率,能使最终输出大致达到设定的码率。这很神奇。

这些内容足够完成这个 commit 了。样式优化还有网络传输的进度啥的等下一个 commit 再说。-re 参数也有点用,可以加进来。 另外也有用户提出递归添加文件夹功能,这个也可以有,不影响后面要做的多输入功能(反正都要改好多)。