编程实现标题栏窗口摇动——显示桌面的未公开细节研究

目录

前言

一、“窗口摇动”功能内部原理

二、explorer.exe 中的 “窗口抖动” 实现

三、“切换到桌面” 功能所扩展的内部细节

四、概念验证

五、进一步研究如何自定义保留窗口列表


原文出处链接:[https://blog.csdn.net/qq_59075481/article/details/139204241]

本文包含微软未记录的功能和特性。遵循本文中的建议,您将自行承担风险。本文中介绍的方法可能依赖于内部实现,将来可能不再适用。

前言

如果您在 Windows 11(或 Windows 10)系统上抓住窗口标题并用鼠标摇动它,屏幕上所有其他打开的窗口都将最小化到任务栏。然后,如果您再次摇动同一个窗口,它们将返回到屏幕上。

此功能称为“窗口摇动” (TitleBar Window Shake)或 Aero Shake,自 Windows 7 起可用。微软曾经计划在 Windows 10 测试版上取消这个特性,但最终没有实施。

要在 Windows 11 上启用(或禁用)它,请转到“开始”->“设置”->“系统”->“多任务处理”。它将以名为“标题栏窗口摇动”的复选框的形式显示。

在 Windows 10 上,相同的选项是同一多任务设置屏幕中“捕捉窗口”功能的一部分。

问题是,我们能否在代码中以编程方式执行“窗口摇动”功能的结果?

Rbmm 做了一些逆向工程来回答这个问题。

顺便说一句,答案是肯定的。

如果你不想了解其工作原理而只想查看代码,请跳转到 “概念验证”。

一、“窗口摇动”功能内部原理

一切都从 uxtheme.dll 模块中的 ThemePreWndProc 函数开始。

uxtheme.dll 被映射到大多数 GUI 进程并提供熟悉的 Windows 操作系统视觉样式。

当该窗口过程收到 WM_MOVING 消息时,它会调用 SHRegGetBoolUSValueW 函数来获取系统注册表设置:

SHRegGetBoolUSValueW(
	L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced",
	L"DisallowShaking",
	FALSE,
	TRUE
);

该检查的结果用于确定是否继续,有很多开发者利用该项关闭这个功能。

如果以下任何注册表项中的 DisallowShaking 值设置为非零,则不会执行“窗口摇动”选项:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced

否则它将调用 CShakeWnd::FromHwnd,并且如果返回 0,它将调用 CShakeWnd::Attach,这将创建 CShakeWnd 类并将其附加到使用中的窗口句柄 HWND:

SetProp(hWnd, hAtom, this);

还有一个 uxtheme.dll 导出的 API (仅按照序号 #86 访问)。它具有以下声明:

BOOL WINAPI IsValidShakeWindow(HWND hWnd);

CShakeWnd 类具有以下成员函数:

CShakeWnd::AddReference
CShakeWnd::Attach
CShakeWnd::CShakeWnd
CShakeWnd::Detach
CShakeWnd::FromHwnd
CShakeWnd::FromHwndAddRef
CShakeWnd::GetMsgProc
CShakeWnd::HandleEscapeActions
CShakeWnd::IsGoodAngle
CShakeWnd::IsGoodLength
CShakeWnd::IsGoodRatio
CShakeWnd::MessageToShell
CShakeWnd::ReleaseReference
CShakeWnd::Shift
CShakeWnd::_cObj
CShakeWnd::_st

经过一些内部处理后,代码最终会调用:

static LRESULT CShakeWnd::MessageToShell(HWND hWnd);

该函数在内部查找具有 “Shell_TrayWnd” 窗口类 的窗口:

if(HWND hwndTray = FindWindowW(L"Shell_TrayWnd", 0))
{
	//...
}

然后执行以下操作:

ULONG dwProcessId;

if(GetWindowThreadProcessId(hwndTray, &dwProcessId))
{
	AllowSetForegroundWindow(dwProcessId);

	//...
}

最后:

//hWnd = window handle that was shaken
PostMessageW(hwndTray, 0x4F2, 0, (LPARAM)hWnd);

这样就向 hwndTray 发送了一条私有消息 0x4F2。

0x4F2 消息是 WM_USER 范围内的属于一个 Windows 资源管理器 窗口类的私有消息。 

所有进一步的处理均在 explorer.exe 进程内完成。

CShakeWnd::MessageToShell 本身的调用来自:

LRESULT OnPreWindowMovingShakeHandler(CShakeWnd *, THEME_MSG *)

从 CShakeWnd::HandleEscapeAction 开始看:

; void CShakeWnd::HandleEscapeAction(CShakeWnd::_ESC_ACTION)
;
; rcx = this
; rdx = _ESC_ACTION

	mov         qword ptr [rsp+8],rbx
	push        rdi
	sub         rsp,20h
	mov         rbx,rcx
	mov         edi,edx

	mov         rcx,qword ptr [rcx+58h]
	test        rcx,rcx
	jne         lbl_1
	cmp         edx,1
	jne         lbl_2

	call        qword ptr [GetCurrentThreadId]
	nop         dword ptr [rax+rax]

	xor         r8d,r8d
	lea         rdx,[CShakeWnd::GetMsgProc]
	mov         r9d,eax
	lea         ecx,[rdi+2]
	call        qword ptr [SetWindowsHookExW]
	nop         dword ptr [rax+rax]

	mov         qword ptr [rbx+58h],rax
	jmp         lbl_2

lbl_1:

	call        qword ptr [UnhookWindowsHookEx]
	nop         dword ptr [rax+rax]

	and         qword ptr [rbx+58h],0
	cmp         edi,2
	jne         lbl_2

	mov         rcx,qword ptr [rbx+18h]
	call        CShakeWnd::MessageToShell

lbl_2:

	mov         rbx,qword ptr [rsp+30h]
	add         rsp,20h
	pop         rdi
	ret

OnPreWindowMovingShakeHandler 调用 CShakeWnd::IsGoodLength、CShakeWnd::IsGoodAngle、CShakeWnd::IsGoodRatio 来确定将哪些鼠标移动可以解释为摇动。

二、explorer.exe 中的 “窗口抖动” 实现

在 explorer.exe 内部,当它收到 0x4F2 私有消息时,它会调用:

//'hWnd' = window handle for the window that was shaken
LRESULT CTray::_ShakeTriggered(enum SHOWDESKTOPTRIGGER, HWND hWnd)

它在内部调用 IsValidShakeWindow(hWnd),然后调用 IsShakeEnabled。它的汇编代码如下:

; int IsShakeEnabled(void)

	push        rbx
	sub         rsp,20h
	lea         rcx,[POLID_NoWindowMinimizingShortcuts]       ; {4BF2C693-2A9A-48F8-89A6-0A778F7B1D7D}
	call        qword ptr [SHWindowsPolicy]
	nop         dword ptr [rax+rax]

	test        eax,eax
	jne         lbl_10

	lea         ebx,[rax+1]
	mov         dl,bl
	lea         rcx,['wil::Feature<__WilFeatureTraits_Feature_AeroShakeEnabled>::GetImpl'::'2'::impl]
	call        'wil::details::FeatureImpl<__WilFeatureTraits_Feature_AeroShakeEnabled>::ReportUsage'

	mov         r9d,ebx
	lea         rdx,L"DisallowShaking"
	xor         r8d,r8d
	lea         rcx,L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"
	call        qword ptr [SHRegGetBoolUSValueW]
	nop         dword ptr [rax+rax]

	test        eax,eax
	jne         lbl_10

	call        IsShakedAllowedInSku

	test        eax,eax
	jne         lbl_11

lbl_10:

	xor         ebx,ebx

lbl_11:

	mov         eax,ebx
	add         rsp,20h
	pop         rbx
	ret

IsShakedAllowedInSku 是一个巨大的函数,除了“DisallowShaking” 注册表设置已经指定的检查之外,它进一步确定是否可以使用“窗口摇动”功能。

如果 IsShakeEnabled 函数返回成功,代码将调用私有函数 CTray::_MinimizeAll :

int CTray::_MinimizeAll(BOOL , HANDLE hEvent)

这反过来又调用 TrayUI::MinimizeAll:

virtual BOOL TrayUI::MinimizeAll(BOOL b, HANDLE hEvent)
{
	SendMessageW(hwnd, 0x435, (WPARAM)hEvent, b);
}

该函数向具有 “MSTaskSwWClass” 窗口类和 “运行中的应用程序”(Running applications)标题的窗口发送另一条私有消息 0x435。

然后调用以下函数:

int CTaskBand::_MinimizeAll(HEVENT hEvent, BOOL b)

该线程最小化所有窗口,并在结束时向 CTaskBand::_MinimizeAll 发送先前传递的 hEvent 信号。

此外,根据传递给 CTaskBand::_MinimizeAll 的布尔变量,它可以向 Shell 桌面窗口发送私有消息(通过调用 GetShellWindow 函数返回)。结果,它将调用内部函数 CDesktopBrowser::_Raise(BOOL),调用树如下:

CDesktopBrowser::_WndProcBS
  CDesktopBrowser::_OnRaise
    CDesktopBrowser::_Raise
      GetLastActivePopup
      CDesktopBrowser::_EnsureRaisedDesktop
        SHCreateWorkerWindowW
        SetPropW
        GlobalAddAtomW
        SetPropW
        GlobalDeleteAtom
        SetPropW
        SetPropW
        DwmSetWindowAttribute
        CZOrderManager::InsertWindowInZOrder
        SHRestricted
        RegisterDragDrop
        CDesktopBrowser::_ShowOrHideDesktopIconsBasedOnDesktopVisibility
        __security_check_cookie
        ret
      LockWindowUpdate
      CDesktopBrowser::_SwapParents
      GetWindowRect
      SetWindowPos
      SetForegroundWindow
      LockWindowUpdate
      SetFocus
      __security_check_cookie
      ret
    PostMessageW
  __security_check_cookie
  ret

三、“切换到桌面” 功能所扩展的内部细节

我们知道,当按下快捷键 “Windows + D” 或者鼠标点击任务栏右侧的 “显示桌面” 按钮时,操作将最小化全部的桌面窗口。

对于那些没有 Windows/Start 键的人来说,还有一种方法......

运行...对话框或资源管理器地址栏中,命令:

Shell:::{3080F90D-D7AD-11D9-BD98-0000947B0257}

执行“显示桌面”命令。因此,如果创建一个新的快捷方式并将以下内容粘贴为目标:

explorer "Shell:::{3080F90D-D7AD-11D9-BD98-0000947B0257}"

Explorer将在创建时充实路径和扩展)

并指定一个快捷键组合,你将有一个显示桌面的键盘快捷键。

此外,还可以创建一个名为 Show Desktop.scf(确保文件扩展名确实是 .scf)的文件,其内容如下:

[Shell]
Command=2
IconFile=explorer.exe,3
[Taskbar]
Command=ToggleDesktop

然后,可以通过右键单击文件 →发送到 →桌面(创建快捷方式)来创建此文件的快捷方式。然后打开快捷方式的文件属性(右键单击 →属性),在快捷方式选项卡上,通过单击此字段并执行所需的组合键来为其指定快捷键。

“切换到桌面” 和 “窗口摇动” 的唯一不同点是触发方式不太一样,现在我将不得不解释上面没有展开的一些细节。

首先,无论是 “窗口摇动” 还是 “切换到桌面”(Toggle Desktop),它们都具有可恢复性,当再次重复操作时(重复摇动窗口或者再次输入快捷键等),原本被 “最小化” 的窗口将逐一恢复(动画)。

这是如何实现的?这就要涉及到 ZOrder 了,每一个窗口都有它的 Z-序,上面的操作并不是通过普通的广播窗口最小化消息来实现的,而是直接操作了 ZOrderList,将指定的窗口(如桌面窗口)插入到 可见 Z 序 的最前面。这就好比将一张纸张直接放到了首页一样。

这也就是为什么我们不能够第一时间在应用的窗口接收到 WM_WINDOWPOSCHANGING 消息的原因(在接收到消息之前就可以看到桌面已经跑到最前面了,应用窗口最小化只是附带的状态)。

而对于快捷键 “Windows + M” 则处理有所不同,它是将所有窗口都直接最小化,而不保留可恢复的显示顺序。

先来谈谈可恢复式的状态切换:

除了要必须要发送的私有消息 0x435 以外,还需要发送私有消息 0x434 和 0x8B。

首先发送一条 0x434 消息,其 wParam 传递 hEvent ,lParam 为 0。然后,再紧接着发送一条 0x435 消息,并指定 lParam 为 1(TRUE,当 lParam 传递 FALSE 时,就会忽略后面的 0x8B 消息,那么就是彻底的最小化操作了),这将要求传递后面需要恢复的窗口顺序链表和前台窗口句柄。

最后发送一条或者两条 0x8B 消息,wParam 表示前台活动窗口句柄(焦点窗口),lParam 很可能是内部结构体的指针(包含窗口顺序信息)。

发送第一次 0x8B 消息,将置后所有窗口;发送第二次 0x8B 消息,将恢复窗口顺序。

下图展示了实际环境中先后两次按下 “显示桌面” 按钮的消息记录,其中 SHELLHOOK 消息属于背景噪声:

然后,我们再来谈谈最小化所有窗口的操作:

其实就是砍掉 0x434 消息,然后直接指定 0x435 消息的 lParam 为 0:

四、概念验证

如果我们消除所有系统注册表检查,最重要的是调用 IsShakedAllowedInSku,我们可以创建以下 POC 函数,它将最小化所有打开的窗口,并且无法在重复操作中恢复:

#include <iostream>
#include <Windows.h>
#include <cassert>

#define REBARCLASSNAMEW L"ReBarWindow32"

void MinimizeAllWindows()
{
	if (HWND hwnd = FindWindowW(L"Shell_TrayWnd", 0))
	{
		ULONG dwProcessId;

		if (GetWindowThreadProcessId(hwnd, &dwProcessId))
		{
			AllowSetForegroundWindow(dwProcessId);

			HWND hWndChild = 0;
			while (hWndChild = FindWindowExW(hwnd,
				hWndChild,
				REBARCLASSNAMEW,
				0))
			{
				if (HWND hwndTask = FindWindowEx(hWndChild,
					0,
					L"MSTaskSwWClass",
					0 /*name*/))
				{
					if (HANDLE hProcess = OpenProcess(PROCESS_DUP_HANDLE,
						FALSE,
						dwProcessId))
					{
						if (HANDLE hEvent = CreateEvent(0, 0, 0, 0))
						{
							HANDLE hRemoteEvent;
							if (DuplicateHandle(GetCurrentProcess(),
								hEvent,
								hProcess,
								&hRemoteEvent,
								0,
								0,
								DUPLICATE_SAME_ACCESS))
							{
								if (PostMessageW(hwndTask,
									0x435,
									(WPARAM)hRemoteEvent,
									0))
								{
									ULONG n;
									while (1 == (n =
										MsgWaitForMultipleObjectsEx(1,
											&hEvent,
											2000,
											QS_ALLINPUT,
											MWMO_INPUTAVAILABLE)))
									{
										MSG msg;
										while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE))
										{
											DispatchMessageW(&msg);
										}
									}

									if (WAIT_OBJECT_0 != n)
									{
										assert(false);
									}
								}

								DuplicateHandle(hProcess,
									hRemoteEvent,
									0,
									0,
									0,
									0,
									DUPLICATE_CLOSE_SOURCE);
							}

							CloseHandle(hEvent);
						}

						CloseHandle(hProcess);
					}
					break;
				}
			}
		}
	}
}

