系列
Electron + Vue3开源跨平台壁纸工具实战(二)本地运行
Electron + Vue3开源跨平台壁纸工具实战(三)主进程
Electron + Vue3开源跨平台壁纸工具实战(四)主进程-数据管理(1)
Electron + Vue3开源跨平台壁纸工具实战(五)主进程-数据管理(2)
Electron + Vue3开源跨平台壁纸工具实战(六)子进程服务
Electron + Vue3开源跨平台壁纸工具实战(七)进程通信
Electron + Vue3开源跨平台壁纸工具实战(八)主进程-核心功能
Electron + Vue3开源跨平台壁纸工具实战(九)子进程服务(2)
Electron + Vue3开源跨平台壁纸工具实战(十)渲染进程
Electron + Vue3开源跨平台壁纸工具实战(十一)图片壁纸
源码
省流点我进Github获取源码,欢迎fork、star、PR
动态壁纸
动态壁纸功能允许用户将视频文件设置为桌面壁纸,通过创建特殊的透明窗口来播放视频,实现桌面背景的动态效果。该功能支持多种视频格式,提供丰富的控制选项,并在不同操作系统上采用不同的实现策略。
实现原理
1. 核心思路
动态壁纸的实现原理是通过创建一个全屏的透明窗口,在窗口内播放视频,并将该窗口设置为桌面级别,使其显示在桌面图标和壁纸之间。整个过程包括:
- 窗口创建:创建全屏透明窗口
- 视频播放:在窗口内播放视频文件
- 窗口定位:将窗口设置为桌面级别
- 交互处理:实现点击穿透,不影响桌面操作
2. 技术架构
用户选择视频 → 渲染进程 → IPC → 主进程 → DynamicWallpaperWindow → 创建透明窗口 → 视频播放 → 系统API → 桌面动态壁纸
3. 操作系统差异
Windows 实现:
- 使用
koffi库调用 Windows API - 通过
user32.dll实现窗口嵌入桌面 - 支持透明度调节和点击穿透
macOS 实现:
- 使用 Electron 的
desktop窗口类型 - 设置窗口为所有工作区可见
- 隐藏 dock 图标
Linux 实现:
- 使用标准的透明窗口
- 依赖窗口管理器的支持
核心功能实现
1. 动态壁纸窗口管理
DynamicWallpaperWindow 单例模式:
export default class DynamicWallpaperWindow {
// 单例实例
static _instance = null
// 获取单例实例
static getInstance() {
if (!DynamicWallpaperWindow._instance) {
DynamicWallpaperWindow._instance = new DynamicWallpaperWindow()
}
return DynamicWallpaperWindow._instance
}
constructor() {
if (DynamicWallpaperWindow._instance) {
return DynamicWallpaperWindow._instance
}
this.url = getWindowURL('DynamicWallpaperWindow')
this.win = null
this.options = {
frame: false,
show: false,
transparent: true,
skipTaskbar: true,
type: isMac() ? 'desktop' : '',
autoHideMenuBar: true,
enableLargerThanScreen: true,
hasShadow: false,
webPreferences: {
preload: path.join(__dirname, '../preload/index.mjs'),
sandbox: false,
webSecurity: false,
contextIsolation: true,
nodeIntegration: false,
allowRunningInsecureContent: true,
devTools: true
}
}
}
}
2. 窗口创建与配置
create 方法实现:
async create() {
return await new Promise((resolve) => {
if (this.win) {
this.win.show()
resolve()
} else {
const { x, y, width, height } = screen.getPrimaryDisplay().bounds
// 创建新的窗口
this.win = new BrowserWindow({
...this.options,
width: width,
height: isMac() ? height + 40 : height,
x,
y
})
preventContextMenu(this.win)
if (isWin()) {
// 设置点击穿透
this.win.setIgnoreMouseEvents(true, { forward: true })
}
// Mac 上设置窗口为所有工作区可见
if (isMac()) {
this.win.setHasShadow(false)
this.win.setVisibleOnAllWorkspaces(true)
this.win.setFullScreenable(false)
// 隐藏 dock 图标
app.dock.hide()
}
// 监听渲染进程console消息
this.win.webContents.on('console-message', (event, level, message, line, sourceId) => {
global.logger.info(`[Renderer Console][${level}] ${message} (${sourceId}:${line})`)
})
this.win.once('ready-to-show', async () => {
// 设置为桌面级别
if (isWin()) {
// 同时设置纯色背景壁纸图片,提高视角体验
const dynamicBackgroundColor =
global.FBW.store?.settingData?.dynamicBackgroundColor || '#FFFFFF'
await global.FBW.store?.wallpaperManager.setColorWallpaper(dynamicBackgroundColor)
setWindowsDynamicWallpaper(this.win.getNativeWindowHandle().readInt32LE(0))
}
this.win.show()
resolve()
})
this.win.on('closed', () => {
this.win = null
// Mac 上恢复 dock 图标
if (isMac()) {
app.dock.show()
}
})
}
})
}
3. 动态壁纸设置
setDynamicWallpaper 方法:
async setDynamicWallpaper(videoPath) {
try {
// 创建动态壁纸窗口
await this.create()
// 等待窗口准备好
if (this.win.isVisible()) {
this.win.webContents.send('main:setVideoSource', videoPath)
} else {
this.win.once('ready-to-show', () => {
this.win.webContents.send('main:setVideoSource', videoPath)
})
}
// 停止自动切换壁纸
await global.FBW.store?.toggleAutoSwitchWallpaper(false)
// 更新设置数据中"最后视频地址"
await global.FBW.store?.updateSettingData({
dynamicLastVideoPath: videoPath
})
return { success: true, message: t('messages.operationSuccess') }
} catch (err) {
global.logger.error(`设置动态壁纸失败: ${err}`)
return { success: false, message: t('messages.operationFail') }
}
}
4. WallpaperManager 集成
WallpaperManager 中的动态壁纸设置:
// 设置动态壁纸
async setDynamicWallpaper(videoPath) {
if (!videoPath || !fs.existsSync(videoPath)) {
return {
success: false,
message: t('messages.fileNotExist')
}
}
try {
// 调用动态壁纸设置功能
const res = await global.FBW.dynamicWallpaperWindow?.setDynamicWallpaper(videoPath)
if (res?.success) {
return {
success: true,
message: t('messages.setDynamicWallpaperSuccess')
}
} else {
return {
success: false,
message: res?.message || t('messages.setDynamicWallpaperFail')
}
}
} catch (err) {
this.logger.error(`设置动态壁纸失败: error => ${err}`)
return {
success: false,
message: t('messages.setDynamicWallpaperFail')
}
}
}
// 关闭视频壁纸
closeDynamicWallpaper() {
try {
global.FBW.dynamicWallpaperWindow?.closeDynamicWallpaper()
// 清理"最后视频地址"
global.FBW.store?.updateSettingData({
dynamicLastVideoPath: ''
})
return {
success: false,
message: t('messages.operationSuccess')
}
} catch (err) {
return {
success: false,
message: t('messages.operationFail')
}
}
}
Windows 系统实现
1. Windows API 调用
setWindowsDynamicWallpaper 函数:
export const setWindowsDynamicWallpaper = (handlers) => {
if (!handlers || process.platform !== 'win32') return false
const lib = koffi.load('user32.dll')
const FindWindowW = lib.func('FindWindowW', 'int32', ['string', 'string'])
const SendMessageTimeoutW = lib.func('SendMessageTimeoutW', 'int32', [
'int32',
'int32',
'int32',
'int32',
'int32',
'int32',
'int32'
])
const FindWindowExW = lib.func('FindWindowExW', 'int32', ['int32', 'int32', 'string', 'int32'])
const SetParent = lib.func('SetParent', 'int32', ['int32', 'int32'])
const IsWindowVisible = lib.func('IsWindowVisible', 'bool', ['int32'])
const ShowWindow = lib.func('ShowWindow', 'bool', ['int32', 'int32'])
const SetWindowPos = lib.func('SetWindowPos', 'bool', [
'int32',
'int32',
'int32',
'int32',
'int32',
'int32',
'uint32'
])
const SetLayeredWindowAttributes = lib.func('SetLayeredWindowAttributes', 'bool', [
'int32',
'uint32',
'uint8',
'uint32'
])
const HWND_BOTTOM = 1
const SWP_NOSIZE = 0x0001
const SWP_NOMOVE = 0x0002
const SWP_NOACTIVATE = 0x0010
const SetWindowLongPtrW = lib.func('SetWindowLongPtrW', 'int32', ['int32', 'int32', 'int32'])
const GWL_STYLE = -16
const WS_CHILD = 0x40000000
const GWL_EXSTYLE = -20
const WS_EX_LAYERED = 0x00080000
const WS_EX_TRANSPARENT = 0x00000020
const GetWindowLongPtrW = lib.func('GetWindowLongPtrW', 'int32', ['int32', 'int32'])
// 要触发在桌面图标和墙纸之间创建WorkerW窗口,我们必须向程序管理器发送一条消息
const progman = FindWindowW(TEXT('Progman'), null)
// 该消息是未记录的消息,因此没有专用的Windows API名称,除了0x052C
SendMessageTimeoutW(
progman,
0x052c, // 在程序管理器上生成墙纸工作程序的未记录消息
0,
0,
0x0000,
1000,
0
)
const callback = (tophandle) => {
// 找到一个具有SHELLDLL_DefView的Windows
const SHELLDLL_DefView = FindWindowExW(tophandle, 0, TEXT('SHELLDLL_DefView'), 0)
if (SHELLDLL_DefView !== 0) {
// 这里的 tophandle 就是正确的 WorkerW
SetParent(handlers, tophandle)
}
return true
}
// 注册一个回调函数指针
const callbackProto2 = koffi.proto('__stdcall', 'callbackProto2', 'bool', ['int32', 'int32'])
const EnumWindows = lib.func('EnumWindows', 'bool', [koffi.pointer(callbackProto2), 'int32'])
EnumWindows(callback, 0)
// 设置为子窗口样式
SetWindowLongPtrW(handlers, GWL_STYLE, WS_CHILD)
// 设置窗口扩展样式为 WS_EX_LAYERED | WS_EX_TRANSPARENT,实现点击穿透
let exStyle = GetWindowLongPtrW(handlers, GWL_EXSTYLE)
exStyle = exStyle | WS_EX_LAYERED | WS_EX_TRANSPARENT
SetWindowLongPtrW(handlers, GWL_EXSTYLE, exStyle)
// 设置窗口为半透明(50% 透明度)
SetLayeredWindowAttributes(handlers, 0, 128, LWA_ALPHA)
// 调整 Z 顺序
SetWindowPos(handlers, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE)
return true
}
2. Windows 透明度控制
setWindowsDynamicWallpaperOpacity 函数:
export const setWindowsDynamicWallpaperOpacity = (hwnd, alpha) => {
const lib = koffi.load('user32.dll')
const SetLayeredWindowAttributes = lib.func('SetLayeredWindowAttributes', 'bool', [
'int32',
'uint32',
'uint8',
'uint32'
])
SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA)
}
3. Windows 实现原理
WorkerW 窗口嵌入:
- 查找 Progman 窗口:使用
FindWindowW查找程序管理器窗口 - 发送特殊消息:向 Progman 发送
0x052C消息,触发创建 WorkerW 窗口 - 枚举窗口:使用
EnumWindows枚举所有窗口,找到包含SHELLDLL_DefView的 WorkerW 窗口 - 设置父窗口:使用
SetParent将动态壁纸窗口设置为 WorkerW 的子窗口 - 设置窗口样式:设置
WS_CHILD样式和WS_EX_LAYERED | WS_EX_TRANSPARENT扩展样式 - 调整 Z 顺序:将窗口置于最底层,确保不影响桌面操作
macOS 系统实现
1. 窗口类型设置
macOS 特殊配置:
this.options = {
frame: false,
show: false,
transparent: true,
skipTaskbar: true,
type: isMac() ? 'desktop' : '', // macOS 使用 desktop 类型
autoHideMenuBar: true,
enableLargerThanScreen: true,
hasShadow: false
// ... 其他配置
}
2. 工作区可见性
设置窗口为所有工作区可见:
if (isMac()) {
this.win.setHasShadow(false)
this.win.setVisibleOnAllWorkspaces(true) // 所有工作区可见
this.win.setFullScreenable(false)
// 隐藏 dock 图标
app.dock.hide()
}
3. Dock 图标管理
隐藏和恢复 Dock 图标:
// 窗口创建时隐藏 dock 图标
if (isMac()) {
app.dock.hide()
}
// 窗口关闭时恢复 dock 图标
this.win.on('closed', () => {
this.win = null
// Mac 上恢复 dock 图标
if (isMac()) {
app.dock.show()
}
})
渲染进程实现
1. 视频播放组件
DynamicWallpaperWindow.vue 核心实现:
<script setup>
const videoSrc = ref('')
const videoRef = ref(null)
const isMuted = ref(true) // 默认静音
const frameRate = ref(30) // 默认帧率
const scaleMode = ref('cover') // 默认缩放模式
const brightness = ref(100) // 默认亮度
const contrast = ref(100) // 默认对比度
let rafId = null // requestAnimationFrame ID
let lastFrameTime = 0 // 上一帧时间
// 接收视频源
const handleSetVideoSource = (event, source) => {
if (source) {
// 将本地文件路径转换为 fbwtp 协议 URL
if (!source.startsWith('fbwtp://') && !source.startsWith('http')) {
// 替换反斜杠为正斜杠,并确保路径格式正确
const formattedPath = source.replace(/\\/g, '/')
videoSrc.value = `fbwtp://fbw/api/videos/get?filePath=${formattedPath}`
} else {
videoSrc.value = source
}
}
}
// 处理静音控制
const handleSetVideoMute = (event, mute) => {
isMuted.value = mute
if (videoRef.value) {
videoRef.value.muted = mute
}
}
// 处理帧率控制
const handleSetVideoFrameRate = (event, rate) => {
frameRate.value = rate
}
// 控制视频播放帧率
const controlFrameRate = (timestamp) => {
if (!videoRef.value) {
rafId = requestAnimationFrame(controlFrameRate)
return
}
try {
const video = videoRef.value
const frameInterval = 1000 / frameRate.value
if (timestamp - lastFrameTime >= frameInterval) {
// 如果视频暂停,则播放
if (video.paused) {
video.play()
}
lastFrameTime = timestamp
} else {
// 如果帧率需要限制,则暂停视频
if (!video.paused) {
video.pause()
}
}
rafId = requestAnimationFrame(controlFrameRate)
} catch (err) {
console.error(err)
}
}
// 处理缩放模式控制
const handleSetVideoScaleMode = (event, mode) => {
scaleMode.value = mode
}
// 处理亮度控制
const handleSetVideoBrightness = (event, value) => {
brightness.value = value
}
// 处理对比度控制
const handleSetVideoContrast = (event, value) => {
contrast.value = value
}
// 计算滤镜样式
const filterStyle = computed(() => {
return `brightness(${brightness.value / 100}) contrast(${contrast.value / 100})`
})
// 监听视频源变化
watch(videoSrc, (newSrc) => {
if (newSrc) {
// 重置帧率控制
if (rafId) {
cancelAnimationFrame(rafId)
}
lastFrameTime = 0
rafId = requestAnimationFrame(controlFrameRate)
}
})
// 监听事件注册和移除需要保持一致
onMounted(() => {
// 监听设置视频源事件
window.FBW.onSetVideoSource(handleSetVideoSource)
window.FBW.onSetVideoMute(handleSetVideoMute)
window.FBW.onSetVideoFrameRate(handleSetVideoFrameRate)
window.FBW.onSetVideoScaleMode(handleSetVideoScaleMode)
window.FBW.onSetVideoBrightness(handleSetVideoBrightness)
window.FBW.onSetVideoContrast(handleSetVideoContrast)
// 启动帧率控制
rafId = requestAnimationFrame(controlFrameRate)
})
onBeforeUnmount(() => {
// 移除监听,确保与注册方式一致
window.FBW.offSetVideoSource(handleSetVideoSource)
window.FBW.offSetVideoMute(handleSetVideoMute)
window.FBW.offSetVideoFrameRate(handleSetVideoFrameRate)
window.FBW.offSetVideoScaleMode(handleSetVideoScaleMode)
window.FBW.offSetVideoBrightness(handleSetVideoBrightness)
window.FBW.offSetVideoContrast(handleSetVideoContrast)
// 取消帧率控制
if (rafId) {
cancelAnimationFrame(rafId)
}
})
</script>
<template>
<div class="window-container">
<video
v-if="videoSrc"
ref="videoRef"
:src="videoSrc"
autoplay
loop
:muted="isMuted"
class="video-player"
:style="{
objectFit: scaleMode === 'stretch' ? 'fill' : scaleMode,
filter: filterStyle
}"
></video>
</div>
</template>
<style scoped>
.window-container {
width: 100vw;
height: 100vh;
overflow: hidden;
margin: 0;
padding: 0;
background-color: transparent;
}
.video-player {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
2. 视频协议处理
本地文件路径转换:
// 将本地文件路径转换为 fbwtp 协议 URL
if (!source.startsWith('fbwtp://') && !source.startsWith('http')) {
// 替换反斜杠为正斜杠,并确保路径格式正确
const formattedPath = source.replace(/\\/g, '/')
videoSrc.value = `fbwtp://fbw/api/videos/get?filePath=${formattedPath}`
} else {
videoSrc.value = source
}
3. 帧率控制算法
requestAnimationFrame 帧率控制:
const controlFrameRate = (timestamp) => {
if (!videoRef.value) {
rafId = requestAnimationFrame(controlFrameRate)
return
}
try {
const video = videoRef.value
const frameInterval = 1000 / frameRate.value
if (timestamp - lastFrameTime >= frameInterval) {
// 如果视频暂停,则播放
if (video.paused) {
video.play()
}
lastFrameTime = timestamp
} else {
// 如果帧率需要限制,则暂停视频
if (!video.paused) {
video.pause()
}
}
rafId = requestAnimationFrame(controlFrameRate)
} catch (err) {
console.error(err)
}
}
控制功能实现
1. 性能模式设置
setDynamicWallpaperPerformance 方法:
setDynamicWallpaperPerformance(mode) {
if (!this.win) {
return { success: false, message: t('messages.noDynamicWallpaperSet') }
}
try {
// 根据性能模式设置帧率限制
let frameRate = 60 // 默认帧率
switch (mode) {
case 'high':
frameRate = 60
break
case 'balanced':
frameRate = 30
break
case 'powersave':
frameRate = 15
break
}
// 发送帧率设置到动态壁纸窗口
this.win.webContents.send('main:setVideoFrameRate', frameRate)
return { success: true, message: t('messages.operationSuccess') }
} catch (err) {
global.logger.error(`设置动态壁纸性能模式失败: ${err}`)
return { success: false, message: t('messages.operationFail') }
}
}
2. 缩放模式控制
setDynamicWallpaperScaleMode 方法:
setDynamicWallpaperScaleMode(mode) {
if (!this.win) {
return { success: false, message: t('messages.noDynamicWallpaperSet') }
}
try {
// 发送缩放模式设置到动态壁纸窗口
this.win.webContents.send('main:setVideoScaleMode', mode)
return { success: true, message: t('messages.operationSuccess') }
} catch (err) {
global.logger.error(`设置动态壁纸缩放模式失败: ${err}`)
return { success: false, message: t('messages.operationSuccess') }
}
}
3. 透明度控制
setDynamicWallpaperOpacity 方法:
setDynamicWallpaperOpacity(opacity) {
if (!this.win) {
return { success: false, message: t('messages.noDynamicWallpaperSet') }
}
try {
// 0~100 转为 0~255
const alpha = Math.round((opacity / 100) * 255)
setWindowsDynamicWallpaperOpacity(this.win.getNativeWindowHandle().readInt32LE(0), alpha)
return { success: true, message: t('messages.operationSuccess') }
} catch (err) {
return { success: false, message: err.message }
}
}
4. 亮度和对比度控制
setDynamicWallpaperBrightness 方法:
setDynamicWallpaperBrightness(brightness) {
if (!this.win) {
return { success: false, message: t('messages.noDynamicWallpaperSet') }
}
try {
// 发送亮度设置到动态壁纸窗口
this.win.webContents.send('main:setVideoBrightness', brightness)
return { success: true, message: t('messages.operationSuccess') }
} catch (err) {
global.logger.error(`设置动态壁纸亮度失败: ${err}`)
return { success: false, message: t('messages.operationFail') }
}
}
5. 背景色控制
setDynamicWallpaperBackgroundColor 方法:
async setDynamicWallpaperBackgroundColor(color) {
if (!this.win) {
return { success: false, message: t('messages.noDynamicWallpaperSet') }
}
try {
return await global.FBW.store?.wallpaperManager.setColorWallpaper(color)
} catch (err) {
global.logger.error(`设置动态壁纸背景色失败: ${err}`)
return { success: false, message: err.message }
}
}
进程间通信
1. IPC 处理器注册
DynamicWallpaperWindow 构造函数中的 IPC 处理:
constructor() {
// ... 其他代码
ipcMain.handle('main:setDynamicWallpaper', async (event, videoPath) => {
return this.setDynamicWallpaper(videoPath)
})
ipcMain.handle('main:setDynamicWallpaperMute', (event, mute) => {
return this.setDynamicWallpaperMute(mute)
})
ipcMain.handle('main:checkDynamicWallpaperStatus', () => {
return this.checkDynamicWallpaperStatus()
})
ipcMain.handle('main:setDynamicWallpaperPerformance', (event, mode) => {
return this.setDynamicWallpaperPerformance(mode)
})
ipcMain.handle('main:setDynamicWallpaperScaleMode', (event, mode) => {
return this.setDynamicWallpaperScaleMode(mode)
})
ipcMain.handle('main:setDynamicWallpaperOpacity', (event, opacity) => {
return this.setDynamicWallpaperOpacity(opacity)
})
ipcMain.handle('main:setDynamicWallpaperBrightness', (event, brightness) => {
return this.setDynamicWallpaperBrightness(brightness)
})
ipcMain.handle('main:setDynamicWallpaperContrast', (event, value) => {
return this.setDynamicWallpaperContrast(value)
})
ipcMain.handle('main:setDynamicWallpaperBackgroundColor', async (event, color) => {
return this.setDynamicWallpaperBackgroundColor(color)
})
}
2. 预加载脚本 API
preload/index.mjs 中的动态壁纸 API:
const api = {
// 动态壁纸相关API
selectVideoFile: () => ipcRenderer.invoke('main:selectVideoFile'),
setDynamicWallpaper: (...args) => ipcRenderer.invoke('main:setDynamicWallpaper', ...args),
closeDynamicWallpaper: (...args) => ipcRenderer.invoke('main:closeDynamicWallpaper', ...args),
// 设置动态壁纸静音状态
setDynamicWallpaperMute: (...args) => ipcRenderer.invoke('main:setDynamicWallpaperMute', ...args),
// 检查动态壁纸状态
checkDynamicWallpaperStatus: () => ipcRenderer.invoke('main:checkDynamicWallpaperStatus'),
// 设置动态壁纸性能模式
setDynamicWallpaperPerformance: (mode) =>
ipcRenderer.invoke('main:setDynamicWallpaperPerformance', mode),
// 设置动态壁纸缩放模式
setDynamicWallpaperScaleMode: (mode) =>
ipcRenderer.invoke('main:setDynamicWallpaperScaleMode', mode),
// 设置动态壁纸背景色
setDynamicWallpaperBackgroundColor: (value) =>
ipcRenderer.invoke('main:setDynamicWallpaperBackgroundColor', value),
// 设置动态壁纸透明度
setDynamicWallpaperOpacity: (value) =>
ipcRenderer.invoke('main:setDynamicWallpaperOpacity', value),
// 设置动态壁纸亮度
setDynamicWallpaperBrightness: (value) =>
ipcRenderer.invoke('main:setDynamicWallpaperBrightness', value),
// 设置动态壁纸对比度
setDynamicWallpaperContrast: (value) =>
ipcRenderer.invoke('main:setDynamicWallpaperContrast', value)
}
用户界面集成
1. 工具页面集成
Utils.vue 中的动态壁纸设置:
const onSetDynamicWallpaper = async () => {
const selectFileRes = await window.FBW.selectFile('video')
const videoPath = selectFileRes && !selectFileRes.canceled ? selectFileRes.filePaths[0] : null
if (!videoPath) {
ElMessage({
type: 'error',
message: t('messages.operationFail')
})
return
}
const setRes = await window.FBW.setDynamicWallpaper(videoPath)
ElMessage({
type: setRes.success ? 'success' : 'error',
message: setRes.message
})
}
const utilList = ref([
// ... 其他工具
{
name: 'setDynamicWallpaper',
text: '设置动态壁纸',
locale: 'pages.Utils.setDynamicWallpaper',
handle: onSetDynamicWallpaper
},
{
name: 'closeDynamicWallpaper',
text: '关闭动态壁纸',
locale: 'pages.Utils.closeDynamicWallpaper',
confirm: true
}
])
2. 设置页面集成
设置页面中的动态壁纸配置:
<el-form-item
:label="t('pages.Setting.settingDataForm.dynamicAutoPlayOnStartup')"
prop="dynamicAutoPlayOnStartup"
>
<el-checkbox
v-model="settingDataForm.dynamicAutoPlayOnStartup"
@change="onSettingDataFormChange"
/>
</el-form-item>
<el-form-item :label="t('pages.Setting.settingDataForm.dynamicMuteAudio')" prop="dynamicMuteAudio">
<el-checkbox
v-model="settingDataForm.dynamicMuteAudio"
@change="onSettingDataFormChange"
/>
</el-form-item>
<el-form-item
:label="t('pages.Setting.settingDataForm.dynamicBackgroundColor')"
prop="dynamicBackgroundColor"
>
<el-color-picker
v-model="settingDataForm.dynamicBackgroundColor"
:predefine="colorList"
@change="onSettingDataFormChange"
/>
</el-form-item>
启动时自动播放
1. 应用启动检查
main/index.mjs 中的启动检查:
// 等待 Store 初始化完成
await global.FBW.store?.waitForInitialization()
// 如果设置了自动播放动态壁纸
if (
global.FBW.store?.settingData?.dynamicAutoPlayOnStartup &&
global.FBW.store?.settingData?.dynamicLastVideoPath
) {
// 创建动态壁纸窗口并设置上次的视频
global.FBW.dynamicWallpaperWindow?.setDynamicWallpaper(
global.FBW.store?.settingData?.dynamicLastVideoPath
)
}
2. 设置数据持久化
动态壁纸相关设置:
dynamicAutoPlayOnStartup:启动时自动播放动态壁纸dynamicMuteAudio:动态壁纸静音dynamicBackgroundColor:动态壁纸背景色dynamicLastVideoPath:上次播放的视频路径
操作系统差异总结
1. Windows 系统特点
优势:
- 使用原生 Windows API,性能最佳
- 支持透明度调节和点击穿透
- 窗口嵌入桌面层级,视觉效果最佳
实现方式:
- 使用
koffi库调用user32.dll - 通过
WorkerW窗口嵌入桌面 - 设置
WS_EX_LAYERED | WS_EX_TRANSPARENT样式
2. macOS 系统特点
优势:
- 使用 Electron 的
desktop窗口类型 - 支持多工作区显示
- 系统集成度高
实现方式:
- 设置
type: 'desktop'窗口类型 - 使用
setVisibleOnAllWorkspaces(true) - 隐藏 dock 图标
3. Linux 系统特点
优势:
- 使用标准透明窗口
- 兼容性好
限制:
- 依赖窗口管理器支持
- 功能相对简单
实现方式:
- 使用标准透明窗口
- 依赖窗口管理器的桌面支持
技术特点
1. 跨平台兼容性
- Windows:使用原生 API,性能最佳
- macOS:使用 Electron 桌面窗口类型
- Linux:使用标准透明窗口
2. 性能优化
- 帧率控制:支持 15/30/60fps 性能模式
- 内存管理:单例模式避免重复创建窗口
- 资源管理:自动清理和状态管理
3. 用户体验
- 点击穿透:不影响桌面操作
- 透明度调节:支持 0-100% 透明度
- 视觉效果:支持亮度、对比度调节
- 背景色设置:提供纯色背景支持
4. 稳定性保障
- 错误处理:完整的异常捕获和处理
- 状态管理:窗口状态和设置数据同步
- 资源清理:窗口关闭时自动清理资源
总结
动态壁纸功能通过创建特殊的透明窗口来播放视频,实现了桌面背景的动态效果。该功能具有以下特点:
- 技术实现先进:使用原生 API 和 Electron 技术,性能优异
- 跨平台兼容:针对不同操作系统采用不同的实现策略
- 功能丰富:支持帧率控制、透明度调节、视觉效果优化
- 用户体验良好:点击穿透、自动播放、设置持久化
- 稳定性可靠:完整的错误处理和资源管理
该功能为飞鸟壁纸提供了高级的动态壁纸支持,让用户能够享受更加生动和个性化的桌面体验。