Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create cef-titlebar-enabler.wh.cpp #1326

Merged
merged 7 commits into from
Dec 13, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
304 changes: 304 additions & 0 deletions mods/cef-titlebar-enabler-universal.wh.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
// ==WindhawkMod==
// @id cef-titlebar-enabler-universal
// @name CEF/Spotify Titlebar Enabler
// @description Force native frames and title bars for CEF apps
// @version 0.1
// @author Ingan121
// @github https://github.com/Ingan121
// @twitter https://twitter.com/Ingan121
m417z marked this conversation as resolved.
Show resolved Hide resolved
// @homepage https://www.ingan121.com/
// @include spotify.exe
// @include cefclient.exe
// ==/WindhawkMod==

// ==WindhawkModReadme==
/*
# CEF Titlebar Enabler
* Force native frames and title bars for CEF apps, such as Spotify
* Only works on apps using native CEF top-level windows
* Steam uses SDL for its top-level windows (except DevTools), so this mod doesn't work with Steam
* Electron apps are NOT supported! Just patch asar to override `frame: false` to true in BrowserWindow creation
* Supported CEF versions: 90.4 to 132
* This mod won't work with versions before 90.4
* Versions after 132 may work but are not tested
* Variant of this mod using copy-pasted CEF structs instead of hardcoded offsets is available at [here](https://github.com/Ingan121/files/tree/master/cte)
* Copy required structs/definitions from your wanted CEF version (available [here](https://cef-builds.spotifycdn.com/index.html)) and paste them to the above variant to calculate the offsets
* Testing with cefclient: `cefclient.exe --use-views --hide-frame --hide-controls`
* Supported Spotify versions: 1.1.60 to 1.2.52 (newer versions may work)
* Spotify notes:
* Old releases are available [here](https://docs.google.com/spreadsheets/d/1wztO1L4zvNykBRw7X4jxP8pvo11oQjT0O5DvZ_-S4Ok/edit?pli=1&gid=803394557#gid=803394557)
* 1.1.60-1.1.67: Use [SpotifyNoControl](https://github.com/JulienMaille/SpotifyNoControl) to remove the window controls
* 1.1.68-1.1.70: Window control hiding doesn't work yet
* 1.1.74: Last version to support proper DWM window controls
* Native DWM window controls are invisible since 1.1.75 as it began hooking window messages to support Windows 11 maximize button hover menu
* 1.1.85: Last version to support proper non-DWM window controls
* They are still visible after 1.1.85, but they don't respond to clicks
* 1.2.7: First version to use Library X UI by default
* 1.2.13: Last version to have the old UI
* 1.2.45: Last version to support disabling the global navbar
* Try toggling hardware acceleration multiple times (both in window menu and preferences) if you want a proper window icon. It may work on recent-ish versions
*/
// ==/WindhawkModReadme==

// ==WindhawkModSettings==
/*
- showframe: true
$name: Enable native frames and title bars
- showmenu: true
$name: Show the menu button
$description: Disabling this also prevents opening the Spotify menu with the Alt key
- showcontrols: false
$name: Show Spotify's custom window controls
*/
// ==/WindhawkModSettings==

/* Spotify CEF version map
90.6: 1.1.60-1.1.62
91.1: 1.1.63-1.1.67
91.3: 1.1.68-1.1.70
94: 1.1.71
102: 1.1.89
106: 1.1.97-1.2.3
109: 1.2.4-1.2.6
110: 1.2.7
111: 1.2.8-1.2.10
112: 1.2.11-1.2.12
113: 1.2.13-1.2.19
115: 1.2.20
116: 1.2.21-1.2.22
117: 1.2.23-1.2.24
118: 1.2.25
119: 1.2.26
120: 1.2.28-1.2.30
121: 1.2.31-1.2.32
122: 1.2.33-1.2.37
124: 1.2.38-1.2.39
125: 1.2.40-1.2.44
127: 1.2.45-1.2.46
128: 1.2.47-1.2.48
129: 1.2.49-1.2.50
130: 1.2.51-1.2.52
*/

#include <libloaderapi.h>
#include <windhawk_utils.h>

#define CEF_CALLBACK __stdcall
#define CEF_EXPORT __cdecl
#define ANY_MINOR -1