int main()
{
	MinimizeAllWindows();
	return 0;
}

紧接着是可以恢复的操作(例如 显示桌面):

#include <iostream>
#include <Windows.h>
#include <cassert>

#define REBARCLASSNAMEW L"ReBarWindow32"

void ToggleAllWindows(_In_ BOOL bDialogsToo, _In_opt_ HWND hwndMy = 0)
{
	if (HWND hwnd = FindWindowW(L"Shell_TrayWnd", 0))
	{
		ULONG dwProcessId;

		if (GetWindowThreadProcessId(hwnd, &dwProcessId))
		{
			AllowSetForegroundWindow(dwProcessId);

			HWND hWndChild = 0;
			while (hWndChild = FindWindowExW(hwnd,
				hWndChild,
				REBARCLASSNAMEW,
				0))
			{
				if (HWND hwndTask = FindWindowEx(hWndChild,
					0,
					L"MSTaskSwWClass",
					0 /*name*/))
				{
					if (HANDLE hProcess = OpenProcess(PROCESS_DUP_HANDLE,
						FALSE,
						dwProcessId))
					{
						if (HANDLE hEvent = CreateEvent(0, 0, 0, 0))
						{
							HANDLE hRemoteEvent;
							if (DuplicateHandle(GetCurrentProcess(),
								hEvent,
								hProcess,
								&hRemoteEvent,
								0,
								0,
								DUPLICATE_SAME_ACCESS))
							{
								// 新增 1 
								PostMessageW(hwndTask,
									0x434,
									(WPARAM)hRemoteEvent,           // 在显示桌面时触发它
									0);

								// 原始的
								if (PostMessageW(hwndTask,          // 只传入 0x435 私有消息,则 Shake Window 时最小化的效果
									0x435,
									(WPARAM)hRemoteEvent,
									(LPARAM)bDialogsToo))   // bDialogsToo 为 1 ,则需要提供窗口列表以供恢复时使用;
									// 如果为 0, 则类似 Win + M ,直接最小化所有窗口而不会在再此调用时恢复
								{
									// 新增 2 
									PostMessageW(hwndTask,
										0x8B,
										(WPARAM)hwndMy,     // wParam 为显示桌面前前台窗口的句柄。
										// 猜测 Shell 会保存 ZOrder 小于它的所有窗口层次
										(LPARAM)0x03D8F8E0);  // 控制 ZOrderList 的保存和恢复,
									// 每次建立桌面 wParam 和 lParam 都会变
									// 如果传入无效的值,将默认再次回到桌面

									ULONG n;
									while (1 == (n =
										MsgWaitForMultipleObjectsEx(1,
											&hEvent,
											2000,
											QS_ALLINPUT,
											MWMO_INPUTAVAILABLE)))
									{
										MSG msg;
										while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE))
										{
											DispatchMessageW(&msg);
										}
									}

									if (WAIT_OBJECT_0 != n)
									{
										assert(false);
									}
								}

								DuplicateHandle(hProcess,
									hRemoteEvent,
									0,
									0,
									0,
									0,
									DUPLICATE_CLOSE_SOURCE);
							}

							CloseHandle(hEvent);
						}

						CloseHandle(hProcess);
					}
					break;
				}
			}
		}
	}
}

