Compare commits

..

2 Commits

Author SHA1 Message Date
cast1e 21d4c33460 release 2024-10-14 22:08:46 +08:00
cast1e ba2cd4d5a3 core update 2024-10-14 15:20:18 +08:00
23 changed files with 236 additions and 151 deletions

View File

@ -20,12 +20,16 @@ BOOL APIENTRY DllMain( HMODULE hModule,
playerInstance* player_instance = NULL; playerInstance* player_instance = NULL;
extern "C" __declspec(dllexport) BOOL init(const char* ffpath,const char* videoPath, BOOL mute) { extern "C" __declspec(dllexport) void init(const char* ffpath,const char* videoPath, BOOL mute) {
player_instance = new playerInstance(conFig(ffpath, videoPath, mute)); player_instance = new playerInstance(conFig(ffpath, videoPath, mute));
return player_instance->generate(); player_instance->generate();
} }
extern "C" __declspec(dllexport) void destroy() { extern "C" __declspec(dllexport) void destroy() {
player_instance->exit(); player_instance->exit();
delete player_instance; delete player_instance;
} }
extern "C" __declspec(dllexport) BOOL set_wallpaper(const char* window_title) {
return set_as_wallpaper(window_title);
}

View File

@ -1,7 +1,5 @@
#include "pch.h" #include "pch.h"
#include "playerInstance.h" #include "playerInstance.h"
#include <chrono>
#include <thread>
playerInstance::playerInstance(const conFig& config) { playerInstance::playerInstance(const conFig& config) {
this->config = config; this->config = config;
@ -18,33 +16,6 @@ static BOOL CALLBACK EnumWindowsProc(_In_ HWND hwnd, _In_ LPARAM Lparam) {
return TRUE; return TRUE;
} }
HWND findWindowTimeOut(const wchar_t* name, int timeoutMillis) {
auto start = std::chrono::steady_clock::now();
HWND hwnd = nullptr;
while (true) {
// 使用 FindWindowW 查找窗口
hwnd = FindWindowW(name, 0);
// 如果找到窗口,则返回
if (hwnd != nullptr) {
return hwnd;
}
// 计算当前经过的时间
auto now = std::chrono::steady_clock::now();
int elapsedMillis = std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count();
// 如果超时,则返回 nullptr
if (elapsedMillis > timeoutMillis) {
return nullptr;
}
// 等待一段时间例如100毫秒再尝试查找
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
BOOL playerInstance::showWindow(LPCWSTR lpParameter) { BOOL playerInstance::showWindow(LPCWSTR lpParameter) {
if (this->hFfplay != NULL) { if (this->hFfplay != NULL) {
DWORD dwPID = 0; DWORD dwPID = 0;
@ -54,20 +25,14 @@ BOOL playerInstance::showWindow(LPCWSTR lpParameter) {
system(strCmd); system(strCmd);
} }
STARTUPINFO si{ 0 }; STARTUPINFO si{ 0 };
//si.dwFlags = STARTF_USESHOWWINDOW; si.dwFlags = STARTF_USESHOWWINDOW;
//si.wShowWindow = SW_HIDE; si.wShowWindow = SW_HIDE;
PROCESS_INFORMATION pi{ 0 }; PROCESS_INFORMATION pi{ 0 };
if (CreateProcess(this->config.ffpath.c_str(), (LPWSTR)lpParameter, 0, 0, 0, CREATE_NO_WINDOW, 0, 0, &si, &pi)) { if (CreateProcess(this->config.ffpath.c_str(), (LPWSTR)lpParameter, 0, 0, 0, CREATE_NO_WINDOW, 0, 0, &si, &pi)) {
//Sleep(600);//等待视频播放器启动完成 Sleep(600);//等待视频播放器启动完成
//HWND hProgman = FindWindow(L"Progman", 0);// 找到PI窗口 HWND hProgman = FindWindow(L"Progman", 0);// 找到PI窗口
HWND hProgman = findWindowTimeOut(L"Progman",2000); SendMessageTimeout(hProgman, 0x052c, 0, 0, 0, 100, 0);// 给它发特殊消息
std::this_thread::sleep_for(std::chrono::milliseconds(300)); this->hFfplay = FindWindowW(L"SDL_app", 0);// 找到视频窗口
if (hProgman == nullptr) return FALSE;
SendMessageTimeout(hProgman, 0x052c, 0, 0, 0, 300, 0);// 给它发特殊消息
//this->hFfplay = FindWindowW(L"SDL_app", 0);// 找到视频窗口
this->hFfplay = findWindowTimeOut(L"SDL_app", 2000);
std::this_thread::sleep_for(std::chrono::milliseconds(300));
if (this->hFfplay == nullptr) return FALSE;
SetParent(hFfplay, hProgman);// 将视频窗口设苦为PM的子窗口 SetParent(hFfplay, hProgman);// 将视频窗口设苦为PM的子窗口
int systemWidth = GetSystemMetrics(0); int systemWidth = GetSystemMetrics(0);
int systemHeight = GetSystemMetrics(1); int systemHeight = GetSystemMetrics(1);
@ -84,8 +49,6 @@ BOOL playerInstance::showWindow(LPCWSTR lpParameter) {
MoveWindow(hFfplay, 0, x, cRct.right - cRct.left, cRct.bottom - cRct.top, 0); MoveWindow(hFfplay, 0, x, cRct.right - cRct.left, cRct.bottom - cRct.top, 0);
}*/ }*/
EnumWindows(EnumWindowsProc, 0);// 找到第二个workerw窗口并隐藏它 EnumWindows(EnumWindowsProc, 0);// 找到第二个workerw窗口并隐藏它
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return TRUE; return TRUE;
} }
CloseHandle(pi.hProcess); CloseHandle(pi.hProcess);
@ -93,7 +56,7 @@ BOOL playerInstance::showWindow(LPCWSTR lpParameter) {
return FALSE; return FALSE;
} }
BOOL playerInstance::generate() { void playerInstance::generate() {
std::wstring fcmd = L" \""; std::wstring fcmd = L" \"";
fcmd += this->config.videoPath + L"\""; fcmd += this->config.videoPath + L"\"";
fcmd += L" -noborder -loop 0"; fcmd += L" -noborder -loop 0";
@ -101,7 +64,7 @@ BOOL playerInstance::generate() {
fcmd += L" -an"; fcmd += L" -an";
} }
fcmd += L" -fs"; fcmd += L" -fs";
return this->showWindow(fcmd.c_str()); this->showWindow(fcmd.c_str());
} }
void playerInstance::exit() { void playerInstance::exit() {
@ -112,4 +75,15 @@ void playerInstance::exit() {
sprintf_s(strCmd, "taskkill /pid %d -f", dwPID); sprintf_s(strCmd, "taskkill /pid %d -f", dwPID);
system(strCmd); system(strCmd);
} }
}
BOOL set_as_wallpaper(const char* window_title) {
HWND hProgman = FindWindow(L"Progman", 0);// 找到PI窗口
SendMessageTimeout(hProgman, 0x052c, 0, 0, 0, 100, 0);// 给它发特殊消息
std::string tmp = window_title;
HWND player = FindWindowW(0, std::wstring(tmp.begin(), tmp.end()).c_str());// 找到视频窗口
if (player == NULL) return FALSE;
SetParent(player, hProgman);// 将视频窗口设苦为PM的子窗口
EnumWindows(EnumWindowsProc, 0);// 找到第二个workerw窗口并隐藏它
return TRUE;
} }

View File

@ -9,7 +9,8 @@ private:
public: public:
playerInstance(const conFig& config); playerInstance(const conFig& config);
BOOL showWindow(LPCWSTR lpParameter); BOOL showWindow(LPCWSTR lpParameter);
BOOL generate(); void generate();
void exit(); void exit();
}; };
BOOL set_as_wallpaper(const char* window_title);

View File

@ -5,8 +5,6 @@ VisualStudioVersion = 17.9.34622.214
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wallitor-core", "wallitor-core.vcxproj", "{36591862-6F9C-4A1D-BBAD-4B1CBB1EC24B}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wallitor-core", "wallitor-core.vcxproj", "{36591862-6F9C-4A1D-BBAD-4B1CBB1EC24B}"
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wallitor-cmd", "..\wallitor-cmd\wallitor-cmd.vcxproj", "{5675A6E4-1AEF-4AA3-A366-0D975C311AAF}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64 Debug|x64 = Debug|x64
@ -23,14 +21,6 @@ Global
{36591862-6F9C-4A1D-BBAD-4B1CBB1EC24B}.Release|x64.Build.0 = Release|x64 {36591862-6F9C-4A1D-BBAD-4B1CBB1EC24B}.Release|x64.Build.0 = Release|x64
{36591862-6F9C-4A1D-BBAD-4B1CBB1EC24B}.Release|x86.ActiveCfg = Release|Win32 {36591862-6F9C-4A1D-BBAD-4B1CBB1EC24B}.Release|x86.ActiveCfg = Release|Win32
{36591862-6F9C-4A1D-BBAD-4B1CBB1EC24B}.Release|x86.Build.0 = Release|Win32 {36591862-6F9C-4A1D-BBAD-4B1CBB1EC24B}.Release|x86.Build.0 = Release|Win32
{5675A6E4-1AEF-4AA3-A366-0D975C311AAF}.Debug|x64.ActiveCfg = Debug|x64
{5675A6E4-1AEF-4AA3-A366-0D975C311AAF}.Debug|x64.Build.0 = Debug|x64
{5675A6E4-1AEF-4AA3-A366-0D975C311AAF}.Debug|x86.ActiveCfg = Debug|Win32
{5675A6E4-1AEF-4AA3-A366-0D975C311AAF}.Debug|x86.Build.0 = Debug|Win32
{5675A6E4-1AEF-4AA3-A366-0D975C311AAF}.Release|x64.ActiveCfg = Release|x64
{5675A6E4-1AEF-4AA3-A366-0D975C311AAF}.Release|x64.Build.0 = Release|x64
{5675A6E4-1AEF-4AA3-A366-0D975C311AAF}.Release|x86.ActiveCfg = Release|Win32
{5675A6E4-1AEF-4AA3-A366-0D975C311AAF}.Release|x86.Build.0 = Release|Win32
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -16,6 +16,7 @@ coverage
/cypress/videos/ /cypress/videos/
/cypress/screenshots/ /cypress/screenshots/
target/
# Editor directories and files # Editor directories and files
.vscode/ .vscode/

View File

@ -3,5 +3,5 @@
"identifier": "default", "identifier": "default",
"description": "Capability for the main window", "description": "Capability for the main window",
"windows": ["main"], "windows": ["main"],
"permissions": ["core:default", "shell:allow-open", "dialog:allow-open", "dialog:default","core:webview:allow-create-webview-window"] "permissions": ["core:default", "shell:allow-open", "dialog:allow-open", "dialog:default","core:webview:allow-create-webview-window","core:window:allow-destroy"]
} }