struct cte_settings {
BOOL showframe;
BOOL showmenu;
BOOL showcontrols;
} cte_settings;

typedef struct cte_offset {
int ver_major;
int ver_minor; // -1 for any
int offset_x86;
int offset_x64;
} cte_offset_t;

cte_offset_t is_frameless_offsets[] = {
{90, 4, 0x48, 0x90},
{90, 5, 0x48, 0x90},
{90, 6, 0x48, 0x90},
{91, 0, 0x48, 0x90},
{91, 1, 0x48, 0x90},
// (91.2 is found nowhere)
{91, 3, 0x50, 0x90},
{92, ANY_MINOR, 0x50, 0xa0},
{101, ANY_MINOR, 0x50, 0xa0},
{102, ANY_MINOR, 0x54, 0xa8},
{107, ANY_MINOR, 0x54, 0xa8},
{108, ANY_MINOR, 0x5c, 0xb8},
{114, ANY_MINOR, 0x5c, 0xb8},
{115, ANY_MINOR, 0x60, 0xc0},
{116, ANY_MINOR, 0x60, 0xc0},
{117, ANY_MINOR, 0x64, 0xc8},
{123, ANY_MINOR, 0x64, 0xc8},
{124, ANY_MINOR, 0x68, 0xd0},
{130, ANY_MINOR, 0x68, 0xd0}
};

cte_offset_t add_child_view_offsets[] = {
{94, ANY_MINOR, 0xf0, 0x1e0},
{122, ANY_MINOR, 0xf0, 0x1e0},
{124, ANY_MINOR, 0xf4, 0x1e8},
{130, ANY_MINOR, 0xf4, 0x1e8}
};

int is_frameless_offset = NULL;
int add_child_view_offset = NULL;

typedef int CEF_CALLBACK (*is_frameless_t)(struct _cef_window_delegate_t* self, struct _cef_window_t* window);
int CEF_CALLBACK is_frameless_hook(struct _cef_window_delegate_t* self, struct _cef_window_t* window) {
Wh_Log(L"is_frameless_hook");
return 0;
}

typedef _cef_window_t* CEF_EXPORT (*cef_window_create_top_level_t)(void* delegate);
cef_window_create_top_level_t CEF_EXPORT cef_window_create_top_level_original;
_cef_window_t* CEF_EXPORT cef_window_create_top_level_hook(void* delegate) {
Wh_Log(L"cef_window_create_top_level_hook");

if (is_frameless_offset != NULL && cte_settings.showframe == TRUE) {
((is_frameless_t*)((char*)delegate + is_frameless_offset))[0] = is_frameless_hook;
}
return cef_window_create_top_level_original(delegate);
}

int cnt = -1;

typedef void CEF_CALLBACK (*add_child_view_t)(struct _cef_panel_t* self, struct _cef_view_t* view);
add_child_view_t CEF_CALLBACK add_child_view_original;
void CEF_CALLBACK add_child_view_hook(struct _cef_panel_t* self, struct _cef_view_t* view) {
Wh_Log(L"add_child_view_hook: %d", ++cnt);
// 0: Minimize, 1: Maximize, 2: Close, 3: Menu (removing this also prevents alt key from working)
if (cnt < 3) {
if (cte_settings.showcontrols == FALSE) {
return;
}
} else if (cte_settings.showmenu == FALSE) {
return;
}
add_child_view_original(self, view);
return;
}

typedef _cef_panel_t* CEF_EXPORT (*cef_panel_create_t)(void* delegate);
cef_panel_create_t CEF_EXPORT cef_panel_create_original;
_cef_panel_t* CEF_EXPORT cef_panel_create_hook(void* delegate) {
Wh_Log(L"cef_panel_create_hook");
_cef_panel_t* panel = cef_panel_create_original(delegate);
if (add_child_view_offset != NULL) {
add_child_view_original = ((add_child_view_t*)((char*)panel + add_child_view_offset))[0];
((add_child_view_t*)((char*)panel + add_child_view_offset))[0] = add_child_view_hook;
}
return panel;
}

typedef int (*cef_version_info_t)(int entry);

