Skip to content

About

基于 Bronya0/OnlyWallpaper 深度改造的 macOS 动态壁纸工具:原生 NSWindow + WKWebView(Metal) 将 MP4/MOV 铺为桌面背景,新增 Fyne 图形界面、配置持久化与视频历史、五种淡入效果、双引擎首帧提取及省电开关。 | A reworked macOS video-wallpaper fork using a native NSWindow + WKWebView (Metal), adding a Fyne GUI, persistent config + history, five fade-in modes, dual-engine first-frame extraction and power-saving toggles.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

English | 简体中文

OnlyWallpaper

OnlyWallpaper — macOS 视频动态壁纸工具

把 MP4/MOV 视频变成一块会呼吸的桌面 —— 原生窗口沉到桌面层级,WKWebView + Metal 硬解,功耗低到几乎听不见。

基于上游 fork 深度改造:补齐图形界面、配置持久化、五种淡入效果与双引擎首帧提取。

Platform Go GUI Render Fork


它解决什么问题

macOS 原生不提供「用视频当壁纸」的官方入口,第三方方案要么常驻高功耗、要么无法在锁屏/睡眠时优雅让位,还常常在切换桌面空间(Mission Control)时窗口错位。

OnlyWallpaper 换了一条路:用一个无边框、忽略鼠标事件、沉到桌面图标层之下的原生 NSWindow 承载一块 WKWebView,由 WebKit 用 Metal 直接解码播放视频。窗口被加入「所有空间 + 固定 + 忽略循环切换」,因此切换桌面、全屏应用、Mission Control 时都不会被顶掉或抢焦点;系统睡眠、屏幕锁定时通过通知中心自动暂停,5 分钟无人操作也会自动暂停(可关)。

它不联网、不上传任何数据,只读取你选择的本地 .mp4 / .mov 文件;配置全部落在本机 ~/Library/Application Support/OnlyWallpaper/。


✨ 功能

  • 🎬 视频壁纸 —— 将 .mp4 / .mov 设为桌面背景,等比例 object-fit: cover 铺满并循环播放。
  • 🚀 GPU 硬件加速 —— 由 WKWebView 调用 Metal 解码,静止窗口 + 静音播放,实测功耗约 10–50mW(上游 README 口径)。
  • 🖥️ 图形界面(新增) —— Fyne 深色主题双列布局,osascript 调起 macOS 原生文件选择器,2 秒轮询刷新运行状态。
  • 💾 配置记忆与历史(新增) —— 自动保存上次视频与设置,保留最近 10 条视频历史,一键下拉复用。
  • 🎨 五种淡入效果(新增) —— 黑色 / 模糊渐变 / 图片 / 视频第一帧 / 视频第一帧模糊渐变,切换到该空间时丝滑淡入。
  • 🎞️ 双引擎首帧提取(新增) —— ffmpeg 与 AVFoundation 双通道,可指定或自动回退。
  • 🔧 开机自启(增强) —— 生成 LaunchAgent plist,且自动补全当前全部设置(淡入、省电、首帧提取方法)。
  • 📦 App 打包 —— build_app.sh 一键产出 OnlyWallpaper.app。
  • 🔒 单实例与自愈 —— 文件锁保证唯一实例;检测到残留旧进程会 SIGTERM → 必要时 Kill 接管。

🚀 快速开始

方式一:面向 AI Agent(一键安装,推荐)

把下面这段提示词直接发给你的本地 AI Agent(Claude Code / Codex / OpenCode …):

请帮我安装并运行 OnlyWallpaper(macOS 视频动态壁纸工具,Go + Fyne + WKWebView)。
仓库:https://github.com/RayMorTwinkle/OnlyWallpaperv2

环境要求:macOS 10.14+、Go 1.25+、Xcode Command Line Tools(CGO 需要)。
可选:ffmpeg(用于首帧提取,未安装会自动回退到 AVFoundation)。

步骤:
1. 克隆:git clone https://github.com/RayMorTwinkle/OnlyWallpaperv2.git && cd OnlyWallpaperv2
2. 拉依赖:go mod tidy
3. 编译:CGO_ENABLED=1 go build -o wallpaper
4. 启动图形界面:./wallpaper --gui
   (无参数直接运行 ./wallpaper 也会进入 GUI)
5. 在 GUI 里「浏览...」选择一段 .mp4/.mov,点击「启动壁纸」。
6. 打包成应用(可选):./build_app.sh  → build/OnlyWallpaper.app
7. 向我确认壁纸已生效,并简述开销(Metal 解码、锁屏/空闲自动暂停)。

