解决 bunx 报错 invalid CA cert

用以下脚本导出一份系统的 CA 证书:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$store = Get-ChildItem Cert:\LocalMachine\Root
$out = "cafile.crt"

Remove-Item $out -ErrorAction SilentlyContinue

foreach ($cert in $store) {
$pem = @"
-----BEGIN CERTIFICATE-----
$([System.Convert]::ToBase64String($cert.RawData, 'InsertLineBreaks'))
-----END CERTIFICATE-----

"@
Add-Content -Path $out -Value $pem
}

然后在 $HOME\.bunfig.toml 配置:

1
2
[install]
cafile = "C:/cafile.crt"

为 NanoPi R1 修复 istoreos 的 WLAN 支持

笔者最近在为捡垃圾收来的一台 NanoPi R1 编译 istoreos,发现 AP6212 一直无法驱动起来,研究了四天,遂记录下来。

问题原因:openwrt 上游仓库中 sunxidts 设备树文件存在问题。

修复方案:参考友善电子官方 friendlywrt 系统中的设备树文件,将 wifi_pwrseq 修改为:

1
2
3
4
5
6
7
8
wifi_pwrseq {
compatible = "mmc-pwrseq-simple";
pinctrl-names = "default";
pinctrl-0 = <0x99>;
post-power-on-delay-ms = <0xc8>;
reset-gpios = <0x37 0x00 0x07 0x01>;
phandle = <0x0c>;
};

并在 pinctrl@1f02c00 下添加:

1
2
3
4
5
wifi_en_pin {
pins = "PL7";
function = "gpio_out";
phandle = <0x99>;
};

让 Windows 使用苹果提供的 active probe 服务避免网络小地球

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet]
"ActiveDnsProbeContent"="223.5.5.5"
"ActiveDnsProbeContentV6"="2400:3200::1"
"ActiveDnsProbeHost"="dns.alidns.com"
"ActiveDnsProbeHostV6"="dns.alidns.com"
"CaptivePortalTimerBackOffIncrementsInSeconds"=dword:00000005
"CaptivePortalTimerMaxInSeconds"=dword:0000001e
"EnableActiveProbing"=dword:00000001
"PassivePollPeriod"=dword:0000000f
"StaleThreshold"=dword:0000001e
"WebTimeout"=dword:00000023
"ActiveWebProbeContent"="<HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>"
"ActiveWebProbeContentV6"="<HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>"
"ActiveWebProbeHost"="captive.apple.com"
"ActiveWebProbeHostV6"="captive.apple.com"
"ActiveWebProbePath"="ncsi.txt"
"ActiveWebProbePathV6"="ncsi.txt"
"CaptivePortalTimer"=dword:00000001

ffmpeg 标准化视频音量

学校这两天搞活动,收集来的素材音量全部不统一……我又懒得重新剪辑,遂想到使用 ffmpegloudnorm 滤镜对视频进行响度均衡。

于是写(用 deepseek 搓)了个小脚本来处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import ffmpeg
import os
import re
import json
from pathlib import Path

def analyze_loudness(input_path):
"""使用ffmpeg-python分析音频响度参数"""
try:
# 执行分析命令并捕获输出
stdout, stderr = (
ffmpeg
.input(input_path)
.audio.filter('loudnorm', print_format='json')
.output('pipe:', format='null')
.global_args('-loglevel', 'info')
.run(capture_stdout=True, capture_stderr=True)
)
output = stderr.decode() # 成功时从返回的stderr获取输出
except ffmpeg.Error as e:
# 失败时从异常对象获取输出
output = e.stderr.decode()
json_match = re.search(r'\{.*\}', output, re.DOTALL)
if not json_match:
raise ValueError("未找到响度分析数据")

try:
return json.loads(json_match.group())
except json.JSONDecodeError:
raise ValueError("响度数据解析失败")

def normalize_video(input_path, output_path, target_lufs=-19):
"""执行标准化处理"""
# 分析音频特征
loudness_data = analyze_loudness(input_path)

