Chrome 学习小记5——demo:(动态壁纸基础)
Chrome 学习小记5——demo:(动态壁纸基础)
欸!我们有一个很自然的想法,如果我们将创建的窗口,挂载到咱们的Windows里的桌面,这不就是Windows的动态壁纸桌面吗?非常好。我们就可以进一步的对我们的程序做一个改造。
为了挂载到桌面,我们的第一步就是找出来WorkerW,也就是我们这篇博客的主角。
啥是WorkerW呢
简单来说,WorkerW 就像是 桌面上的透明舞台,给程序提供一个 “桌面级别的画布”,而不会破坏桌面图标和现有窗口层次。我们的处理核心就是在这里。实际上是一个承载层。
WorkerW 的典型用途
用途 | 说明 |
---|---|
动态壁纸 / 桌面动画 | 将自定义窗口嵌入 WorkerW,使动画或应用看起来就在桌面背景上。 |
屏保/Widget 显示 | 像一些天气插件、桌面时钟,可以通过 WorkerW 显示在桌面图标之上。 |
桌面交互隔离 | 桌面图标仍然可用,而 WorkerW 上的窗口不会挡住图标,形成视觉分层。 |
系统主题或特效 | Windows 自带的一些动画效果(如 Vista/7 的动态背景)也是通过 WorkerW 实现。 |
这些窗口的Z关系(从上到下)
- 普通应用窗口
- WorkerW(可嵌入自定义界面)
- SHELLDLL_DefView(桌面图标)
- Progman(桌面管理顶层窗口)
重点:WorkerW 一般位于桌面图标前面或者后面,取决于消息触发顺序。通过它嵌入的窗口不会挡住桌面图标,但看起来就像是桌面的一部分。
把他创建出来,给我们后续的工作奠定基础。
Step 1. 找到桌面顶层窗口 Progman
HWND progman = FindWindow(L"Progman", nullptr);
if (!progman) return nullptr;
Progman是咱们桌面体系的最顶层的Window,我们现在要找出来他,委托他派生一个WorkerW出来。
向 Progman 发送 0x052C 消息
下面的三行话实际上第一行话就能正确的工作(对于Windows 10+),但是为了兼容不同版本的Windows,我们都写上,实际上,这里就是我们让 Explorer 创建 WorkerW 窗口。
SendMessageTimeout(progman, 0x052C, 0, 0, SMTO_NORMAL, 1000, nullptr);
SendMessageTimeout(progman, 0x052C, 0xD, 0, SMTO_NORMAL, 1000, nullptr);
SendMessageTimeout(progman, 0x052C, 0xD, 1, SMTO_NORMAL, 1000, nullptr);
这种SendMessageTimeout实际上是一种异步的操作,防止咱们的电脑死住。当然,笔者测试过,需要等待一定的事件,要不然可能会创建没有成功,函数就结束了。虽然最严肃的办法是采用一个带有超时机制的TimeOut循环。
Sleep(100); // Delay to check the WorkerW
找出来WorkerW 窗口
HWND desktopWnd = GetDesktopWindow();
HWND found = nullptr;
for (HWND w = nullptr; (w = FindWindowEx(desktopWnd, w, L"WorkerW", nullptr)) != nullptr;) {HWND defView = FindWindowEx(w, nullptr, L"SHELLDLL_DefView", nullptr);if (defView == nullptr) {found = w;break;}
}
现在我们终于可以准备查找我们的Windows了,我们先去枚举所有 WorkerW 窗口。如果 WorkerW 不包含 SHELLDLL_DefView
,说明它是空白 WorkerW,可安全用作嵌入自定义窗口。找到了这样安全的WorkerW后,咱们就返回。
fallback: 备选方案
if (!found) {HWND defInProgman = FindWindowEx(progman, nullptr, L"SHELLDLL_DefView", nullptr);if (defInProgman) {return progman;}
}
- 如果没有找到空 WorkerW,就退而求其次:用
Progman
本身作为父窗口。 - 这种情况下,嵌入窗口可能会覆盖桌面图标。
#include "Windows.h"
HWND GetWorkerW() {// 找 ProgmanHWND progman = FindWindow(L"Progman", nullptr);if (!progman) {return nullptr;}SendMessageTimeout(progman, 0x052C, 0, 0, SMTO_NORMAL, 1000, nullptr);SendMessageTimeout(progman, 0x052C, 0xD, 0, SMTO_NORMAL, 1000, nullptr);SendMessageTimeout(progman, 0x052C, 0xD, 1, SMTO_NORMAL, 1000, nullptr);// 等一小段时间让系统有机会创建 WorkerWSleep(100); // 可以适当调节// HWND workerw = nullptr;HWND desktopWnd = GetDesktopWindow();// 枚举 WorkerW 窗口HWND found = nullptr;for (HWND w = nullptr;(w = FindWindowEx(desktopWnd, w, L"WorkerW", nullptr)) != nullptr;) {// 检查这个 WorkerW 是否 *不含* SHELLDLL_DefView 子窗口HWND defView = FindWindowEx(w, nullptr, L"SHELLDLL_DefView", nullptr);if (defView == nullptr) {// 这个可能是我们要的那个found = w;break;}}// 如果没找到,尝试在 Progman 下的子窗口里查if (!found) {HWND defInProgman = FindWindowEx(progman, nullptr, L"SHELLDLL_DefView", nullptr);if (defInProgman) {// Progman 下有 DefView,就用 progman 作为备选父窗口return progman;}}return found;
}
设置我们的具体样式
下面我们就来装饰一下得到的WorkerW。就用咱们创建的Widget的原生Window。
HWND hwnd = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
HWND workerw = GetWorkerW();
下一步就是设置咱们的WorkerW为我们的父窗口。
if (workerw && hwnd) {SetParent(hwnd, workerw);
扩展样式(Extended Style)
LONG exStyles = GetWindowLong(hwnd, GWL_EXSTYLE);
exStyles |= WS_EX_NOACTIVATE | WS_EX_TRANSPARENT;
SetWindowLong(hwnd, GWL_EXSTYLE, exStyles);
- WS_EX_NOACTIVATE:窗口不会激活,不抢焦点。
- WS_EX_TRANSPARENT:鼠标点击穿透,不阻挡桌面交互。
- 组合效果:你的窗口可以显示,但不影响用户正常点击桌面图标或其他窗口。
普通样式(Style)
LONG styles = GetWindowLong(hwnd, GWL_STYLE);
styles &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME | WS_POPUPWINDOW);
styles |= WS_POPUP;
SetWindowLong(hwnd, GWL_STYLE, styles);
- 去掉标题栏、边框、可调整大小等装饰。
- 设置成 WS_POPUP 类型窗口,方便做全屏桌面显示。
设置大小和显示位置
SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, screen_w, screen_h,SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW);
HWND_BOTTOM
:放在底部 z-order,确保桌面图标在上层可用。SWP_NOACTIVATE
:不激活窗口。SWP_FRAMECHANGED
:让窗口样式生效。SWP_SHOWWINDOW
:显示窗口。screen_w
,screen_h
:全屏覆盖桌面大小。
现在我们的任务完成了!
(我的截图工具把咱们的)任务栏给隐藏了,正常而言是直接像是替换了我们的桌面一样。
demo代码合计
#include <windows.h>
#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include "ui/aura/env.h"
#include "base/at_exit.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/i18n/icu_util.h"
#include "base/lazy_instance.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "base/test/test_discardable_memory_allocator.h"
#include "build/build_config.h"
#include "base/test/test_timeouts.h"
#include "mojo/core/embedder/embedder.h"
#include "ui/accessibility/platform/ax_platform_for_test.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/ime/init/input_method_initializer.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_paths.h"
#include "ui/base/win/scoped_ole_initializer.h"
#include "ui/compositor/test/test_context_factories.h"
#include "ui/display/screen.h"
#include "ui/gfx/font_util.h"
#include "ui/gl/init/gl_factory.h"
#include "ui/views/background.h"
#include "ui/views/buildflags.h"
#include "ui/views/controls/label.h"
#include "ui/views/examples/example_base.h"
#include "ui/views/examples/examples_window.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/test/desktop_test_views_delegate.h"
#include "ui/views/view.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/examples/examples_color_mixer.h"
#if defined(USE_AURA)
#include "ui/wm/core/wm_state.h"
#endif#if BUILDFLAG(ENABLE_DESKTOP_AURA)
#include "ui/views/widget/desktop_aura/desktop_screen.h"
#endif// ------------------------
// 自定义 View
// ------------------------
class SimpleView : public views::View {public:SimpleView() {SetLayoutManager(std::make_unique<views::FillLayout>());auto* label = new views::Label(u"Hello, simple Views Widget!");auto fonts = label->GetDefaultFontList().GetFonts();if(!fonts.empty()) {gfx::Font default_font = fonts[0];gfx::Font large_font(default_font.GetFontName(), 20);label->SetFontList(gfx::FontList(large_font));}AddChildView(label);SetBackground(views::CreateSolidBackground(SK_ColorWHITE));}~SimpleView() override = default;
};class SimpleDelegate : public views::WidgetDelegate {public:SimpleDelegate() = default;~SimpleDelegate() override = default;views::View* GetContentsView() override {if (!contents_) {contents_ = new SimpleView();}return contents_;}std::u16string GetWindowTitle() const override {return u"Simple Demo";}void assignedClosure(base::OnceClosure on_close) {on_close_ = std::move(on_close);}void WindowClosing() override {// Call the run_loop's Quit method to end the message loop.std::cerr << "Window is Closing!" << std::endl;if (on_close_) {std::move(on_close_).Run();}}private:raw_ptr<views::View> contents_ = nullptr;base::OnceClosure on_close_;
};base::LazyInstance<base::TestDiscardableMemoryAllocator>::DestructorAtExitg_discardable_memory_allocator = LAZY_INSTANCE_INITIALIZER;bool g_initialized_once = false;#include "Windows.h"
HWND GetWorkerW() {// 找 ProgmanHWND progman = FindWindow(L"Progman", nullptr);if (!progman) {return nullptr;}SendMessageTimeout(progman, 0x052C, 0, 0, SMTO_NORMAL, 1000, nullptr);SendMessageTimeout(progman, 0x052C, 0xD, 0, SMTO_NORMAL, 1000, nullptr);SendMessageTimeout(progman, 0x052C, 0xD, 1, SMTO_NORMAL, 1000, nullptr);// 等一小段时间让系统有机会创建 WorkerWSleep(100); // 可以适当调节// HWND workerw = nullptr;HWND desktopWnd = GetDesktopWindow();// 枚举 WorkerW 窗口HWND found = nullptr;for (HWND w = nullptr;(w = FindWindowEx(desktopWnd, w, L"WorkerW", nullptr)) != nullptr;) {// 检查这个 WorkerW 是否 *不含* SHELLDLL_DefView 子窗口HWND defView = FindWindowEx(w, nullptr, L"SHELLDLL_DefView", nullptr);if (defView == nullptr) {// 这个可能是我们要的那个found = w;break;}}// 如果没找到,尝试在 Progman 下的子窗口里查if (!found) {HWND defInProgman = FindWindowEx(progman, nullptr, L"SHELLDLL_DefView", nullptr);if (defInProgman) {// Progman 下有 DefView,就用 progman 作为备选父窗口return progman;}}return found;
}int main(int argc, char* argv[]) {base::CommandLine::Init(argc, argv);TestTimeouts::Initialize();base::AtExitManager at_exit;ui::ScopedOleInitializer ole_initializer;base::CommandLine* command_line =base::CommandLine::ForCurrentProcess();ui::AXPlatformForTest ax_platform;// Disabling Direct Composition works around the limitation that// InProcessContextFactory doesn't work with Direct Composition, causing the// window to not render. See http://crbug.com/936249.command_line->AppendSwitch(switches::kDisableDirectComposition);base::FeatureList::InitInstance(command_line->GetSwitchValueASCII(switches::kEnableFeatures),command_line->GetSwitchValueASCII(switches::kDisableFeatures));if (!g_initialized_once) {mojo::core::Init();gl::init::InitializeGLOneOff(/*gpu_preference=*/gl::GpuPreference::kDefault);base::i18n::InitializeICU();ui::RegisterPathProvider();base::DiscardableMemoryAllocator::SetInstance(g_discardable_memory_allocator.Pointer());gfx::InitializeFonts();g_initialized_once = true;}base::test::TaskEnvironment task_environment(base::test::TaskEnvironment::MainThreadType::UI);auto context_factories =std::make_unique<ui::TestContextFactories>(false,/*output_to_window=*/true);ui::ResourceBundle::InitSharedInstanceWithLocale("en-US", nullptr,ui::ResourceBundle::LoadResources::DO_NOT_LOAD_COMMON_RESOURCES);ui::ColorProviderManager::Get().AppendColorProviderInitializer(base::BindRepeating(&views::examples::AddExamplesColorMixers));std::unique_ptr<aura::Env> env = aura::Env::CreateInstance();aura::Env::GetInstance()->set_context_factory(context_factories->GetContextFactory());ui::InitializeInputMethodForTesting();views::DesktopTestViewsDelegate views_delegate;wm::WMState wm_state;std::unique_ptr<display::Screen> desktop_screen =views::CreateDesktopScreen();// ------------------------// 创建 Widget// ------------------------base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);auto* widget = new views::Widget();views::Widget::InitParams params(views::Widget::InitParams::CLIENT_OWNS_WIDGET,views::Widget::InitParams::TYPE_WINDOW);int screen_w = GetSystemMetrics(SM_CXSCREEN);int screen_h = GetSystemMetrics(SM_CYSCREEN);params.bounds = gfx::Rect(100, 100, screen_w, screen_h);auto* delegate = new SimpleDelegate();delegate->assignedClosure(run_loop.QuitClosure());params.delegate = delegate;widget->Init(std::move(params));HWND hwnd = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget();HWND workerw = GetWorkerW();if (workerw && hwnd) {// 把 widget 挂到 workerw 下SetParent(hwnd, workerw);// 扩展样式,使其不激活、不抢焦点、透明鼠标点击LONG exStyles = GetWindowLong(hwnd, GWL_EXSTYLE);exStyles |= WS_EX_NOACTIVATE | WS_EX_TRANSPARENT;SetWindowLong(hwnd, GWL_EXSTYLE, exStyles);// 去掉边框、标题等装饰LONG styles = GetWindowLong(hwnd, GWL_STYLE);styles &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME | WS_POPUPWINDOW);styles |= WS_POPUP; // popup 类型SetWindowLong(hwnd, GWL_STYLE, styles);// 设置大小和显示位置SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, screen_w, screen_h,SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW);}widget->Show();auto disable_timeout =std::make_unique<base::test::ScopedDisableRunLoopTimeout>();run_loop.Run();ui::ResourceBundle::CleanupSharedInstance();ui::ShutdownInputMethod();env.reset();return 0;
}