方式二:面向人类用户

# 1. 安装依赖(CGO 编译需要)
xcode-select --install
brew install ffmpeg          # 可选,用于提取视频第一帧

# 2. 克隆与编译
git clone https://github.com/RayMorTwinkle/OnlyWallpaperv2.git
cd OnlyWallpaperv2
go mod tidy
CGO_ENABLED=1 go build -o wallpaper

# 3. 图形界面
./wallpaper --gui
# 或直接命令行启动
./wallpaper --video ~/Movies/loop.mp4

环境要求:macOS 10.14+、Go 1.25+、Xcode Command Line Tools。ffmpeg 可选。 本项目使用 CGO 桥接 Objective-C,必须 CGO_ENABLED=1。


🖥️ 使用

CLI 命令

命令 说明
./wallpaper --video <path> 后台启动壁纸(自动 --daemon 转后台)
./wallpaper --cmd stop 停止当前壁纸
./wallpaper --cmd status 查询是否正在运行
./wallpaper --cmd enable-autostart --video <path> 写入 LaunchAgent 并登录自启
./wallpaper --cmd disable-autostart 卸载自启
./wallpaper --gui 打开图形界面

便捷写法:./wallpaper stop / status / start 会被 normalizeArgs() 自动补成 --cmd 形式。

CLI 参数

参数 取值 默认 说明
--video 路径 — .mp4 / .mov 文件路径,支持 ~/ 展开
--cmd start stop status enable-autostart disable-autostart start 子命令
--daemon — false 后台运行(内部使用)
--gui — false 启动 Fyne 图形界面
--debug — false 前台运行并打印详细日志
--no-idle-pause — false 禁用「5 分钟空闲自动暂停」
--opaque-window true / false true 不透明窗口更省电;false 支持透明/模糊
--fade-in black transparent image firstframe firstframe-blur black 淡入模式
--fade-in-image 路径 — --fade-in image 时使用的图片
--extract-method auto ffmpeg avfoundation auto 首帧提取引擎

典型工作流

# 用视频第一帧做淡入,自动挑选提取引擎(推荐)
./wallpaper --video ~/Movies/loop.mp4 --fade-in firstframe-blur

# 强制用 AVFoundation 提取首帧(无需安装 ffmpeg)
./wallpaper --video ~/Movies/loop.mp4 --fade-in firstframe --extract-method avfoundation

# 锁屏/空闲时不暂停(演示场景)
./wallpaper --video ~/Movies/loop.mp4 --no-idle-pause

# 开机自启并继承当前全部设置
./wallpaper --cmd enable-autostart --video ~/Movies/loop.mp4

🏗️ 架构

系统总览

GUI 与 CLI 两条入口最终都汇聚到 main.go,再由 CGO 桥接到 Objective-C 渲染层;配置与视频历史由 config.go 独立持久化。

flowchart TB
  subgraph ENTRY["入口层"]
    GUI["GUI 模式<br/>gui.go · NewWallpaperGUI()"]
    CLI["CLI 模式<br/>main.go · flag.Parse()"]
    APP["OnlyWallpaper.app<br/>build_app.sh"]
  end

  subgraph CORE["主程序 main.go"]
    NORM["normalizeArgs()"]
    LOCK["单实例锁 flock<br/>/tmp/desktop-wallpaper.lock"]
    START["startWallpaper()"]
    BG["startBackground()"]
    CMDS["stop / status / enable-autostart"]
  end

  subgraph CFG["配置 config.go"]
    JSON["config.json<br/>+ firstframe.jpg"]
  end

  subgraph SYS["渲染层(CGO)bridge.m"]
    BRIDGE["InitWallpaper()"]
    WIN["NSWindow<br/>桌面层级"]
    WV["WKWebView<br/>Metal 解码"]
    HTML["assets/player.html"]
    FF["ffmpeg"]
    AV["AVFoundation<br/>ExtractVideoFirstFrame()"]
  end

  APP --> GUI
  GUI --> START
  CLI --> NORM --> LOCK --> START
  CLI --> CMDS
  START --> BG
  START --> CFG
  CFG --> JSON
  START --> FF
  START --> AV
  START --> BRIDGE --> WIN --> WV --> HTML
Loading

配置与参数数据流

GUI 与命令行共享同一份 Config;GUI 的改动既写回 JSON,也拼成 CLI 参数透传给后台进程,开机自启时再写进 plist。

