Skip to content
Open
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Chrome++ Next is a `version.dll` injection project for Google Chrome. It is load
- Keep the last tab from closing the browser window.
- Switch tabs with the mouse wheel over the tab strip.
- Switch tabs with the mouse wheel while holding the right mouse button.
- Activate a tab after hovering over it.
- Open omnibox input or bookmarks in a new tab.
- Control new-tab detection through `new_tab_disable` and `new_tab_disable_name`.

Expand Down
1 change: 1 addition & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Chrome++ Next 是一个 `version.dll` 注入项目,会与 `chrome.exe` 一同
- 保留最后一个标签页,避免关闭整个浏览器窗口。
- 鼠标悬停标签栏时滚轮切换标签页。
- 按住右键时滚轮切换标签页。
- 鼠标悬停标签页后自动激活该标签页。
- 将地址栏输入内容或书签在新标签页中打开。
- 通过 `new_tab_disable``new_tab_disable_name` 控制新标签页判定。

Expand Down
Binary file modified src/chrome++.ini
Binary file not shown.
17 changes: 17 additions & 0 deletions src/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ void Config::LoadConfig() {
wheel_tab_when_press_rbutton_ =
::GetPrivateProfileIntW(L"tabs", L"wheel_tab_when_press_rbutton", 1,
GetIniPath().c_str()) != 0;
hover_activate_tab_ =
::GetPrivateProfileIntW(L"tabs", L"hover_activate_tab", 0,
GetIniPath().c_str()) != 0;
hover_activate_tab_delay_ = LoadHoverActivateTabDelay();
open_url_new_tab_ = LoadOpenUrlNewTabMode();
bookmark_new_tab_ = LoadBookmarkNewTabMode();
new_tab_disable_ = ::GetPrivateProfileIntW(L"tabs", L"new_tab_disable", 1,
Expand Down Expand Up @@ -125,4 +129,17 @@ int Config::LoadBookmarkNewTabMode() {
GetIniPath().c_str());
}

unsigned int Config::LoadHoverActivateTabDelay() {
constexpr int kDefaultDelayMs = 200;
constexpr int kMaxDelayMs = 5000;
const int delay = ::GetPrivateProfileIntW(L"tabs",
L"hover_activate_tab_delay",
kDefaultDelayMs,
GetIniPath().c_str());
if (delay < 0 || delay > kMaxDelayMs) {
return kDefaultDelayMs;
}
return static_cast<unsigned int>(delay);
}

const Config& config = Config::Instance();
7 changes: 7 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class Config {
bool IsWheelTabWhenPressRightButton() const {
return wheel_tab_when_press_rbutton_;
}
bool IsHoverActivateTab() const { return hover_activate_tab_; }
unsigned int GetHoverActivateTabDelay() const {
return hover_activate_tab_delay_;
}
int GetOpenUrlNewTabMode() const { return open_url_new_tab_; }
int GetBookmarkNewTabMode() const { return bookmark_new_tab_; }
bool IsNewTabDisable() const { return new_tab_disable_; }
Expand All @@ -61,6 +65,7 @@ class Config {
std::optional<std::wstring> LoadDirPath(const std::wstring& dir_type);
int LoadOpenUrlNewTabMode();
int LoadBookmarkNewTabMode();
unsigned int LoadHoverActivateTabDelay();

private:
// general
Expand All @@ -82,6 +87,8 @@ class Config {
bool right_click_close_;
bool wheel_tab_;
bool wheel_tab_when_press_rbutton_;
bool hover_activate_tab_;
unsigned int hover_activate_tab_delay_;
int open_url_new_tab_;
int bookmark_new_tab_;
bool new_tab_disable_;
Expand Down
2 changes: 1 addition & 1 deletion src/inputhook.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
return CallNextHookEx(mouse_hook, nCode, wParam, lParam);
}

if (wParam == WM_MOUSEMOVE || wParam == WM_NCMOUSEMOVE) {
if (wParam == WM_NCMOUSEMOVE) {
return CallNextHookEx(mouse_hook, nCode, wParam, lParam);
}

Expand Down
119 changes: 119 additions & 0 deletions src/tabbookmark.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,16 @@
namespace {

constexpr UINT kDefaultDpi = 96;
constexpr UINT_PTR kHoverActivateTabTimerId = 0x1603ABDA;
POINT lbutton_down_point = {-1, -1};

struct HoverActivateTabState {
HWND hwnd = nullptr;
RECT tab_rect = {};
};

HoverActivateTabState hover_activate_tab_state;

enum class KeepTabTrigger {
kRightClick = 0,
kMiddleClick,
Expand Down Expand Up @@ -42,6 +50,111 @@ bool IsNeedKeep(int tab_count, KeepTabTrigger trigger) {
return keep_tab;
}

bool IsAnyMouseButtonPressed() {
return IsKeyPressed(VK_LBUTTON) || IsKeyPressed(VK_RBUTTON) ||
IsKeyPressed(VK_MBUTTON);
}

HWND GetChromeRootAtPoint(POINT pt) {
const HWND hwnd = WindowFromPoint(pt);
const HWND root = hwnd ? GetAncestor(hwnd, GA_ROOT) : nullptr;
if (!root || !IsChromeWindow(root)) {
return nullptr;
}
return root;
}

bool IsSameHoverTarget(HWND hwnd, const RECT& tab_rect) {
return hover_activate_tab_state.hwnd == hwnd &&
EqualRect(&hover_activate_tab_state.tab_rect, &tab_rect);
}

void CancelHoverActivateTabTimer() {
if (hover_activate_tab_state.hwnd) {
KillTimer(hover_activate_tab_state.hwnd, kHoverActivateTabTimerId);
}
hover_activate_tab_state = {};
}

void ActivateHoveredTabIfStable(HWND hwnd) {
if (!hover_activate_tab_state.hwnd ||
hover_activate_tab_state.hwnd != hwnd) {
CancelHoverActivateTabTimer();
return;
}

POINT pt = {};
if (IsAnyMouseButtonPressed() || !GetCursorPos(&pt) ||
GetChromeRootAtPoint(pt) != hwnd ||
!PtInRect(&hover_activate_tab_state.tab_rect, pt)) {
CancelHoverActivateTabTimer();
return;
}

const auto hit = FindTabHitResult(pt, false, true);
if (!hit || hit->on_close_button ||
!EqualRect(&hit->tab_rect, &hover_activate_tab_state.tab_rect)) {
CancelHoverActivateTabTimer();
return;
}

SelectTab(*hit);
CancelHoverActivateTabTimer();
}

void CALLBACK HoverActivateTabTimerProc(HWND hwnd,
UINT,
UINT_PTR event_id,
DWORD) {
if (event_id != kHoverActivateTabTimerId) {
return;
}
ActivateHoveredTabIfStable(hwnd);
}

void ArmHoverActivateTabTimer(HWND hwnd, const RECT& tab_rect) {
if (IsSameHoverTarget(hwnd, tab_rect)) {
return;
}

CancelHoverActivateTabTimer();
hover_activate_tab_state.hwnd = hwnd;
hover_activate_tab_state.tab_rect = tab_rect;

const UINT delay = config.GetHoverActivateTabDelay();
if (delay == 0) {
ActivateHoveredTabIfStable(hwnd);
return;
}

if (!SetTimer(hwnd, kHoverActivateTabTimerId, delay,
HoverActivateTabTimerProc)) {
hover_activate_tab_state = {};
}
}

void HandleHoverActivateTab(const MOUSEHOOKSTRUCT* pmouse) {
if (!config.IsHoverActivateTab() || IsAnyMouseButtonPressed()) {
CancelHoverActivateTabTimer();
return;
}

const POINT pt = pmouse->pt;
const HWND root = GetChromeRootAtPoint(pt);
if (!root) {
CancelHoverActivateTabTimer();
return;
}

const auto hit = FindTabHitResult(pt, false, true);
if (!hit || hit->on_close_button) {
CancelHoverActivateTabTimer();
return;
}

ArmHoverActivateTabTimer(root, hit->tab_rect);
}

// Use the mouse wheel to switch tabs
bool HandleMouseWheel(LPARAM lParam, const MOUSEHOOKSTRUCT* pmouse) {
if (!config.IsWheelTab() && !config.IsWheelTabWhenPressRightButton()) {
Expand Down Expand Up @@ -240,15 +353,21 @@ bool TabBookmarkMouseHandler(WPARAM wParam, LPARAM lParam) {
static bool closing_tab_by_right = false;

switch (wParam) {
case WM_MOUSEMOVE:
HandleHoverActivateTab(pmouse);
return false;
case WM_LBUTTONDOWN:
CancelHoverActivateTabTimer();
// Simply record the position of `LBUTTONDOWN` for drag detection
closing_tab_by_dblclk = false;
lbutton_down_point = pmouse->pt;
return false;
case WM_MBUTTONDOWN:
CancelHoverActivateTabTimer();
closing_tab_by_middle = false;
return false;
case WM_RBUTTONDOWN:
CancelHoverActivateTabTimer();
closing_tab_by_right = false;
return false;
case WM_LBUTTONUP:
Expand Down
37 changes: 36 additions & 1 deletion src/uia.cc
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ ComPtr<IUIAutomationElementArray> FindTabElements(

ComPtr<IUIAutomationElement> FindTabElementAtPoint(
const ComPtr<IUIAutomationElementArray>& tab_elements,
RECT* tab_rect,
POINT pt) {
if (!tab_elements) {
return nullptr;
Expand All @@ -639,6 +640,9 @@ ComPtr<IUIAutomationElement> FindTabElementAtPoint(
continue;
}
if (PtInRect(&rect, pt)) {
if (tab_rect) {
*tab_rect = rect;
}
return tab_element;
}
}
Expand Down Expand Up @@ -710,7 +714,8 @@ std::optional<TabHitResult> BuildTabHitResult(const UiaSession& session,
return std::nullopt;
}

const auto tab_element = FindTabElementAtPoint(tab_elements, pt);
RECT tab_rect = {};
const auto tab_element = FindTabElementAtPoint(tab_elements, &tab_rect, pt);
if (!tab_element) {
return std::nullopt;
}
Expand All @@ -728,6 +733,7 @@ std::optional<TabHitResult> BuildTabHitResult(const UiaSession& session,

TabHitResult hit_result;
hit_result.tab = tab_element;
hit_result.tab_rect = tab_rect;
hit_result.tab_count = need_count ? tab_count : 0;
hit_result.on_close_button =
need_close_button && IsOnTabCloseButton(session, tab_element, pt);
Expand Down Expand Up @@ -867,6 +873,35 @@ std::optional<TabHitResult> FindTabHitResult(POINT pt,
need_close_button);
}

bool SelectTab(const TabHitResult& hit_result) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个思路不错,值得留着。

if (!hit_result.tab) {
return false;
}

ComPtr<IUnknown> pattern;
HRESULT hr = hit_result.tab->GetCurrentPattern(
UIA_SelectionItemPatternId, pattern.ReleaseAndGetAddressOf());
if (FAILED(hr) || !pattern) {
DebugLog(L"UIA: tab selection item pattern unavailable");
return false;
}

ComPtr<IUIAutomationSelectionItemPattern> selection_item;
hr = pattern->QueryInterface(
IID_PPV_ARGS(selection_item.ReleaseAndGetAddressOf()));
if (FAILED(hr) || !selection_item) {
DebugLog(L"UIA: tab selection item QueryInterface failed");
return false;
}

hr = selection_item->Select();
if (FAILED(hr)) {
DebugLog(L"UIA: tab Select failed");
return false;
}
return true;
}

std::optional<int> FindTabCount(HWND hwnd) {
const UiaSession* session = GetUiaSession();
if (!session) {
Expand Down
2 changes: 2 additions & 0 deletions src/uia.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@

struct TabHitResult {
Microsoft::WRL::ComPtr<IUIAutomationElement> tab;
RECT tab_rect = {};
int tab_count = 0;
bool on_close_button = false;
};

[[nodiscard]] std::optional<TabHitResult>
FindTabHitResult(POINT pt, bool need_count, bool need_close_button);
[[nodiscard]] bool SelectTab(const TabHitResult& hit_result);
[[nodiscard]] std::optional<int> FindTabCount(HWND hwnd);
[[nodiscard]] bool IsOnTabBar(POINT pt);
[[nodiscard]] bool IsOnBookmark(POINT pt);
Expand Down