# 构建处理命令
input_stream = ffmpeg.input(input_path)
audio_stream = input_stream.audio.filter(
'loudnorm',
linear='true',
I=target_lufs,
measured_I=loudness_data['input_i'],
measured_LRA=loudness_data['input_lra'],
measured_tp=loudness_data['input_tp'],
measured_thresh=loudness_data['input_thresh'],
offset=loudness_data['target_offset'],
print_format='summary'
)

# 保留所有视频流和其他数据流
output = ffmpeg.output(
input_stream.video,
audio_stream,
output_path,
vcodec='copy', # 复制视频流
acodec='aac', # 重新编码音频
audio_bitrate='192k',
ar='48000',
y='-y' # 允许覆盖输出文件
)

try:
output.run()
except ffmpeg.Error as e:
raise RuntimeError(f"处理失败: {e.stderr.decode()}")

def batch_normalize():
"""批量处理当前目录下的视频文件"""
video_exts = ['.mp4', '.mkv', '.mov', '.avi', '.flv']
current_dir = Path.cwd()

for file in current_dir.iterdir():
if file.suffix.lower() in video_exts and '_normalized' not in file.stem:
output_name = f"{file.stem}_normalized{file.suffix}"
output_path = file.with_name(output_name)

print(f"正在处理: {file.name}")
try:
normalize_video(str(file), str(output_path))
print(f"完成: {output_name}")
except Exception:
print(f"处理 {file.name} 失败: {str(Exception)}")
if output_path.exists():
output_path.unlink() # 删除可能生成的不完整文件

if __name__ == "__main__":
# 检查ffmpeg是否可用
try:
ffmpeg.probe('')
except ffmpeg.Error:
print("错误: 请先安装ffmpeg并添加到系统路径")
exit(1)

batch_normalize()

【Arch Linux】广东省省直单位WPS 安装包

今天放假闲来无事,用官网上的 .deb 包封了一个 arch 系的安装包。

版本:wps-office-pro-11.8.2.12065

安装报错是正常的,懒得再改脚本了。

链接:https://alist.omn.cc/%E5%B9%BF%E4%B8%9C%E7%9C%81%E7%BA%A7%E5%85%9A%E6%94%BF%E6%9C%BA%E5%85%B3%E6%94%BF%E5%8A%A1%E5%A4%96%E7%BD%91%E4%B8%93%E7%94%A8-wps-office-pro-11.8.2.12065-1-x86_64.pkg.tar.zst

使用 Nexus 3 搭建 Docker 镜像源

由于众所周知的原因,Docker Hub 在中国大陆被禁止了。
本文搭建了一个公共 Docker Hub 镜像源。如果实在懒得自建的话,文末我会附上自己的镜像源供公开使用,但请不要滥用

安装 Nexus

找一个目录,把 docker-compose.yml 丢进去。
下文使用 /opt/nexus 代替本目录。

1
2
3
mkdir -p /opt/nexus
mkdir -p /opt/nexus/nexus-data
vim /opt/nexus/docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
version: "3.8"
services:
nexus:
image: sonatype/nexus3
container_name: nexus
restart: always
environment:
INSTALL4J_ADD_VM_PARAMS: -Xms4G -Xmx8G -XX:MaxDirectMemorySize=4G #视实际情况调整
ports:
- 8081:8081
- 8082:8082
volumes:
- /opt/nexus/nexus-data:/nexus-data
1
docker compose up -d

等待几分钟后,获取初始密码。

1
docker exec nexus cat /nexus-data/admin.password

为 Docker Hub 添加 Docker Proxy Repository

登录进去后,默认要求你修改密码。
完成后来到 Administration-Repository-Repositories 页面。

创建代理仓库