flowchart LR
  GUI["gui.go<br/>loadSettings() / Set*()"] --> CFG["Config 结构体"]
  CFG --> P1["~/Library/Application Support/OnlyWallpaper/config.json"]
  OLD["旧路径 ~/.onlywallpaper/config.json"] -. "LoadConfig() 迁移并删除" .-> P1
  CFG --> ARGS["CLI 参数<br/>--fade-in / --extract-method ..."]
  ARGS --> PLIST["~/Library/LaunchAgents/com.user.onlywallpaper.plist"]
  GUI -->|onStart 拼参| ARGS
Loading

淡入模式决策

fade_in_mode 在 startWallpaper() 中被翻译成 fadeInFlag(0–4);需要首帧时先提取,失败则按模式回退。

flowchart TD
  M["fade_in_mode"] --> Q{"取值?"}
  Q -->|black| F0["fadeInFlag = 0<br/>黑色淡入"]
  Q -->|transparent| F1["fadeInFlag = 1<br/>模糊渐变"]
  Q -->|image| F2["fadeInFlag = 2<br/>图片淡入"]
  Q -->|firstframe| F3["fadeInFlag = 3"]
  Q -->|firstframe-blur| F4["fadeInFlag = 4"]
  F3 --> EX["extractFirstFrame()"]
  F4 --> EX
  EX --> OK{"提取成功?"}
  OK -->|是| IMG["fadeInImage = firstframe.jpg"]
  OK -->|否| FB["回退:firstframe → black<br/>firstframe-blur → transparent"]
Loading

首帧提取时序

--extract-method auto 时优先 ffmpeg,命中失败自动回退 AVFoundation。

sequenceDiagram
  autonumber
  participant U as 用户
  participant M as startWallpaper()
  participant E as extractFirstFrame()
  participant F as ffmpeg
  participant A as AVFoundation
  U->>M: --fade-in firstframe --extract-method auto
  M->>E: extractFirstFrame(video)
  E->>F: findFFmpeg() 命中 /opt/homebrew/bin
  F-->>E: 写出 firstframe.jpg(-vframes 1 -q:v 2)
  Note over E: 若 ffmpeg 不可用或失败
  E->>A: ExtractVideoFirstFrame()<br/>AVAssetImageGenerator.copyCGImageAtTime
  A-->>E: 写出 firstframe.jpg
  E-->>M: 返回图片路径
  M->>M: C.InitWallpaper(..., fadeInImage)
Loading

系统事件与省电

窗口生命周期由系统通知驱动:睡眠/唤醒、屏幕参数变化、应用退出,以及基于计时的空闲暂停。

sequenceDiagram
  autonumber
  participant OS as macOS
  participant N as NSNotificationCenter
  participant W as WKWebView
  participant V as player.html
  OS->>N: NSWorkspaceWillSleepNotification
  N->>W: evaluateJavaScript("externalPause()")
  W->>V: video.pause()
  OS->>N: NSWorkspaceDidWakeNotification
  N->>W: evaluateJavaScript("externalResume()")
  W->>V: video.play()
  OS->>N: NSApplicationDidChangeScreenParametersNotification
  N->>W: setFrame(screenRect) 适配分辨率变化
  Note over V: 空闲 300000ms 自动暂停,每 60000ms 检测一次<br/>受 {{DISABLE_IDLE_PAUSE}} 控制
Loading

配置数据模型

config.json 的字段与 Go 结构体一一对应(JSON 键为 snake_case)。