View File

@ -3,5 +3,5 @@
"identifier": "video", "identifier": "video",
"description": "Capability for the main window", "description": "Capability for the main window",
"windows": ["wallitor_video_playback"], "windows": ["wallitor_video_playback"],
"permissions": ["core:default", "shell:allow-open"] "permissions": ["core:default", "shell:allow-open","core:window:allow-destroy"]
} }

View File

@ -1 +1 @@
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:default","shell:allow-open","dialog:allow-open","dialog:default","core:webview:allow-create-webview-window"]},"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-maximize","core:window:allow-unmaximize","core:window:allow-toggle-maximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-start-dragging"]},"video":{"identifier":"video","description":"Capability for the main window","local":true,"windows":["wallitor_video_playback"],"permissions":["core:default","shell:allow-open"]}} {"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:default","shell:allow-open","dialog:allow-open","dialog:default","core:webview:allow-create-webview-window","core:window:allow-destroy"]},"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-maximize","core:window:allow-unmaximize","core:window:allow-toggle-maximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-start-dragging"]},"video":{"identifier":"video","description":"Capability for the main window","local":true,"windows":["wallitor_video_playback"],"permissions":["core:default","shell:allow-open","core:window:allow-destroy"]}}

View File

@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
use serde_json::{self, json}; use serde_json::{self, json};
use std::ffi::CString; use std::ffi::CString;
use std::path::Path; use std::path::Path;
use std::{fs, path}; use std::{fs};
use tauri::ipc::Response; use tauri::ipc::Response;
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
@ -44,11 +44,9 @@ async fn read_resource_dir() -> String {
#[tauri::command] #[tauri::command]
async fn get_file(path: String) -> Response { async fn get_file(path: String) -> Response {
let p = Path::new(&path); let p = Path::new(&path);
if p.starts_with(".\\") { if let Ok(true) = fs::exists(p) {
if let Ok(true) = fs::exists(p) { let data: Vec<u8> = fs::read(p).unwrap();
let data: Vec<u8> = fs::read(p).unwrap(); return tauri::ipc::Response::new(data);
return tauri::ipc::Response::new(data);
}
} }
tauri::ipc::Response::new(String::from("")) tauri::ipc::Response::new(String::from(""))
} }

View File

@ -1,7 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import TitleBar from './components/TitleBar.vue'; import TitleBar from './components/TitleBar.vue';
import { RouterLink, RouterView } from 'vue-router' import { RouterView } from 'vue-router'
import { invoke } from "@tauri-apps/api"
const isDarkTheme = window.matchMedia("(prefers-color-scheme: dark)"); const isDarkTheme = window.matchMedia("(prefers-color-scheme: dark)");
const html_node = document.getElementsByTagName("html")[0] const html_node = document.getElementsByTagName("html")[0]

View File

@ -1 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1728872416760" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3377" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M874.666667 241.066667h-202.666667V170.666667c0-40.533333-34.133333-74.666667-74.666667-74.666667h-170.666666c-40.533333 0-74.666667 34.133333-74.666667 74.666667v70.4H149.333333c-17.066667 0-32 14.933333-32 32s14.933333 32 32 32h53.333334V853.333333c0 40.533333 34.133333 74.666667 74.666666 74.666667h469.333334c40.533333 0 74.666667-34.133333 74.666666-74.666667V305.066667H874.666667c17.066667 0 32-14.933333 32-32s-14.933333-32-32-32zM416 170.666667c0-6.4 4.266667-10.666667 10.666667-10.666667h170.666666c6.4 0 10.666667 4.266667 10.666667 10.666667v70.4h-192V170.666667z m341.333333 682.666666c0 6.4-4.266667 10.666667-10.666666 10.666667H277.333333c-6.4 0-10.666667-4.266667-10.666666-10.666667V309.333333h490.666666V853.333333z" fill="#currentColor" p-id="3378"></path><path d="M426.666667 736c17.066667 0 32-14.933333 32-32V490.666667c0-17.066667-14.933333-32-32-32s-32 14.933333-32 32v213.333333c0 17.066667 14.933333 32 32 32zM597.333333 736c17.066667 0 32-14.933333 32-32V490.666667c0-17.066667-14.933333-32-32-32s-32 14.933333-32 32v213.333333c0 17.066667 14.933333 32 32 32z" fill="currentColor" p-id="3379"></path></svg> <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1728872416760" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3377" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path fill="currentColor" d="M874.666667 241.066667h-202.666667V170.666667c0-40.533333-34.133333-74.666667-74.666667-74.666667h-170.666666c-40.533333 0-74.666667 34.133333-74.666667 74.666667v70.4H149.333333c-17.066667 0-32 14.933333-32 32s14.933333 32 32 32h53.333334V853.333333c0 40.533333 34.133333 74.666667 74.666666 74.666667h469.333334c40.533333 0 74.666667-34.133333 74.666666-74.666667V305.066667H874.666667c17.066667 0 32-14.933333 32-32s-14.933333-32-32-32zM416 170.666667c0-6.4 4.266667-10.666667 10.666667-10.666667h170.666666c6.4 0 10.666667 4.266667 10.666667 10.666667v70.4h-192V170.666667z m341.333333 682.666666c0 6.4-4.266667 10.666667-10.666666 10.666667H277.333333c-6.4 0-10.666667-4.266667-10.666666-10.666667V309.333333h490.666666V853.333333z" fill="currentColor" p-id="3378"></path><path d="M426.666667 736c17.066667 0 32-14.933333 32-32V490.666667c0-17.066667-14.933333-32-32-32s-32 14.933333-32 32v213.333333c0 17.066667 14.933333 32 32 32zM597.333333 736c17.066667 0 32-14.933333 32-32V490.666667c0-17.066667-14.933333-32-32-32s-32 14.933333-32 32v213.333333c0 17.066667 14.933333 32 32 32z" fill="currentColor" p-id="3379"></path></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727275506845" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8495" data-darkreader-inline-fill="" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M563.8 512l262.5-312.9c4.4-5.2 0.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9c-4.4 5.2-0.7 13.1 6.1 13.1h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z" p-id="8496"></path></svg> <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727275506845" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8495" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path fill="currentColor" d="M563.8 512l262.5-312.9c4.4-5.2 0.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9c-4.4 5.2-0.7 13.1 6.1 13.1h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z" p-id="8496"></path></svg>

Before

Width:  |  Height:  |  Size: 710 B

After

Width:  |  Height:  |  Size: 699 B

View File

@ -1 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727275608799" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15290" data-darkreader-inline-fill="" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32z m-40 728H184V184h656v656z" p-id="15291"></path></svg> <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727275608799" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15290" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path fill="currentColor" d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32z m-40 728H184V184h656v656z" p-id="15291"></path></svg>

Before

Width:  |  Height:  |  Size: 529 B

After

Width:  |  Height:  |  Size: 518 B

View File

@ -48,7 +48,7 @@
</tr> </tr>
<tr> <tr>
<td></td> <td></td>
<td><button class="apply-button" @click="handleAdd"></button></td> <td><button class="apply-button" @click="handleAdd" ref="applyButton"></button></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -89,6 +89,7 @@ const support_img_ext_map: { [key in (typeof support_img_ext)[number]]: string }
'.gif': 'image/gif', '.gif': 'image/gif',
'.webp': 'image/webp' '.webp': 'image/webp'
} }
const applyButton = ref<HTMLButtonElement | null>(null);
defineExpose({ open }) defineExpose({ open })
@ -146,33 +147,44 @@ function checkInfo(info: AddInfo) {
} }
function handleAdd() { function handleAdd() {
if (checkInfo(addInfo.value)) invoke("new_wallpaper", { if (checkInfo(addInfo.value)) {
info: addInfo.value if (applyButton.value) {
}).then((res) => {
if (res as string == "Success") {
addInfo.value = {
name: "",
preview: "",
media: "",
description: ""
}
image_src.value = "";
store.commit("getWpList");
toggleVisible();
ElMessage({ ElMessage({
type: "success", type:"info",
message: "新建成功" message:"正在新建项目, 请勿重新点击或关闭程序"
})
applyButton.value.disabled = true;
invoke("new_wallpaper", {
info: addInfo.value
}).then((res) => {
if (applyButton.value) applyButton.value.disabled = false;
if (res as string == "Success") {
addInfo.value = {
name: "",
preview: "",
media: "",
description: ""
}
image_src.value = "";
store.commit("getWpList");
toggleVisible();
ElMessage({
type: "success",
message: "新建成功"
})
}
else ElMessage({
type: "error",
message: `新建失败 ${res}`
})
}) })
} }
else ElMessage({ }
type: "error",
message: `新建失败 ${res}`
})
})
else ElMessage({ else ElMessage({
type: "error", type: "error",
message: "请填写名称并选择媒体文件" message: "请填写名称并选择媒体文件"
}) })
} }
</script> </script>
@ -256,7 +268,7 @@ function handleAdd() {
} }
.item-add-preview:hover { .item-add-preview:hover {
background: var(--bg-color-alpha-darker); background: var(--bg-hover-fill);
} }
.item-add-preview:active { .item-add-preview:active {
@ -264,7 +276,7 @@ function handleAdd() {
} }
.item-add-image { .item-add-image {
background: linear-gradient(135deg, transparent 0%, #FFFFFF10 100%); background: linear-gradient(135deg, #0000001A 0%, #FFFFFF1A 100%);
border-radius: 5px; border-radius: 5px;
width: 400px; width: 400px;
margin-bottom: 10px; margin-bottom: 10px;

View File

@ -54,9 +54,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineProps, defineExpose, defineEmits, defineModel, watch, ref, type PropType } from 'vue'; import { defineProps, defineExpose, defineEmits, defineModel, watch, ref, type PropType } from 'vue';
type Position = "left" | "right"; type Position = "left" | "right";
import type { Info, Cell } from '@/ts/types' import type { Cell } from '@/ts/types'
import { invoke } from '@tauri-apps/api/core'; import { useStore } from 'vuex';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
const appWindow = getCurrentWebviewWindow();
const props = defineProps({ const props = defineProps({
position: { position: {
type: String as PropType<Position>, type: String as PropType<Position>,
@ -64,6 +65,7 @@ const props = defineProps({
}, },
}) })
const store = useStore();
const visible = defineModel<boolean>(); const visible = defineModel<boolean>();
const emit = defineEmits(["submit"]); const emit = defineEmits(["submit"]);
const visible_ = ref(false); const visible_ = ref(false);
@ -83,7 +85,6 @@ const cell = ref<Cell>({
} }
} }
}) })
const info_items = ref<(keyof Info)[]>(["media_type", "description", "created"]);
const bg = ref<HTMLDivElement | null>(null); const bg = ref<HTMLDivElement | null>(null);
defineExpose({ open }) defineExpose({ open })
watch(() => visible.value, (val, _) => { watch(() => visible.value, (val, _) => {
@ -102,25 +103,15 @@ function handleClose() {
function open(conFig: Cell) { function open(conFig: Cell) {
cell.value = conFig; cell.value = conFig;
console.log(cell.value)
visible.value = true; visible.value = true;
} }
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
function apply() { function apply() {
const webview = new WebviewWindow('wallitor_video_playback', { store.commit("apply_wallpaper",{
title: "wallitor_video_playback", url:`/video/?url=${cell.value.path}\\res\\${cell.value.config.info.entry_point}&mute=${cell.value.config.option.mute}`,
url: `/video/?url=${cell.value.path}\\res\\${cell.value.config.info.entry_point}`, title:"wallitor_video_playback"
fullscreen: true, })
decorations: false appWindow.minimize();
});
webview.once("tauri://created", () => {
invoke("set_wallpaper", {
title: "wallitor_video_playback"
}).then((res) => {
console.log(res)
})
});
} }
</script> </script>

View File

@ -91,7 +91,7 @@ function close() {
position: relative; position: relative;
justify-content: space-between; justify-content: space-between;
height: var(--titlebar-height); height: var(--titlebar-height);
padding: 5px; padding: 4px;
color: var(--text-color); color: var(--text-color);
} }
@ -114,7 +114,9 @@ function close() {
} }
.titlebar-button-wrapper { .titlebar-button-wrapper {
height: var(--titlebar-height); height: calc(var(--titlebar-height) - 4px);
margin-top: 5px;
margin-right: 5px;
width: fit-content; width: fit-content;
place-self: center; place-self: center;
place-items: center; place-items: center;
@ -128,15 +130,15 @@ function close() {
} }
.titlebar-button { .titlebar-button {
height: calc(var(--titlebar-height) - 6px); height: 30px;
width: calc(var(--titlebar-height) - 6px); width: 30px;
padding-top: 3px; padding-top: 3px;
padding-bottom: 3px; padding-bottom: 3px;
} }
.titlebar-button-rect { .titlebar-button-rect {
height: 34px; height: 30px;
width: 34px; width: 30px;
border-radius: 100%; border-radius: 100%;
transition: .3s; transition: .3s;
display: flex; display: flex;

View File

@ -1,6 +1,7 @@
import { createStore } from 'vuex' import { createStore } from 'vuex'
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import type { ResourceDir, wpConfig, Cell } from '@/ts/types' import type { ResourceDir, wpConfig, Cell } from '@/ts/types'
import { WebviewWindow, } from '@tauri-apps/api/webviewWindow'
function arrayBufferToString(buffer: ArrayBuffer): string { function arrayBufferToString(buffer: ArrayBuffer): string {
const decoder = new TextDecoder('utf-8') const decoder = new TextDecoder('utf-8')
@ -15,10 +16,12 @@ const support_ext_map: { [key in (typeof support_ext)[number]]: string } = {
'.webp': 'image/webp' '.webp': 'image/webp'
} }
type VideoWindow = null | WebviewWindow
export const store = createStore({ export const store = createStore({
state() { state() {
return { return {
wpList: [] as Cell[] wpList: [] as Cell[],
videoWindow: null as VideoWindow
} }
}, },
mutations: { mutations: {
@ -69,6 +72,22 @@ export const store = createStore({
}) })
} }
}) })
},
apply_wallpaper(state, payload:{
title:string,
url:string
}) {
const window_options = {
title: payload.title,
url: payload.url,
fullscreen: true,
shadow:false,
decorations: false,
transparent: true
}
if (state.videoWindow)
state.videoWindow.destroy().then((_) => {state.videoWindow = new WebviewWindow(payload.title, window_options)})
else state.videoWindow = new WebviewWindow(payload.title,window_options)
} }
} }
}) })