新建一个仓库,选择 docker (proxy)
在 Name 这里填入当前仓库的命名,例如 docker-proxy-dockerhub
Proxy - Remote storage 这里,配置 Docker Hub 代理时填入 https://registry-1.docker.io,配置 K8s 仓库时填入 https://k8s.gcr.io,配置 quay.io 仓库时填入 https://quay.io
Proxy - Docker Index 这里,配置 Docker Hub 代理时选择 Use Docker Hub,配置其他仓库代理时选择 Use proxy registry(specified above)
去掉 Proxy - Auto blocking enabled 这里的对勾。
去掉 Negative Cache - Not found cache enabled 这里的对勾。
可选只创建第一个重复操作,创建完成 3 个仓库:docker-proxy-dockerhubdocker-proxy-k8sgcrdocker-proxy-quayio

创建聚合仓库

[Repository] - [Repositories] 中创建新仓库,类型为 docker (group)
Name 这里填入当前仓库的命名,例如 docker-proxy-group
Repository Connectors 这里,勾选 HTTP 并填入一个未被占用的端口,必须与创建容器时映射的端口号一致。这里我使用 8002。
Group - Member repositorles 这里,将需要整合的仓库添加到右边,并按先后顺序排好。

配置 Docker 认证

Security - Realms 选项中,将 Docker Bearer Token Realm 添加到右边,保存即可。

配置 HTTPS

略,有些时候饭不能喂到嘴里,否则就没味道了。

我的公开镜像

镜像源:https://hub.20220303.xyz/禁止滥用!禁止滥用!禁止滥用!

编译最新的 Linux-next 内核

!> Linux-next 不是适用于任何人的!
@> The linux-next tree is the holding area for patches aimed at the next kernel merge window. If you’re doing bleeding edge kernel development, you may want to work from that tree rather than Linus Torvalds’ mainline tree.

获取源代码

git仓库找到最新的commit(被标注为绿色 HEAD 的)。确认你这是你需要的,clone下来。
注意:Linux Kernel 很大,保证空间足够!
clone

修改配置

先复制你自己机器的配置文件

1
cp /boot/config-"$(uname -r)" .config

接下来,更新配置文件到最新的

1
make olddefconfig

如果你在用DebianUbuntu或他们的衍生版本,务必关闭默认签名证书

1
2
./scripts/config --file .config --set-str SYSTEM_TRUSTED_KEYS ''
./scripts/config --file .config --set-str SYSTEM_REVOCATION_KEYS ''

自定义配置

  • defconfig: 默认配置。
  • allmodconfig: 根据当前系统状态,尽可能地把项目构建为可加载模块(而非内建)。
  • tinyconfig: 极简的 Linux 内核。
    一般来说,建议使用defconfig
1
make defconfig

接下来可以在默认配置的基础上自定义配置了。

1
make menuconfig

在此界面,你可以根据各选项的类型来进行切换操作。
有两类可切换选项:

  1. 布尔状态选项:这类选项只能关闭([ ])或作为内建组件开启([*])。
  2. 三态选项:这类选项可以关闭(< >)、内建(<*>),或作为可加载模块()进行构建。
    保存好之后,就可以编译了

编译

1
make -j$(nproc) 2>&1 | tee log

有报错就修吧

哪吒面板 MDUI 主题添加分组

效果图:加了之后
改了之后的 html(没格式化是因为我的格式化插件有 bug 格式化会导致哪吒插入的关键字段被优化掉):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
{{define "theme-mdui/home"}}
<!doctype html>
<html lang="{{.Conf.Language}}">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>{{.Title}}</title>
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />

