Sandboxie/Sandboxie/apps/control/BorderGuard.cpp

642 lines
18 KiB
C++

/*
* Copyright 2004-2020 Sandboxie Holdings, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//---------------------------------------------------------------------------
// Border Guard
//---------------------------------------------------------------------------
#include "stdafx.h"
#include "resource.h"
#include "BorderGuard.h"
#include "Boxes.h"
#include "common/my_version.h"
//---------------------------------------------------------------------------
// Structures
//---------------------------------------------------------------------------
struct BoxBorderParms {
WCHAR boxname[BOXNAME_COUNT];
COLORREF color;
BOOL title;
int width;
};
//---------------------------------------------------------------------------
// Variables
//---------------------------------------------------------------------------
CBorderGuard *CBorderGuard::m_instance = NULL;
void *CBorderGuard::m_DwmIsCompositionEnabled = NULL;
void *CBorderGuard::m_DwmGetWindowAttribute = NULL;
//---------------------------------------------------------------------------
// Constructor
//---------------------------------------------------------------------------
CBorderGuard::CBorderGuard()
{
m_instance = this;
m_windows_8 = false;
HMODULE dwmapi = LoadLibrary(L"dwmapi.dll");
if (dwmapi) {
m_DwmIsCompositionEnabled =
GetProcAddress(dwmapi, "DwmIsCompositionEnabled");
if (m_DwmIsCompositionEnabled) {
m_DwmGetWindowAttribute =
GetProcAddress(dwmapi, "DwmGetWindowAttribute");
if (! m_DwmGetWindowAttribute)
m_DwmIsCompositionEnabled = NULL;
}
if (GetProcAddress(dwmapi, "DwmRenderGesture"))
m_windows_8 = true;
}
m_timer_id = NULL;
m_active_hwnd = NULL;
m_active_pid = 0;
m_border_hwnd = NULL;
m_border_brush = NULL;
m_border_brush_color = RGB(0,0,0);
m_border_visible = FALSE;
m_thumb_width = GetSystemMetrics(SM_CXHTHUMB);
m_thumb_height = GetSystemMetrics(SM_CYVTHUMB);
static const WCHAR *WindowClassName = SANDBOXIE_CONTROL L"BorderWindow";
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_GLOBALCLASS;
wc.lpfnWndProc = ::DefWindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = AfxGetInstanceHandle();
wc.hIcon = ::LoadIcon(wc.hInstance, L"AAAPPICON");
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = WindowClassName;
wc.hIconSm = NULL;
ATOM atom = RegisterClassEx(&wc);
if (! atom)
return;
m_border_hwnd = CreateWindowEx(
WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_NOACTIVATE
| WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
(LPCWSTR)atom, WindowClassName,
WS_POPUP | WS_CLIPSIBLINGS,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, NULL, NULL);
if (! m_border_hwnd)
return;
if (m_border_hwnd) {
SetLayeredWindowAttributes(m_border_hwnd, 0, 192, LWA_ALPHA);
::ShowWindow(m_border_hwnd, SW_HIDE);
}
RefreshConf();
}
//---------------------------------------------------------------------------
// Destructor
//---------------------------------------------------------------------------
CBorderGuard::~CBorderGuard()
{
if (m_border_hwnd) {
DestroyWindow(m_border_hwnd);
m_border_hwnd = NULL;
}
}
//---------------------------------------------------------------------------
// MyTimerProc
//---------------------------------------------------------------------------
void CBorderGuard::MyTimerProc(
HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
m_instance->Refresh();
}
//---------------------------------------------------------------------------
// Refresh
//---------------------------------------------------------------------------
void CBorderGuard::Refresh()
{
if ((! m_border_hwnd) || (! m_timer_id))
return;
//
// get current foreground window,
// and its associated process id and box name
//
HWND hwnd;
ULONG style;
ULONG pid = NULL;
WCHAR boxname[BOXNAME_COUNT];
boxname[0] = L'\0';
hwnd = GetForegroundWindow();
if (hwnd) {
style = GetWindowLong(hwnd, GWL_STYLE);
if (style & WS_VISIBLE) {
GetWindowThreadProcessId(hwnd, &pid);
const CBoxes &boxes = CBoxes::GetInstance();
const CBox &box = boxes.GetBoxByProcessId(pid);
wcscpy(boxname, box.GetName());
}
}
//
// foreground process is sandboxed
//
if (boxname[0]) {
//
// get the bounding rectangle for the foreground window
//
RECT rect;
bool doGetWindowRect = true;
if (m_DwmIsCompositionEnabled) {
typedef HRESULT (*P_DwmIsCompositionEnabled)(BOOL *enabled);
P_DwmIsCompositionEnabled pDwmIsCompositionEnabled =
(P_DwmIsCompositionEnabled)m_DwmIsCompositionEnabled;
BOOL dwmEnabled = FALSE;
HRESULT hr = pDwmIsCompositionEnabled(&dwmEnabled);
if (SUCCEEDED(hr) && dwmEnabled) {
typedef HRESULT (*P_DwmGetWindowAttribute)(
HWND hwnd, DWORD dwAttribute,
void *pvAttribute, DWORD cbAttribute);
P_DwmGetWindowAttribute pDwmGetWindowAttribute =
(P_DwmGetWindowAttribute)m_DwmGetWindowAttribute;
const ULONG DWMWA_EXTENDED_FRAME_BOUNDS = 9;
hr = pDwmGetWindowAttribute(
hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(RECT));
if (SUCCEEDED(hr))
doGetWindowRect = false;
}
}
if (doGetWindowRect)
GetWindowRect(hwnd, &rect);
//
// check if the border has to be refreshed
//
BOOL have_cursor = FALSE;
POINT cursor;
BOOL refresh = TRUE;
if (pid == m_active_pid && hwnd == m_active_hwnd) {
if (memcmp(&rect, &m_active_rect, sizeof(RECT)) == 0) {
//
// window rect is same as last recorded window rect
// but if we track title area for border then also
// check the cursor is still in the title area
//
refresh = FALSE;
if (m_title_mode) {
int title_mode = -1;
have_cursor = GetCursorPos(&cursor);
if (have_cursor && cursor.x >= m_title_rect.left
&& cursor.x <= m_title_rect.right
&& cursor.y >= m_title_rect.top
&& cursor.y <= m_title_rect.bottom)
title_mode = +1;
if (title_mode != m_title_mode)
refresh = TRUE;
}
} else {
if (! m_fast_timer_start_ticks)
m_timer_id = SetTimer(NULL, m_timer_id, 10, MyTimerProc);
m_fast_timer_start_ticks = GetTickCount();
}
}
//
// show the border if necessary
//
if (refresh) {
if (m_border_visible)
::ShowWindow(m_border_hwnd, SW_HIDE);
m_border_visible = FALSE;
m_active_hwnd = hwnd;
m_active_pid = pid;
memcpy(&m_active_rect, &rect, sizeof(RECT));
m_title_mode = 0;
RefreshBorder(hwnd, style, &rect, boxname);
}
//
// foreground process is not sandboxed: hide border
//
} else {
if (m_border_visible) {
::ShowWindow(m_border_hwnd, SW_HIDE);
m_border_visible = FALSE;
}
m_active_hwnd = NULL;
m_active_pid = 0;
}
//
// reprogram the timer if necessary
//
if (m_fast_timer_start_ticks) {
if (GetTickCount() - m_fast_timer_start_ticks >= 1000) {
m_fast_timer_start_ticks = 0;
m_timer_id = SetTimer(NULL, m_timer_id, 100, MyTimerProc);
}
}
}
//---------------------------------------------------------------------------
// RefreshBorder
//---------------------------------------------------------------------------
void CBorderGuard::RefreshBorder(
HWND hwnd, ULONG style, RECT *rect, const WCHAR *boxname)
{
//
// ignore very small windows
//
if (rect->right - rect->left <= 2 || rect->bottom - rect->top <= 2)
return;
//
//
//
BoxBorderParms *boxparm = NULL;
ULONG count = (ULONG)m_boxes.GetSize();
ULONG i = 0;
for (i = 0; i < count; ++i) {
boxparm = (BoxBorderParms *)m_boxes.GetAt(i);
if (wcscmp(boxparm->boxname, boxname) == 0)
break;
}
if (i == count)
return;
//
// get information about the screen containing the window.
// don't draw the color border if the window is full screen
// and also has no caption bar (i.e. a full screen app)
//
HMONITOR hmonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
if (! hmonitor)
return;
MONITORINFO monitor;
memzero(&monitor, sizeof(MONITORINFO));
monitor.cbSize = sizeof(MONITORINFO);
if (! GetMonitorInfo(hmonitor, &monitor))
return;
const RECT *desktop = &monitor.rcMonitor;
if (rect->left <= desktop->left && rect->top <= desktop->top &&
rect->right >= desktop->right && rect->bottom >= desktop->bottom &&
(style & WS_CAPTION) != WS_CAPTION) {
return;
}
//
// display the border only if the cursor is on the title bar area,
// when the corresponding border mode is enabled
//
if (boxparm->title) {
TITLEBARINFO tbinfo;
tbinfo.cbSize = sizeof(TITLEBARINFO);
GetTitleBarInfo(hwnd, &tbinfo);
memcpy(&m_title_rect, &tbinfo.rcTitleBar, sizeof(RECT));
#define t m_title_rect
#define w (*rect)
if (t.left < w.left || t.left > w.right ||
t.top < w.top || t.top > w.bottom ||
t.right < w.left || t.right > w.right ||
t.bottom < w.top || t.bottom > w.bottom ||
t.right - t.left <= m_thumb_width ||
t.bottom - t.top <= m_thumb_height) {
// if window has no title bar area, then calculate some area
m_title_rect.left = rect->left;
m_title_rect.top = rect->top;
m_title_rect.right = rect->right;
m_title_rect.bottom = rect->top + m_thumb_height * 2;
}
/*{WCHAR xxx[256];wsprintf(xxx, L"Window %08X Rect (%d,%d)-(%d,%d) title (%d,%d)-(%d,%d) cursor (%d,%d)\n",
hwnd, rect->left,rect->top,rect->right,rect->bottom,
m_title_rect.left,m_title_rect.top,m_title_rect.right,m_title_rect.bottom,
cursor.x,cursor.y);
OutputDebugString(xxx);}*/
#undef w
#undef t
m_title_rect.top -= 8;
if (m_title_rect.top < desktop->top)
m_title_rect.top = desktop->top;
m_title_mode = -1;
POINT cursor;
if (GetCursorPos(&cursor) && cursor.x >= m_title_rect.left
&& cursor.x <= m_title_rect.right
&& cursor.y >= m_title_rect.top
&& cursor.y <= m_title_rect.bottom)
m_title_mode = +1;
if (m_title_mode == -1)
return;
}
//
// replace background brush if necessary
//
if ((! m_border_brush) ||
boxparm->color != m_border_brush_color) {
HBRUSH hbr = CreateSolidBrush(boxparm->color);
SetClassLongPtr(m_border_hwnd, GCLP_HBRBACKGROUND, (LONG_PTR)hbr);
if (m_border_brush)
DeleteObject(m_border_brush);
m_border_brush = hbr;
m_border_brush_color = boxparm->color;
}
//
// get work area of the screen containing the window,
// then compute window bounds within the work area
//
desktop = &monitor.rcWork;
int ax = rect->left;
if (rect->left < desktop->left && (desktop->left - rect->left) < (boxparm->width + 4))
ax = desktop->left;
int ay = rect->top;
if (rect->top < desktop->top && (desktop->top - rect->top) < (boxparm->width + 4))
ay = desktop->top;
int aw = -ax;
if (rect->right > desktop->right && (desktop->right - rect->right) < (boxparm->width + 4))
aw += desktop->right;
else
aw += rect->right;
int ah = -ay;
if (rect->bottom > desktop->bottom && (desktop->bottom - rect->bottom) < (boxparm->width + 4))
ah += desktop->bottom;
else
ah += rect->bottom;
if (rect->bottom == desktop->bottom)
ah -= 1;
//int bb = 6;
//if (rect->left <= desktop->left &&
// rect->top <= desktop->top &&
// rect->right >= desktop->right &&
// rect->bottom >= desktop->bottom)
// bb = 4;
int bb = boxparm->width;
//
// don't display the border if any of it would be obscured by
// some other window that has the "always on top" attribute
//
HWND hwndShellTrayWnd = FindWindow(L"Shell_TrayWnd", NULL);
for (i = 0; i < 4; ++i) {
POINT testpt;
if (i == 0) {
testpt.x = ax;
testpt.y = ay;
} else if (i == 1) {
testpt.x = ax + aw;
testpt.y = ay;
} else if (i == 2) {
testpt.x = ax;
testpt.y = ay + ah;
} else if (i == 3) {
testpt.x = ax + aw;
testpt.y = ay + ah;
}
if (testpt.x >= desktop->right)
testpt.x = desktop->right - 1;
if (testpt.y >= desktop->bottom)
testpt.y = desktop->bottom - 1;
HWND testwnd = WindowFromPoint(testpt);
while (testwnd) {
if ( (testwnd == hwndShellTrayWnd) || (testwnd == hwnd) )
break;
ULONG exStyle = GetWindowLong(testwnd, GWL_EXSTYLE);
if (exStyle & WS_EX_TOPMOST) {
//
// Windows 8 places invisible "always on top" windows in
// the corners of the desktop, we should ignore those
//
bool foundTopmostWindow = true;
if (m_windows_8) {
WCHAR clsnm[64];
int n = GetClassName(testwnd, clsnm, 64);
if (n == 19 &&
_wcsicmp(clsnm, L"EdgeUiInputWndClass") == 0)
foundTopmostWindow = false;
}
if (foundTopmostWindow) {
m_active_hwnd = NULL;
m_active_pid = 0;
return;
}
}
testwnd = GetParent(testwnd);
}
}
//
// compute new region
//
POINT points[16];
int num_points = 0;
#define ADD_POINT(xx,yy) \
points[num_points].x = (xx); \
points[num_points].y = (yy); \
++num_points;
ADD_POINT(0, 0 );
ADD_POINT(aw, 0 );
ADD_POINT(aw, ah );
ADD_POINT(0, ah );
ADD_POINT(0, 0 );
ADD_POINT(0 + bb, 0 + bb );
ADD_POINT(aw - bb, 0 + bb );
ADD_POINT(aw - bb, ah - bb );
ADD_POINT(0 + bb, ah - bb );
ADD_POINT(0 + bb, 0 + bb );
#undef ADD_POINT
HRGN hrgn = CreatePolygonRgn(points, num_points, ALTERNATE);
SetWindowRgn(m_border_hwnd, hrgn, TRUE);
DeleteObject(hrgn);
SetWindowPos(m_border_hwnd, NULL, ax, ay, aw, ah,
SWP_SHOWWINDOW | SWP_NOACTIVATE);
m_border_visible = TRUE;
}
//---------------------------------------------------------------------------
// RefreshConf2
//---------------------------------------------------------------------------
void CBorderGuard::RefreshConf2()
{
/*if (GetSystemMetrics(SM_CXSCREEN) < 1000)
m_border_width = 2;
else
m_border_width = 4;*/
BoxBorderParms *boxparm;
int i;
while (m_boxes.GetSize()) {
boxparm = (BoxBorderParms *)m_boxes.GetAt(0);
delete boxparm;
m_boxes.RemoveAt(0);
}
CBoxes &boxes = CBoxes::GetInstance();
for (i = 1; i < boxes.GetSize(); ++i) {
CBox &box = boxes.GetBox(i);
if (! box.GetName().IsEmpty()) {
COLORREF color;
BOOL title;
int width;
BOOL enabled = box.GetBorder(&color, &title, &width);
if (enabled) {
boxparm = new BoxBorderParms;
wcscpy(boxparm->boxname, box.GetName());
boxparm->color = color;
boxparm->title = title;
boxparm->width = width;
m_boxes.Add(boxparm);
}
}
}
if (m_boxes.GetSize()) {
if (! m_timer_id) {
m_fast_timer_start_ticks = 0;
m_timer_id = SetTimer(NULL, 0, 100, MyTimerProc);
}
} else if (m_timer_id) {
KillTimer(NULL, m_timer_id);
m_timer_id = NULL;
}
}
//---------------------------------------------------------------------------
// RefreshConf
//---------------------------------------------------------------------------
void CBorderGuard::RefreshConf()
{
if (! m_instance)
m_instance = new CBorderGuard();
m_instance->RefreshConf2();
}