View File

@ -14,17 +14,15 @@ html {
--bg-color-alter: #ffffff; --bg-color-alter: #ffffff;
--bg-color: #ffffffae; --bg-color: #ffffffae;
--text-color: #1c1c1c; --text-color: #1c1c1c;
--bd-color: #bbbbbbce; --bd-color: #b2b2b2da;
--text-active: #7cbeff; --text-active: #7cbeff;
--login-button-bg: #9acdff; --titlebar-height: 40px;
--login-button-bd: #c8c8c8; --shadow-edge-glow: inset 1px 1px 1px -.5px rgba(255, 255, 255, .2), inset -1px -1px 1px -.5px rgba(255, 255, 255, .12), inset 0 0 4px rgba(255, 255, 255, .4);
--login-button-hover: #7db7f0; --shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .06);
--branch-viewing-color: rgb(122, 212, 215); --bg-color-alpha: hsla(240, 17%, 91%, 0.68);
--branch-viewing-hover-color: rgb(102, 177, 180); --bg-color-alpha-darker: hsla(0, 22%, 96%, 0.948);
--delete-box-left-border: #f56c6c; --bg-hover-fill: rgba(172, 172, 193, 0.24);
--delete-box-bg: #f56c6c42; --bg-hover-fill-close: rgba(239, 90, 90, 0.579);
--delete-box-title: #511f1f;
--delete-box-text: black;
} }
html.dark { html.dark {
@ -133,7 +131,7 @@ textarea:focus {
} }
.apply-button:hover { .apply-button:hover {
background-color: var(--bg-color-alter); background-color: var(--bg-hover-fill);
} }
.apply-button:active { .apply-button:active {

View File

@ -17,7 +17,22 @@ const apply_bar_visible = ref(false);
const applyBar = ref<InstanceType<typeof ApplyBar> | null>(null); const applyBar = ref<InstanceType<typeof ApplyBar> | null>(null);
const item_add_visible = ref(false); const item_add_visible = ref(false);
const r_display = ref(false); const r_display = ref(false);
const r_data = ref<Cell>(); const r_data = ref<Cell>({
img: "",
path: "",
config: {
name: "",
info: {
description: "",
created: 0,
media_type: "Video",
entry_point: ""
},
option: {
mute: true
}
}
});
const menu = ref<InstanceType<typeof CRMenu> | null>(null); const menu = ref<InstanceType<typeof CRMenu> | null>(null);
const options = ref<{ name: string, icon: string, handler: (data: Cell) => void }[]>([{ const options = ref<{ name: string, icon: string, handler: (data: Cell) => void }[]>([{
name: "删除", name: "删除",
@ -30,7 +45,6 @@ onMounted(() => {
}) })
function openCard(config: Cell) { function openCard(config: Cell) {
console.log(applyBar.value)
if (applyBar.value) applyBar.value.open(config); if (applyBar.value) applyBar.value.open(config);
} }

View File

@ -12,10 +12,16 @@
margin: 0; margin: 0;
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
background: transparent;
} }
#player{ #player{
width: 100%; width: 100%;
height: 100%; height: 100%;
margin: 0;
padding: 0;
opacity: 0;
transition: .5s ease-in-out;
object-fit: cover;
} }
</style> </style>
</head> </head>

View File

@ -1,6 +1,8 @@
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
interface Params{ import { getCurrentWindow } from '@tauri-apps/api/window'
url?:string interface Params {
url?: string,
mute?: string
} }
function getSearchParamsAsObject() { function getSearchParamsAsObject() {
const params = new URLSearchParams(window.location.search) const params = new URLSearchParams(window.location.search)
@ -10,22 +12,36 @@ function getSearchParamsAsObject() {
}) })
return paramsObject return paramsObject
} }
const params:Params = getSearchParamsAsObject()
if (params.url) { window.onload = () => {
console.log(params.url) invoke('set_wallpaper', {
invoke('get_file', { title: 'wallitor_video_playback'
path: params.url
}).then((res) => { }).then((res) => {
console.log(res) if(!res) {
const binary_data_arr = new Uint8Array(res as number[]) console.error("Unable to set wallpaper.");
const blob = new Blob([binary_data_arr], { type: 'video/mp4' }) const win = getCurrentWindow();
const videoUrl = URL.createObjectURL(blob) win.destroy();
const player = document.getElementById("player") as HTMLVideoElement; return;
if(player) { }
player.src = videoUrl; const params: Params = getSearchParamsAsObject()
player.play(); if (params.url) {
player.muted = true; invoke('get_file', {
player.loop = true; path: params.url
}).then((res) => {
const binary_data_arr = new Uint8Array(res as number[])
const blob = new Blob([binary_data_arr], { type: 'video/mp4' })
const videoUrl = URL.createObjectURL(blob)
const player = document.getElementById('player') as HTMLVideoElement
if (player) {
player.src = videoUrl
player.play()
if(params.mute) {
player.muted = JSON.parse(params.mute);
}
player.loop = true
setTimeout(()=>{player.style.opacity = '1'},0);
}
})
} }
}) })
} }