<!-- MDUI CSS -->
<link rel="stylesheet" href="https://cdn.staticfile.org/mdui/1.0.2/css/mdui.min.css"/>
<link rel="stylesheet" href="/static/theme-mdui/mdui.css" type="text/css">
<style>
.mdui-table td, .mdui-table th{padding: 6px;}
.progress{width: 10%;min-width: 75px;}
.progress-text{font-size: 16px;font-weight: 800;position: relative;top: 4px;left: 6px;}
.offline st,.offline at,.offline gt,.offline .progress-text{color: grey;}
a{text-decoration:none;color:#333;}.mdui-theme-layout-dark a{color:#fff;}
</style>
{{if ts .CustomCode}}
{{.CustomCode|safe}}
{{end}}
</head>

<body>
{{template "theme-mdui/menu" .}}

<div id="app">
<div id="container" class="mdui-container">
<button @click="toggleView" class="mdui-fab mdui-fab-wrapper mdui-fab-fixed mdui-ripple mdui-color-pink-accent">
<i v-if="showCard" class="mdui-icon material-icons">list</i>
<i v-else class="mdui-icon material-icons">apps</i>
</button>
<div v-if="showCard" class="mdui-row-xs-1 mdui-row-sm-2 mdui-row-md-3 mdui-row-lg-4">
<div class="mdui-panel" mdui-panel="{accordion: false}">
<div class="mdui-panel-item mdui-panel-item-open " v-for="group in groups">
<div class="mdui-panel-item-header">@#(group.Tag!==''?group.Tag:'{{tr "Default"}}')#@</div>
<div class="mdui-panel-item-body">
<div id="servers">
<div class="mdui-col" v-for='server in group.data' :id="server.ID">
<div :class="'mdui-card mt' + (server.live?'':' offline')">
<div class="mdui-card-header">
<img class="mdui-card-header-avatar" :src="'https://cdn.staticfile.org/flag-icon-css/4.1.5/flags/1x1/' + (server.Host.CountryCode?server.Host.CountryCode:'cn') + '.svg'"/>
<div class="mdui-card-header-title">@#server.Name#@</div>
<div class="mdui-card-header-subtitle">@#server.Host.CountryCode.toUpperCase()#@ | @#server.Host.Platform#@ @#server.Host.PlatformVersion#@</div>
</div>
<div v-if="server.live" class="mdui-card-menu">
<i :id="'info-' + server.ID" class="mdui-icon material-icons">info_outline</i>
</div>
<div v-else class="mdui-card-menu mdui-typo-title mdui-text-color-grey">Offline</div>
<div class="mdui-card-content">
<ul class="mdui-list">
<li class="mdui-list-item">
<i class="mdui-list-item-icon mdui-icon material-icons">memory</i>
<div class="mdui-list-item-content">
<st class="mdui-list-item-title mdui-list-item-one-line">CPU <span>@#server.live?parseInt(server.State.CPU):'NaN'#@%</span></st>
<div class="mdui-list-item-text" style="opacity:1;">
<div class="mdui-progress">
<div class="mdui-progress-determinate mdui-color-indigo-400" :style="'width: ' + (server.live?server.State.CPU:'0') + '%;'"></div>
</div>
</div>
</div>
</li>
<li class="mdui-list-item" :id="'mem-' + server.ID">
<i class="mdui-list-item-icon mdui-icon material-icons">straighten</i>
<div class="mdui-list-item-content">
<at class="mdui-list-item-title mdui-list-item-one-line">MEM <span>@#server.live?parseInt(server.State?server.State.MemUsed/server.Host.MemTotal*100:0):'NaN'#@%</span></at>
<div class="mdui-progress">
<div class="mdui-progress-determinate mdui-color-pink-400" :style="'width: ' + (server.live?parseInt(server.State?server.State.MemUsed/server.Host.MemTotal*100:0):'0') + '%;'"></div>
</div>
</div>
</li>
<li class="mdui-list-item">
<i class="mdui-list-item-icon mdui-icon material-icons">swap_vert</i>
<div class="mdui-list-item-content">
<div class="mdui-list-item-title">{{tr "UpNetTransfer"}}</div>
<div class="mdui-list-item-text mdui-list-item-one-line" style="opacity:1;">
<at><span>@#formatNetByteSize(server.State.NetOutSpeed)#@</span></at>
</div>
</div>
<div class="mdui-list-item-content">
<div class="mdui-list-item-title">{{tr "DownNetTransfer"}}</div>
<div class="mdui-list-item-text mdui-list-item-one-line" style="opacity:1;">
<st><span>@#formatNetByteSize(server.State.NetInSpeed)#@</span></st>
</div>
</div>
</li>
<li class="mdui-list-item">
<i class="mdui-list-item-icon mdui-icon material-icons">swap_horiz</i>
<div class="mdui-list-item-content">
<div class="mdui-list-item-title">{{tr "TotalUpNetTransfer"}}</div>
<div class="mdui-list-item-text mdui-list-item-one-line" style="opacity:1;">
<at><span>@#formatByteSize(server.State.NetOutTransfer)#@</span></at>
</div>
</div>
<div class="mdui-list-item-content">
<div class="mdui-list-item-title">{{tr "TotalDownNetTransfer"}}</div>
<div class="mdui-list-item-text mdui-list-item-one-line" style="opacity:1;">
<st><span>@#formatByteSize(server.State.NetInTransfer)#@</span></st>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

<div v-else class="mdui-table-fluid mdui-m-t-1">
<table class="mdui-table mdui-table-hoverable">
<thead>
<tr>
<th class="mdui-text-center">ID</th>
<th class="mdui-text-center">{{tr "Name"}}</th>
<th class="mdui-text-center">{{tr "UpNetTransfer"}}</th>
<th class="mdui-text-center">{{tr "DownNetTransfer"}}</th>
<th class="mdui-text-center">{{tr "TotalUpNetTransfer"}}</th>
<th class="mdui-text-center">{{tr "TotalDownNetTransfer"}}</th>
<th class="mdui-text-center">CPU</th>
<th class="mdui-text-center">RAM</th>
<th class="mdui-text-center">{{tr "Uptime"}}</th>
</tr>
</thead>
<tbody>
<tr :class="(server.live?'':'offline')" v-for="server in servers">
<td class="mdui-text-center">@#server.ID#@</td>
<td class="mdui-text-center">@#server.Name#@</td>
<td class="mdui-text-center"><at>@#formatNetByteSize(server.State.NetOutSpeed)#@</at></td>
<td class="mdui-text-center"><st>@#formatNetByteSize(server.State.NetInSpeed)#@</st></td>
<td class="mdui-text-center"><at>@#formatByteSize(server.State.NetOutTransfer)#@</at></td>
<td class="mdui-text-center"><st>@#formatByteSize(server.State.NetInTransfer)#@</st></td>
<td class="progress">
<div class="mdui-progress" style="height: 30px; background-color: #edbbd2;">
<div class="mdui-progress-determinate mdui-color-pink-a400" :style="'width: ' + (server.live?server.State.CPU:'0') + '%;'">
<span class="mdui-text-truncate progress-text">@#server.live?parseInt(server.State.CPU):'NaN'#@%</span>
</div>
</div>
</td>
<td class="progress">
<div class="mdui-progress" style="height: 30px;">
<div class="mdui-progress-determinate mdui-color-indigo-400" :style="'width: ' + parseInt(server.State?server.State.MemUsed/server.Host.MemTotal*100:0) + '%;'">
<span class="mdui-text-truncate progress-text">@#parseInt(server.State?server.State.MemUsed/server.Host.MemTotal*100:0)#@%</span>
</div>
</div>
</td>
<td class="mdui-text-center">@#secondToDate(server.State.Uptime)#@</td>
</tr>
</tbody>
</table>
</div>

</div>
</div>

{{template "theme-mdui/footer" .}}

<script src="/static/theme-mdui/mdui.js"></script>
<script src="https://cdn.staticfile.org/mdui/1.0.2/js/mdui.min.js"></script>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>

<script>
var container = document.querySelector("#container");
container.style.minHeight = window.innerHeight-document.body.clientHeight+container.clientHeight+'px';
mdui.mutation();
const initData = JSON.parse('{{.Servers}}').servers;
var statusCards = new Vue({
el: '#app',
delimiters: ['@#', '#@'],
data: {
servers: initData,
cache: [],
groups: [],
showCard: true
},
created() {
this.group()
},
methods: {
toggleView() {
this.showCard = !this.showCard
},
toFixed2(f) {
return f.toFixed(2)
},
group() {
this.groups = groupingData(this.servers, "Tag")
},
secondToDate(s) {
var d = Math.floor(s / 3600 / 24);
if (d > 0) {
return d + " {{tr "Day"}}"
}
var h = Math.floor(s / 3600 % 24);
var m = Math.floor(s / 60 % 60);
var s = Math.floor(s % 60);
return h + ":" + ("0" + m).slice(-2) + ":" + ("0" + s).slice(-2);
},
readableBytes(bytes) {
if (!bytes) {
return '0B'
}
var i = Math.floor(Math.log(bytes) / Math.log(1024)),
sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + sizes[i];
},
readableNetBytes(bytes) {
if (!bytes) {
return '0B'
}
var Kbps=125, Mbps=Kbps*1000, Gbps=Mbps*1000, Tbps=Gbps*1000;
if (bytes < Kbps) return (bytes * 8).toFixed(2) + 'bps';
if (bytes < Mbps) return (bytes / Kbps).toFixed(2) + 'Kbps';
if (bytes < Gbps) return (bytes / Mbps).toFixed(2) + 'Mbps';
if (bytes < Tbps) return (bytes / Gbps).toFixed(2) + 'Gbps';
else return (bytes / Tbps).toFixed(2) + 'Tbps';
},
formatTimestamp(t) {
return new Date(t * 1000).toLocaleString()
},
formatByteSize(bs) {
const x = this.readableBytes(bs)
return x != "NaN undefined" ? x : 'NaN'
},
formatNetByteSize(bs) {
const x = this.readableNetBytes(bs)
return x != "NaN undefined" ? x : 'NaN'
},
formatTooltip(server) {
var disk = this.formatByteSize(server.State.DiskUsed) + '/' + this.formatByteSize(server.Host.DiskTotal);
var upTime = this.secondToDate(server.State.Uptime);
var tooltip = `{content: 'System: ${server.Host.Platform}-${server.Host.PlatformVersion}[${server.Host.Arch}]<br>CPU: ${server.Host.CPU}<br>Disk: ${disk}<br>Online: ${upTime}<br>Version: ${server.Host.Version}'}`;
return tooltip
}
}
})

function groupingData(data, field) {
if (!data) {
return
}
let map = {};
let dest = [];
data.forEach(item => {
if (!map[item[field]]) {
dest.push({
[field]: item[field],
data: [item]
});
map[item[field]] = item;
} else {
dest.forEach(dItem => {
if (dItem[field] == item[field]) {
dItem.data.push(item);
}
});
}
})
return dest;
}
const wsProtocol = window.location.protocol == "https:" ? "wss" : "ws"
let canShowError = true;
function connect() {
const ws = new WebSocket(wsProtocol + '://' + window.location.host + '/ws');
ws.onopen = function (evt) {
canShowError = true;
mdui.snackbar({
message: '{{tr "RealtimeChannelEstablished"}}',
timeout: 2000,
position: 'top',
onClosed: function () {
mdui.mutation();
}
});
}
var infoTooltip = {}, memTooltip = {};
ws.onmessage = function (evt) {
const data = JSON.parse(evt.data)
statusCards.servers = data.servers
for (let i = 0; i < statusCards.servers.length; i++) {
const ns = statusCards.servers[i];
if (!ns.Host) ns.live = false
else {
const lastActive = new Date(ns.LastActive).getTime()
if (data.now - lastActive > 10 * 1000) {
ns.live = false
} else {
ns.live = true
if (statusCards.showCard) {
if (infoTooltip[ns.ID]) {
var disk = statusCards.formatByteSize(ns.State.DiskUsed) + '/' + statusCards.formatByteSize(ns.Host.DiskTotal);
var upTime = statusCards.secondToDate(ns.State.Uptime);
var content =
`System: ${ns.Host.Platform}-${ns.Host.PlatformVersion}[${ns.Host.Arch}]
CPU: ${ns.Host.CPU}
Disk: ${disk}
Online: ${upTime}
Version: ${ns.Host.Version}`;
infoTooltip[ns.ID].$element[0].innerText = content;
}
else {
if (document.getElementById(`info-${ns.ID}`)) infoTooltip[ns.ID] = new mdui.Tooltip(`#info-${ns.ID}`, {});
}

if (memTooltip[ns.ID]) {
var content = `${statusCards.formatByteSize(ns.State.MemUsed)}/${statusCards.formatByteSize(ns.Host.MemTotal)}`;
memTooltip[ns.ID].$element[0].innerText = content;
}
else {
if (document.getElementById(`mem-${ns.ID}`)) memTooltip[ns.ID] = new mdui.Tooltip(`#mem-${ns.ID}`, {});
}
} else { mdui.$('div').remove('.mdui-tooltip'); infoTooltip = {}; memTooltip = {}; }
}
}
}
statusCards.groups = groupingData(statusCards.servers, "Tag")
mdui.mutation();
}
ws.onclose = function () {
if (canShowError) {
canShowError = false;
mdui.snackbar({
message: '{{tr "RealtimeChannelDisconnect"}}',
timeout: 2000,
position: 'top',
});
}
setTimeout(function () {
connect()
}, 3000);
}
ws.onerror = function () {
ws.close()
}
}
connect();
</script>
</body>
</html>
{{end}}

[C++]天翼云电脑本机挂机隐藏窗口

废话不多说,直接贴代码。有想要解析的可以继续往下翻。
完整解决方案:KawaiiSh1zuku/CtYunAgentWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <iostream>
#include <windows.h>
#include <string.h>
#include <tchar.h>
#pragma comment(lib, "user32.lib")

int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);

BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam) {
char className[256];
GetClassNameA(hwnd, className, sizeof(className));

std::cout << "Child Window: " << className << std::endl;

RECT clientRect;
GetClientRect(hwnd, &clientRect);

RECT windowRect;
GetWindowRect(hwnd, &windowRect);

std::cout << "Client Area Resolution: " << clientRect.right - clientRect.left
<< " x " << clientRect.bottom - clientRect.top << std::endl;

std::cout << "Window Resolution: " << windowRect.right - windowRect.left
<< " x " << windowRect.bottom - windowRect.top << std::endl;

if (windowRect.right - windowRect.left < screenWidth && windowRect.bottom - windowRect.top < screenHeight) {
std::cout << "You are just the window I'm looking for!" << std::endl;
HWND hd = reinterpret_cast<HWND>(lParam);
if (IsWindowVisible(hd)) {
ShowWindow(hd, SW_HIDE);
std::cout << "Hid" << std::endl;
}
else {
ShowWindow(hd, SW_SHOW);
std::cout << "Shown" << std::endl;
}
return false;
}
return true;
}

int main(int argc, _TCHAR* argv[])
{
HWND hd = GetDesktopWindow();

hd = GetWindow(hd, GW_CHILD);
char s[200] = { 0 };

while (hd != NULL)
{
memset(s, 0, 200);
GetWindowText(hd, s, 200);
if (strstr(s,"CtyunClouddeskUniversal"))
{
EnumChildWindows(hd, EnumChildProc, reinterpret_cast<LPARAM>(hd));
}
hd = GetNextWindow(hd, GW_HWNDNEXT);
}

return 0;
}

解析:用遍历取得客户端的窗口,然后判断是不是我们想要隐藏/显示的窗口(有一个覆盖了全屏的窗口,显示后会在顶层阻断鼠标操作)
至于为什么用遍历?不知道为什么用FindWindow找不到天翼云的窗口。

Laravel多项目horizon冲突问题

起因

用了一个用laravel写的shit项目,没想到项目默认没有修改项目名。导致两个实例绑定到了一个名字上之后,redis内的horizon记录冲突了。
解决办法:.env文件内修改其中一个的APP_NAME

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×