erDiagram
  CONFIG ||--o{ VIDEO_HISTORY : "最多 10 条"
  CONFIG {
    string last_video_path
    bool   autostart_enabled
    bool   no_idle_pause
    bool   opaque_window
    string fade_in_mode
    string fade_in_image
    string frame_extract_method
  }
Loading

📂 目录结构

OnlyWallpaperv2/
├── main.go              # 入口:flag 解析、单实例锁、start/stop/status、首帧提取、自启
├── gui.go               # Fyne 图形界面(深色主题、原生文件选择器、状态轮询)
├── config.go            # 配置读写、旧路径迁移、视频历史(上限 10)
├── bridge.h             # CGO 桥接头文件(InitWallpaper / ExtractVideoFirstFrame ...)
├── bridge.m             # Objective-C:NSWindow + WKWebView + AVFoundation 首帧
├── assets/
│   └── player.html      # 内嵌的视频播放模板(占位符 + 淡入 CSS/JS)
├── build_app.sh         # 打包 OnlyWallpaper.app
└── go.mod               # 模块依赖(fyne / flock)

编译产物 wallpaper 与 build/ 已在 .gitignore / 未纳入版本控制,此处不列出。


🔧 技术细节

窗口层级与穿透。 configureWindow() 把窗口层级设为 CGWindowLevelForKey(kCGDesktopIconWindowLevelKey) - 1(沉到桌面图标层之下),并组合 CanJoinAllSpaces | Stationary | IgnoresCycle | FullScreenAuxiliary 的 collectionBehavior,同时 setIgnoresMouseEvents:YES 让点击穿透、setHasShadow:NO、setAnimationBehavior:NSWindowAnimationBehaviorNone。--opaque-window=true(默认)时窗口不透明、底色纯黑,最省电;false 时透明,配合模糊/透明淡入。

HTML 模板占位符。 renderHTMLToTempFile() 在 Objective-C 侧把模板里的占位符替换为真实值后写入临时文件:{{VIDEO_PATH}}、{{DISABLE_IDLE_PAUSE}}、{{FADE_IN_TRANSPARENT}}、{{FADE_IN_IMAGE}}、{{FADE_IN_IMAGE_BLUR}}。路径经 [NSURL fileURLWithPath:] 转成 file:// URL,规避中文/空格路径问题。

淡入 CSS 动画。 assets/player.html 定义四组 keyframes:fadeInBlack、fadeInTransparent(从 blur(8px) 到清晰,1.5s)、fadeInImage、fadeInImageBlur(从 blur(12px) 到清晰,2s)。切换到该桌面空间(visibilitychange)时重新施加动画。

双引擎首帧提取。 findFFmpeg() 依次探测 /opt/homebrew/bin、/usr/local/bin、/opt/local/bin、/usr/bin,最后回退 exec.LookPath。ffmpeg 命令为 -i <video> -vframes 1 -q:v 2 -update 1 -y <out>;AVFoundation 走 AVAssetImageGenerator(appliesPreferredTrackTransform=YES,容差 kCMTimeZero),JPEG 质量 0.85,输出到配置目录的 firstframe.jpg。

配置与兼容迁移。 主路径 ~/Library/Application Support/OnlyWallpaper/config.json;若读不到则尝试旧路径 ~/.onlywallpaper/config.json,读到后立即写回新路径并删除旧目录。LoadConfig() 对缺失的 fade_in_mode / frame_extract_method 填入默认值 black / auto。视频历史用「去重前置 + 截断到 10」维护。

单实例与自愈。 使用 github.com/gofrs/flock 对 /tmp/desktop-wallpaper.lock 加锁,并写入当前 PID。若加锁失败且能读到旧 PID,会 SIGTERM 后以 100ms 步进轮询最多 5 秒等待其退出,仍未退出则 Kill 强杀并重新加锁。

后台与日志。 startBackground() 会用 syscall.SysProcAttr{Setsid: true} 让子进程脱离会话,并把 stdout/stderr 重定向到 os.TempDir()/onlywallpaper.log。启动前先校验视频存在性、扩展名与内嵌模板完整性,确保错误能回显到前台。

开机自启。 写入 ~/Library/LaunchAgents/com.user.onlywallpaper.plist(Label: com.user.onlywallpaper,RunAtLoad=true),并把 --no-idle-pause、--opaque-window=false、--fade-in、--fade-in-image、--extract-method 按当前配置补齐后 launchctl load。

打包。 build_app.sh 以 CGO_ENABLED=1 go build -ldflags="-s -w" 产出可执行文件 Contents/MacOS/wallpaper,Info.plist 中 CFBundleIdentifier=com.onlywallpaper.app、LSMinimumSystemVersion=10.14,并生成启动脚本 exec wallpaper --gui。


❓ 常见问题

Q:支持哪些视频格式? A:仅 .mp4 与 .mov。两者都会写两份 <source>(video/mp4 与 video/quicktime)。

Q:为什么功耗这么低? A:窗口静止且不透明(默认纯黑),视频静音播放,解码交给 WebKit/Metal;锁屏与 5 分钟空闲自动暂停进一步降耗。

Q:没装 ffmpeg 能用「视频第一帧」淡入吗? A:可以。--extract-method auto 会在 ffmpeg 不可用时自动回退到 AVFoundation(macOS 原生,无额外依赖)。

Q:配置文件在哪?卸载会残留吗? A:在 ~/Library/Application Support/OnlyWallpaper/(config.json + firstframe.jpg),可被 AppCleaner 等卸载器识别清理。

Q:为什么必须 CGO_ENABLED=1? A:项目用 CGO 桥接 Objective-C(bridge.m),链接 Cocoa / WebKit / CoreGraphics / AVFoundation / CoreMedia。

Q:提示「输入错误」或启动失败? A:看 $(getconf DARWIN_USER_TEMP_DIR)(即 os.TempDir())下的 onlywallpaper.log,后台进程的日志都写在那里。


⚠️ 注意事项

  • 首次运行未签名版本可能触发 Gatekeeper,需在「系统设置 → 隐私与安全性」允许,或右键「打开」。
  • 视频播放仍会占用一定 GPU,功耗随分辨率与帧率上升;上游口径约 10–50mW。
  • 单实例运行:新实例会尝试接管/终止旧实例,请勿并发启动多份。
  • 目前为单显示器适配(使用 [NSScreen mainScreen]);多显示器/每个屏幕独立壁纸尚未支持。
  • 上游与本 fork 均无自动化测试;改动请以真机运行为准。

📄 License

本 fork 沿用上游的 MIT License 声明。仓库当前未单独附带 LICENSE 文件(上游 README 标注 MIT,详见上游仓库)。(待确认:是否在本仓库补充 LICENSE 文件。)


🔀 vs 上游

  • 上游:Bronya0/OnlyWallpaper(@main),分叉基线在其 8d0f581。
  • 本 fork:RayMorTwinkle/OnlyWallpaperv2,相对上游领先约 14 个提交(本地 git rev-list 计 15 个),仅 1 个文件属于琐碎改动。

上游原貌:纯 CLI(--video / --cmd / --daemon),单一 <video loop>,窗口恒为透明,空闲暂停硬编码 5 分钟且不可关,无配置文件、无 GUI、无淡入、无首帧提取;CGO 仅链接 Cocoa / WebKit / CoreGraphics,源码只有 8 个文件。

本 fork 的增量:

维度 上游 本 fork
图形界面 ❌ 无 ✅ gui.go:Fyne 深色主题、双列布局、osascript 原生选文件、2s 状态轮询
配置持久化 ❌ 无 ✅ config.go:config.json + 旧路径迁移 + 视频历史(≤10)
淡入效果 ❌ 无 ✅ 五种模式(black/transparent/image/firstframe/firstframe-blur)
首帧提取 ❌ 无 ✅ 双引擎 ffmpeg + AVFoundation,--extract-method 可选/回退
省电开关 恒透明、空闲暂停不可关 ✅ --opaque-window(默认不透明)与 --no-idle-pause
开机自启 仅 --video --daemon ✅ plist 自动补齐全部当前设置
窗口行为 基础 collectionBehavior ✅ 追加 IgnoresCycle、屏幕参数变化响应、关闭动画
打包 ❌ 无 ✅ build_app.sh → OnlyWallpaper.app
CLI 友好度 基础 ✅ expandPath() 支持 ~/、--debug 前台日志、stop/status 便捷子命令
依赖 flock + fyne.io/fyne/v2;LDFLAGS 增加 AVFoundation / CoreMedia

历史说明:作者曾在一个提交中尝试「无缝循环(丝滑模式,双视频交替)」,随后在 0e15632(撤回)中回滚,当前代码不含该功能。故本文档不将其列为现有特性。


🙏 致谢 / Credits

  • 上游项目:Bronya0/OnlyWallpaper —— 本 fork 的全部基础(窗口层级方案、WKWebView 渲染、系统通知省电)均源自上游。
  • Fyne —— 跨平台 Go GUI 框架。
  • WKWebView —— macOS 视频渲染与 Metal 解码。
  • AVFoundation —— 原生首帧提取。
  • ffmpeg —— 可选的首帧提取工具。
  • gofrs/flock —— 跨平台文件锁。
  • 本仓库的中英双语 README 与架构图由本 fork 重制。

OnlyWallpaper · 让桌面,放一段会动的风景

About

基于 Bronya0/OnlyWallpaper 深度改造的 macOS 动态壁纸工具:原生 NSWindow + WKWebView(Metal) 将 MP4/MOV 铺为桌面背景,新增 Fyne 图形界面、配置持久化与视频历史、五种淡入效果、双引擎首帧提取及省电开关。 | A reworked macOS video-wallpaper fork using a native NSWindow + WKWebView (Metal), adding a Fyne GUI, persistent config + history, five fade-in modes, dual-engine first-frame extraction and power-saving toggles.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages