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
cpp
HWND progman = FindWindow(L"Progman", nullptr);
if (!progman) return nullptr;
Progman是咱们桌面体系的最顶层的Window,我们现在要找出来他,委托他派生一个WorkerW出来。
向 Progman 发送 0x052C 消息
下面的三行话实际上第一行话就能正确的工作(对于Windows 10+),但是为了兼容不同版本的Windows,我们都写上,实际上,这里就是我们让 Explorer 创建 WorkerW 窗口。
cpp
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 窗口
cpp
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: 备选方案
cpp
if (!found) {
HWND defInProgman = FindWindowEx(progman, nullptr, L"SHELLDLL_DefView", nullptr);
if (defInProgman) {
return progman;
}
}
- 如果没有找到空 WorkerW,就退而求其次:用
Progman
本身作为父窗口。 - 这种情况下,嵌入窗口可能会覆盖桌面图标。
cpp
#include "Windows.h"
HWND GetWorkerW() {
// 找 Progman
HWND 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);
// 等一小段时间让系统有机会创建 WorkerW
Sleep(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。
cpp
HWND hwnd = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
HWND workerw = GetWorkerW();
下一步就是设置咱们的WorkerW为我们的父窗口。
if (workerw && hwnd) {
SetParent(hwnd, workerw);
扩展样式(Extended Style)
cpp
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)
cpp
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 类型窗口,方便做全屏桌面显示。
设置大小和显示位置
cpp
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代码合计
cpp
#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>::DestructorAtExit
g_discardable_memory_allocator = LAZY_INSTANCE_INITIALIZER;
bool g_initialized_once = false;
#include "Windows.h"
HWND GetWorkerW() {
// 找 Progman
HWND 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);
// 等一小段时间让系统有机会创建 WorkerW
Sleep(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;
}