void LoadSettings() {
cte_settings.showframe = Wh_GetIntSetting(L"showframe");
cte_settings.showmenu = Wh_GetIntSetting(L"showmenu");
cte_settings.showcontrols = Wh_GetIntSetting(L"showcontrols");
}

int FindOffset(int major, int minor, cte_offset_t offsets[], int offsets_size) {
int prev_major = 90;
for (int i = 0; i < offsets_size; i++) {
if (major <= offsets[i].ver_major && major >= prev_major) {
if (offsets[i].ver_minor == ANY_MINOR ||
(minor == offsets[i].ver_minor && major == offsets[i].ver_major) // mandate exact major match here
) {
#ifdef _WIN64
return offsets[i].offset_x64;
#else
return offsets[i].offset_x86;
#endif
}
}
prev_major = offsets[i].ver_major;
}
if (major >= offsets[offsets_size - 1].ver_major) {
#ifdef _WIN64
return offsets[offsets_size - 1].offset_x64;
#else
return offsets[offsets_size - 1].offset_x86;
#endif
}
return NULL;
}

// The mod is being initialized, load settings, hook functions, and do other
// initialization stuff if required.
BOOL Wh_ModInit() {
#ifdef _WIN64
Wh_Log(L"Init - x86_64");
#else
Wh_Log(L"Init - x86");
#endif

LoadSettings();

// Check if this process is auxilliary process by checking if the arguments contain --type=
LPWSTR args = GetCommandLineW();
if (wcsstr(args, L"--type=") != NULL) {
Wh_Log(L"Auxilliary process detected, skipping");
return FALSE;
}

HMODULE cefModule = LoadLibrary(L"libcef.dll");
if (!cefModule) {
Wh_Log(L"Failed to load CEF!");
return FALSE;
}
cef_window_create_top_level_t cef_window_create_top_level =
(cef_window_create_top_level_t)GetProcAddress(cefModule,
"cef_window_create_top_level");
cef_panel_create_t cef_panel_create =
(cef_panel_create_t)GetProcAddress(cefModule, "cef_panel_create");
cef_version_info_t cef_version_info =
(cef_version_info_t)GetProcAddress(cefModule, "cef_version_info");

// Get CEF version
int major = cef_version_info(0);
int minor = cef_version_info(1);
Wh_Log(L"CEF v%d.%d.%d.%d (Chromium v%d.%d.%d.%d) Loaded",
major,
minor,
cef_version_info(2),
cef_version_info(3),
cef_version_info(4),
cef_version_info(5),
cef_version_info(6),
cef_version_info(7)
);

// Check if the app is Spotify
wchar_t exeName[MAX_PATH];
GetModuleFileName(NULL, exeName, MAX_PATH);
BOOL isSpotify = wcsstr(_wcsupr(exeName), L"SPOTIFY.EXE") != NULL;
if (isSpotify) {
Wh_Log(L"Spotify detected");
}

// Get appropriate offsets for current CEF version
is_frameless_offset = FindOffset(major, minor, is_frameless_offsets, ARRAYSIZE(is_frameless_offsets));
Wh_Log(L"is_frameless offset: %#x", is_frameless_offset);

if (isSpotify) {
add_child_view_offset = FindOffset(major, minor, add_child_view_offsets, ARRAYSIZE(add_child_view_offsets));
Wh_Log(L"add_child_view offset: %#x", add_child_view_offset);
}

if ((is_frameless_offset == NULL || !cte_settings.showframe) &&
(!isSpotify || add_child_view_offset == NULL || (cte_settings.showmenu && cte_settings.showcontrols))
) {
Wh_Log(L"Nothing to hook, exiting");
if (is_frameless_offset == NULL) {
Wh_Log(L"This version of CEF is not supported!");
}
return FALSE;
}

Wh_SetFunctionHook((void*)cef_window_create_top_level,
(void*)cef_window_create_top_level_hook,
(void**)&cef_window_create_top_level_original);
Wh_SetFunctionHook((void*)cef_panel_create, (void*)cef_panel_create_hook,
(void**)&cef_panel_create_original);
return TRUE;
}

// The mod is being unloaded, free all allocated resources.
void Wh_ModUninit() {
Wh_Log(L"Uninit");
}

// The mod setting were changed, reload them.
void Wh_ModSettingsChanged() {
LoadSettings();
}
Loading