View File

@ -40,5 +40,11 @@ export default defineConfig({
minify: !process.env.TAURI_DEBUG ? 'esbuild' : false, minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
// 为调试构建生成源代码映射 (sourcemap) // 为调试构建生成源代码映射 (sourcemap)
sourcemap: !!process.env.TAURI_DEBUG, sourcemap: !!process.env.TAURI_DEBUG,
rollupOptions: {
input: {
main: path.resolve(__dirname, 'index.html'),
video: path.resolve(__dirname, 'video/index.html'),
},
},
}, },
}) })

View File

@ -0,0 +1,54 @@
// vite.config.ts
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "file:///D:/code/wallitor/wallitor-gui/node_modules/.pnpm/vite@5.4.7_@types+node@20.16.7/node_modules/vite/dist/node/index.js";
import vue from "file:///D:/code/wallitor/wallitor-gui/node_modules/.pnpm/@vitejs+plugin-vue@5.1.4_vite@5.4.7_@types+node@20.16.7__vue@3.5.8_typescript@5.4.5_/node_modules/@vitejs/plugin-vue/dist/index.mjs";
import vueDevTools from "file:///D:/code/wallitor/wallitor-gui/node_modules/.pnpm/vite-plugin-vue-devtools@7.4.6_rollup@4.22.4_vite@5.4.7_@types+node@20.16.7__vue@3.5.8_typescript@5.4.5_/node_modules/vite-plugin-vue-devtools/dist/vite.mjs";
import ElementPlus from "file:///D:/code/wallitor/wallitor-gui/node_modules/.pnpm/unplugin-element-plus@0.8.0_rollup@4.22.4/node_modules/unplugin-element-plus/dist/vite.mjs";
import path from "path";
import { createSvgIconsPlugin } from "file:///D:/code/wallitor/wallitor-gui/node_modules/.pnpm/vite-plugin-svg-icons@2.0.1_vite@5.4.7_@types+node@20.16.7_/node_modules/vite-plugin-svg-icons/dist/index.mjs";
var __vite_injected_original_dirname = "D:\\code\\wallitor\\wallitor-gui";
var __vite_injected_original_import_meta_url = "file:///D:/code/wallitor/wallitor-gui/vite.config.ts";
var vite_config_default = defineConfig({
clearScreen: false,
// Tauri expects a fixed port, fail if that port is not available
server: {
strictPort: true
},
envPrefix: ["VITE_", "TAURI_PLATFORM", "TAURI_ARCH", "TAURI_FAMILY", "TAURI_PLATFORM_VERSION", "TAURI_PLATFORM_TYPE", "TAURI_DEBUG"],
plugins: [
vue(),
vueDevTools(),
createSvgIconsPlugin({
iconDirs: [
path.resolve(__vite_injected_original_dirname, "src/assets/svgs")
],
symbolId: "icon-[name]"
}),
ElementPlus({
// options
})
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", __vite_injected_original_import_meta_url))
}
},
build: {
// Tauri uses Chromium on Windows and WebKit on macOS and Linux
target: process.env.TAURI_PLATFORM == "windows" ? "chrome105" : "safari13",
// don't minify for debug builds
minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
// 为调试构建生成源代码映射 (sourcemap)
sourcemap: !!process.env.TAURI_DEBUG,
rollupOptions: {
input: {
main: path.resolve(__vite_injected_original_dirname, "index.html"),
video: path.resolve(__vite_injected_original_dirname, "video/index.html")
}
}
}
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJEOlxcXFxjb2RlXFxcXHdhbGxpdG9yXFxcXHdhbGxpdG9yLWd1aVwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiRDpcXFxcY29kZVxcXFx3YWxsaXRvclxcXFx3YWxsaXRvci1ndWlcXFxcdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL0Q6L2NvZGUvd2FsbGl0b3Ivd2FsbGl0b3ItZ3VpL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZmlsZVVSTFRvUGF0aCwgVVJMIH0gZnJvbSAnbm9kZTp1cmwnXHJcblxyXG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xyXG5pbXBvcnQgdnVlIGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZSdcclxuaW1wb3J0IHZ1ZURldlRvb2xzIGZyb20gJ3ZpdGUtcGx1Z2luLXZ1ZS1kZXZ0b29scydcclxuaW1wb3J0IEVsZW1lbnRQbHVzIGZyb20gJ3VucGx1Z2luLWVsZW1lbnQtcGx1cy92aXRlJ1xyXG5pbXBvcnQgcGF0aCBmcm9tIFwicGF0aFwiO1xyXG5pbXBvcnQgeyBjcmVhdGVTdmdJY29uc1BsdWdpbiB9IGZyb20gXCJ2aXRlLXBsdWdpbi1zdmctaWNvbnNcIjtcclxuXHJcbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXHJcbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XHJcbiAgY2xlYXJTY3JlZW46IGZhbHNlLFxyXG4gIC8vIFRhdXJpIGV4cGVjdHMgYSBmaXhlZCBwb3J0LCBmYWlsIGlmIHRoYXQgcG9ydCBpcyBub3QgYXZhaWxhYmxlXHJcbiAgc2VydmVyOiB7XHJcbiAgICBzdHJpY3RQb3J0OiB0cnVlLFxyXG4gIH0sXHJcbiAgZW52UHJlZml4OiBbJ1ZJVEVfJywgJ1RBVVJJX1BMQVRGT1JNJywgJ1RBVVJJX0FSQ0gnLCAnVEFVUklfRkFNSUxZJywgJ1RBVVJJX1BMQVRGT1JNX1ZFUlNJT04nLCAnVEFVUklfUExBVEZPUk1fVFlQRScsICdUQVVSSV9ERUJVRyddLFxyXG4gIHBsdWdpbnM6IFtcclxuICAgIHZ1ZSgpLFxyXG4gICAgdnVlRGV2VG9vbHMoKSxcclxuICAgIGNyZWF0ZVN2Z0ljb25zUGx1Z2luKHtcclxuICAgICAgaWNvbkRpcnM6IFtcclxuICAgICAgICBwYXRoLnJlc29sdmUoX19kaXJuYW1lLCBcInNyYy9hc3NldHMvc3Znc1wiKSxcclxuICAgICAgXSxcclxuICAgICAgc3ltYm9sSWQ6IFwiaWNvbi1bbmFtZV1cIlxyXG4gICAgfSksXHJcbiAgICBFbGVtZW50UGx1cyh7XHJcbiAgICAgIC8vIG9wdGlvbnNcclxuICAgIH0pLFxyXG4gIF0sXHJcbiAgcmVzb2x2ZToge1xyXG4gICAgYWxpYXM6IHtcclxuICAgICAgJ0AnOiBmaWxlVVJMVG9QYXRoKG5ldyBVUkwoJy4vc3JjJywgaW1wb3J0Lm1ldGEudXJsKSlcclxuICAgIH1cclxuICB9LFxyXG4gIGJ1aWxkOiB7XHJcbiAgICAvLyBUYXVyaSB1c2VzIENocm9taXVtIG9uIFdpbmRvd3MgYW5kIFdlYktpdCBvbiBtYWNPUyBhbmQgTGludXhcclxuICAgIHRhcmdldDogcHJvY2Vzcy5lbnYuVEFVUklfUExBVEZPUk0gPT0gJ3dpbmRvd3MnID8gJ2Nocm9tZTEwNScgOiAnc2FmYXJpMTMnLFxyXG4gICAgLy8gZG9uJ3QgbWluaWZ5IGZvciBkZWJ1ZyBidWlsZHNcclxuICAgIG1pbmlmeTogIXByb2Nlc3MuZW52LlRBVVJJX0RFQlVHID8gJ2VzYnVpbGQnIDogZmFsc2UsXHJcbiAgICAvLyBcdTRFM0FcdThDMDNcdThCRDVcdTY3ODRcdTVFRkFcdTc1MUZcdTYyMTBcdTZFOTBcdTRFRTNcdTc4MDFcdTY2MjBcdTVDMDQgKHNvdXJjZW1hcClcclxuICAgIHNvdXJjZW1hcDogISFwcm9jZXNzLmVudi5UQVVSSV9ERUJVRyxcclxuICAgIHJvbGx1cE9wdGlvbnM6IHtcclxuICAgICAgaW5wdXQ6IHtcclxuICAgICAgICBtYWluOiBwYXRoLnJlc29sdmUoX19kaXJuYW1lLCAnaW5kZXguaHRtbCcpLFxyXG4gICAgICAgIHZpZGVvOiBwYXRoLnJlc29sdmUoX19kaXJuYW1lLCAndmlkZW8vaW5kZXguaHRtbCcpLFxyXG4gICAgICB9LFxyXG4gICAgfSxcclxuICB9LFxyXG59KVxyXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQWlSLFNBQVMsZUFBZSxXQUFXO0FBRXBULFNBQVMsb0JBQW9CO0FBQzdCLE9BQU8sU0FBUztBQUNoQixPQUFPLGlCQUFpQjtBQUN4QixPQUFPLGlCQUFpQjtBQUN4QixPQUFPLFVBQVU7QUFDakIsU0FBUyw0QkFBNEI7QUFQckMsSUFBTSxtQ0FBbUM7QUFBZ0ksSUFBTSwyQ0FBMkM7QUFVMU4sSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDMUIsYUFBYTtBQUFBO0FBQUEsRUFFYixRQUFRO0FBQUEsSUFDTixZQUFZO0FBQUEsRUFDZDtBQUFBLEVBQ0EsV0FBVyxDQUFDLFNBQVMsa0JBQWtCLGNBQWMsZ0JBQWdCLDBCQUEwQix1QkFBdUIsYUFBYTtBQUFBLEVBQ25JLFNBQVM7QUFBQSxJQUNQLElBQUk7QUFBQSxJQUNKLFlBQVk7QUFBQSxJQUNaLHFCQUFxQjtBQUFBLE1BQ25CLFVBQVU7QUFBQSxRQUNSLEtBQUssUUFBUSxrQ0FBVyxpQkFBaUI7QUFBQSxNQUMzQztBQUFBLE1BQ0EsVUFBVTtBQUFBLElBQ1osQ0FBQztBQUFBLElBQ0QsWUFBWTtBQUFBO0FBQUEsSUFFWixDQUFDO0FBQUEsRUFDSDtBQUFBLEVBQ0EsU0FBUztBQUFBLElBQ1AsT0FBTztBQUFBLE1BQ0wsS0FBSyxjQUFjLElBQUksSUFBSSxTQUFTLHdDQUFlLENBQUM7QUFBQSxJQUN0RDtBQUFBLEVBQ0Y7QUFBQSxFQUNBLE9BQU87QUFBQTtBQUFBLElBRUwsUUFBUSxRQUFRLElBQUksa0JBQWtCLFlBQVksY0FBYztBQUFBO0FBQUEsSUFFaEUsUUFBUSxDQUFDLFFBQVEsSUFBSSxjQUFjLFlBQVk7QUFBQTtBQUFBLElBRS9DLFdBQVcsQ0FBQyxDQUFDLFFBQVEsSUFBSTtBQUFBLElBQ3pCLGVBQWU7QUFBQSxNQUNiLE9BQU87QUFBQSxRQUNMLE1BQU0sS0FBSyxRQUFRLGtDQUFXLFlBQVk7QUFBQSxRQUMxQyxPQUFPLEtBQUssUUFBUSxrQ0FBVyxrQkFBa0I7QUFBQSxNQUNuRDtBQUFBLElBQ0Y7QUFBQSxFQUNGO0FBQ0YsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K