int main()
{
	Sleep(300);
	ToggleAllWindows(TRUE, (HWND)0x140E12);  // 窗口句柄
	system("pause");
	ToggleAllWindows(FALSE);
	system("pause");
	return 0;
	return 0;
}

“窗口摇动”是 Windows 资源管理器的一个便捷功能,如果您想最小化除一个窗口之外的所有窗口,这个功能会非常方便。

五、进一步研究如何自定义保留窗口列表

Rbmm 并没有提供完整的方案和代码,甚至上面的概念验证也是我修正的一部分,未来将在这里研究清楚 0x8B 的 lParam 以及 CZOrderManager 接口的更多细节。在该理论出现之前,我们使用下面的方法保留某个窗口的显示:

使用计时器或者事件 SetWinEventHook 来确定何时发生最小化,并使用 SetWindowsPos 恢复窗口的焦点,或者提前将窗口设置为桌面的子窗口或者给窗口添加 WS_EX_TOPMOST 扩展属性。但是在运用于 “显示桌面” 这种非消息传递引起的窗口层次变化上,有着缺陷。

未来,我保守估计将有下面的新方案:

方案一:检测 0x435 消息,并在消息处理前将我们的窗口设置为桌面的子窗口或者 WS_EX_TOPMOST,这将避免最小化,然后再再次按下 Win + D 时,恢复窗口设置。

方案二:需要研究清楚 0x8B 消息的 wParam 和 lParam ,它决定了Shell 最小化时要保留的窗口列表以及最小化设置。


原文出处链接:[https://blog.csdn.net/qq_59075481/article/details/139204241]。

本文发布于:2024.05.25,更新于:2024.05.26。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/644049.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ELK 日志监控平台(二)- 优化日志格式

文章目录 ELK 日志监控平台&#xff08;二&#xff09;- 优化日志格式1.日志输出要点2.优化应用的日志格式2.1.确定日志输出要点来源2.1.1.服务名称2.1.2.服务环境2.1.3.日志级别2.1.4.日志输出时间2.1.5.日志内容2.1.6.日志输出对象2.1.7.线程名称 2.2.logback.xml修改日志输出…

win10安装rabbitmq

安装 第一步&#xff1a;下载并安装erlang RabbitMQ服务端代码是使用并发式语言Erlang编写&#xff0c;因此首先需要安装Erlang下载地址&#xff1a;http://www.erlang.org/downloads采用默认安装即可&#xff0c;选择适合的安装路径 添加环境变量 第二步&#xff1a;下载并…

【DevOps】深入了解RabbitMQ:AMQP协议基础、消息队列工作原理和应用场景

目录 一、核心功能 二、优势 三、核心概念 四、工作原理 五、交换机类型 六、消息确认 七、持久性和可靠性 八、插件和扩展 九、集群和镜像队列 十、客户端库 十一、管理界面 十二、应用场景 RabbitMQ是一个基于AMQP协议的消息队列中间件&#xff0c;提供高可用、可…

【数据结构与算法 刷题系列】移除链表元素

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;数据结构与算法刷题系列&#xff08;C语言&#xff09; 期待您的关注 目录 一、问题描述 二、解题思路 三、源代码实现 一、问题…

Qt for android 获取USB设备列表(二)JNI方式 获取

简介 基于上篇 [Qt for android 获取USB设备列表&#xff08;一&#xff09;Java方式 获取]&#xff0c; 这篇就纯粹多了&#xff0c; 直接将上篇代码转换成JNI方式即可。即所有的设备连接与上篇一致。 (https://listentome.blog.csdn.net/article/details/139205850) 关键代码…

UTC与GPS时间转换-[week, sow]

UTC与GPS时间转换-[week, sow] utc2gpsgps2utc测试参考 Ref: Global Positioning System utc2gps matlab源码 function res utc2gps(utc_t, weekStart)%% parameterssec_day 86400;sec_week 604800;leapsec 18; % 默认周一为一周的开始if nargin < 2weekStart d…

HarmonyOS-MPChart绘制一条虚实相接的曲线

本文是基于鸿蒙三方库mpchart&#xff08;OpenHarmony-SIG/ohos-MPChart&#xff09;的使用&#xff0c;自定义绘制方法&#xff0c;绘制一条虚实相接的曲线。 mpchart本身的绘制功能是不支持虚实相接的曲线的&#xff0c;要么完全是实线&#xff0c;要么完全是虚线。那么当我…

VTK 数据处理:特征边提取

VTK 数据处理&#xff1a;特征边提取 VTK 数据处理&#xff1a;特征边提取原理实例 1&#xff1a;边界边提取实例 2&#xff1a;模型特征边提取实例 3&#xff1a;利用 vtkFeatureEdges 提取的边界补洞实例 4&#xff1a;利用 vtkFillHolesFilter 补洞 VTK 数据处理&#xff1a…

[C语言]自定义类型详解:结构体、联合体、枚举

目录 &#x1f680;结构体 &#x1f525;结构体类型的声明 &#x1f525;结构的自引用 &#x1f525;结构体变量的定义和初始化 &#x1f525;结构体内存对齐 &#x1f525;结构体传参 &#x1f525;结构体实现位段&#xff08;位段的填充&可移植性&#xff09; &a…

企业异地网络组网:SD-WAN解决方案的优势

在当今全球化的商业环境中&#xff0c;企业的业务扩展已不再局限于本地或单一国家。随着分公司和子公司的不断增加&#xff0c;企业总部与这些分支机构之间的数据通信和资源共享变得尤为重要。然而&#xff0c;传统的网络访问方式&#xff0c;如点对点电路和多协议标签交换&…

17.7K星开源产品分析平台:Posthog

Posthog&#xff1a;开源洞察&#xff0c;产品优化的得力助手 - 精选真开源&#xff0c;释放新价值。 概览 PostHog是一个全面开源的平台&#xff0c;旨在帮助团队构建更好的产品。它提供了从产品分析到会话回放、功能标志和A/B测试等一系列工具&#xff0c;支持自托管&#x…

个人博客网站搭建笔记1

文章目录 前言要求自己的理解资源过程视频教程SpringBoot开发一个小而美的个人博客p1课程介绍p2需求和功能 前言 自己之前其实就想搭建一个属于自己的网站&#xff0c;但是不知道怎么操作&#xff0c;没找到合适的教程&#xff0c;&#xff08;手把手的那种&#xff09;&#…

Prometheus+Grafana监控服务器、mysql数据库并配置报警规则推送邮箱

文章目录 一、安装prometheus1.1下载1.2 安装1.3 开机启动1.4 验证 二、安装 Grafana2.1 下载2.2 安装2.3 启动2.4 验证 三、安装服务器监控 node_exporter3.1 下载3.2 安装3.3 设置 node_exporter 系统服务3.4 设置开机自动启动3.5 验证3.6配置Prometheus3.7 修改 Prometheus …

Tomcat部署项目的方式

目录 1、Tomcat发布项目的方式 方式1&#xff1a; 直接把项目发布到webapps目录下 方式2&#xff1a;项目发布到ROOT目录 方式3&#xff1a;虚拟路径方式发布项目 方式4&#xff1a;(推荐)虚拟路径&#xff0c;另外的方式&#xff01; 方式5&#xff1a;发布多个网站 1、…

[windows系统安装/重装系统][step-4][番外篇-2]N卡驱动重装 |解决:开机几小时后电脑卡顿 | 后台自动运行了上千个Rundll32进程问题

现象 开机几小时后&#xff0c;电脑变卡&#xff0c;打开后台管理器都卡&#xff0c;后台管理去转圈圈一小会儿后看到后台进程上千个&#xff0c;好多个Rundll32进程 重启下运行会稍快 重启后运行快&#xff0c;后台管理器反应也快 打开后台管理器不卡&#xff08;几小时后打…

Python语法(全)

前言&#xff1a; 下面是Python基本的语法&#xff0c;大家耐心观看&#xff01; 1.基础语法 1.1字面量 字面量&#xff1a;在代码中&#xff0c;被写下来的的固定的值&#xff0c;称之为字面 1.2字符串 字符串&#xff08;string&#xff09;&#xff0c;又称文本&#xff…

C语言数据结构栈的概念及结构、栈的实现、栈的初始化、销毁栈、入栈、出栈、检查是否为空、获取栈顶元素、获取有效元素个数等的介绍

文章目录 前言栈的概念及结构栈的实现一、 栈结构创建二、 初始化结构三、销毁栈四、入栈五、出栈六、检查是否为空七、获取栈顶元素八、获取有效元素的个数九、测试 1十、测试 2总结 前言 C语言数据结构栈的概念及结构、栈的实现、栈的初始化、销毁栈、入栈、出栈、检查是否为…

blender 布尔运算,切割模型。

1.创建一个立方体和球体。 2.选中立方体&#xff0c;在属性面板添加布尔修改器。点击物体属性右边的按钮选中球体。参数如下。 3.此时隐藏球体&#xff0c;就可以看到被切掉的效果了。

C结构详解

目录 1、结构模板 1. 建立结构声明 2. 定义结构变量 3. 访问结构成员 4. 初始化结构 声明结构数组 声明和初始化结构指针 1、结构模板 1. 建立结构声明 struct book{char title[MAXTITL];char author[MAXAUTL];float value; }&#xff1b; 该声明描述了一个又两个字符…

【漫画算法】哈希表:古代皇帝的秘密魔法书

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…