Skip to content
Draft
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 CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ if(WIN32)
option(LAF_WITH_IME "Enable IME for CJK input" OFF)
option(LAF_WITH_DLGS_PROC "Enable laf-dlgs-process.exe for Windows to open the FileDialog through an external process" OFF)
set(LAF_DLGS_PROC_NAME "laf-dlgs-process" CACHE STRING "Name of the process to show native dialogs")
option(LAF_WITH_WINRT "Enable C++/WinRT usage" ON)
endif()
set(LAF_BACKEND ${LAF_DEFAULT_BACKEND} CACHE STRING "Select laf backend")
set_property(CACHE LAF_BACKEND PROPERTY STRINGS "none" "skia")
Expand Down
5 changes: 5 additions & 0 deletions base/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ if(WIN32)
endif()

target_link_libraries(laf-base dbghelp shlwapi version)

if (LAF_WITH_WINRT)
target_compile_definitions(laf-base PUBLIC LAF_WINRT)
target_link_libraries(laf-base WindowsApp)
endif()
else()
if(APPLE)
target_compile_options(laf-base PRIVATE -fobjc-arc)
Expand Down
149 changes: 132 additions & 17 deletions os/win/window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,68 @@
#define INTERACTION_CONTEXT_PROPERTY_INTERACTION_UI_FEEDBACK_OFF 0
#endif

#if LAF_WINRT
#include <DispatcherQueue.h>
#include <Windows.Graphics.Display.Interop.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Graphics.Display.h>

namespace {
using namespace winrt::Windows::Graphics::Display;

winrt::com_ptr<ABI::Windows::System::IDispatcherQueueController> g_dispatcher_queue_controller;
bool g_winrt_error = false;

// Creates a WinRT DispatcherQueueController which is neccesary to recieve the ColorProfileChanged
// event, and then registers the event to the given HWND. Calling this twice will just register it
// twice, if we need to unregister it for some reason, we'll have to store the winrt::event_token
// and use that, or we can delete the dispatcher queue controller.
static bool color_profile_changed(HWND hwnd, std::function<void()> callback)
{
try {
if (!g_dispatcher_queue_controller) {
DispatcherQueueOptions options{ sizeof(DispatcherQueueOptions),
DQTYPE_THREAD_CURRENT,
DQTAT_COM_STA };

winrt::check_hresult(
CreateDispatcherQueueController(options, g_dispatcher_queue_controller.put()));
}

auto factory =
winrt::get_activation_factory<DisplayInformation, IDisplayInformationStaticsInterop>();

DisplayInformation displayInfo{ nullptr };
winrt::check_hresult(factory->GetForWindow(hwnd,
winrt::guid_of<DisplayInformation>(),
winrt::put_abi(displayInfo)));

displayInfo.ColorProfileChanged([callback](const auto&, const auto&) { callback(); });

return true;
}
catch (...) {
LOG(ERROR, "OS: Could not hook to color profile event\n");

// Any error here is more than likely because we're in an unsupported environment (Windows 10)
// so we just set a global flag to avoid re-attempting this.
g_winrt_error = true;

// Get rid of the controller if we managed to create it but the other calls failed
if (g_dispatcher_queue_controller) {
ABI::Windows::Foundation::IAsyncAction* action;
if (g_dispatcher_queue_controller->ShutdownQueueAsync(&action) == S_OK) {
action->GetResults();
action->Release();
}
}

return false;
}
}
} // namespace
#endif

namespace os {

// Converts an os::Hit to a Win32 hit test value
Expand Down Expand Up @@ -183,6 +245,7 @@ WindowWin::WindowWin(const WindowSpec& spec)
, m_titled(spec.titled())
, m_borderless(spec.borderless())
, m_fixingPos(false)
, m_isReceivingColorSpaceEvents(false)
{
auto& winApi = system()->winApi();
if (
Expand Down Expand Up @@ -345,31 +408,48 @@ os::ScreenRef WindowWin::screen() const
return os::System::instance()->primaryScreen();
}

os::ColorSpaceRef WindowWin::colorSpace() const
std::string WindowWin::readIcc() const
{
if (auto cs = Window::colorSpace())
return cs;

std::string iccFilename;
if (m_hwnd) {
HMONITOR monitor = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST);
std::string iccFilename = get_hmonitor_icc_filename(monitor);
if (m_lastICCProfile != iccFilename) {
m_lastICCProfile = iccFilename;
if (!iccFilename.empty())
m_lastColorProfile = get_colorspace_from_icc_file(iccFilename);
}
iccFilename = get_hmonitor_icc_filename(monitor);
}

return iccFilename;
}

os::ColorSpaceRef WindowWin::readColorSpace() const
{
std::string icc = readIcc();
if (!icc.empty())
return get_colorspace_from_icc_file(icc);

// sRGB by default
if (auto system = os::System::instance())
return system->makeColorSpace(gfx::ColorSpace::MakeSRGB());

return nullptr;
}

os::ColorSpaceRef WindowWin::colorSpace() const
{
if (auto cs = Window::colorSpace())
return cs;

if (!m_lastColorProfile) {
if (auto system = os::System::instance())
m_lastColorProfile = system->makeColorSpace(gfx::ColorSpace::MakeSRGB());
m_lastICCProfile = readIcc();
m_lastColorProfile = readColorSpace();
}

return m_lastColorProfile;
}

void WindowWin::setScale(int scale)
{
if (m_scale == scale)
return;

m_scale = scale;

// Align window size to new scale
Expand Down Expand Up @@ -1177,7 +1257,20 @@ LRESULT WindowWin::wndProc(UINT msg, WPARAM wparam, LPARAM lparam)
break;

case WM_SETTINGCHANGE:
checkDarkModeChange();
if (wparam == SPI_SETWORKAREA)
checkColorSpaceChange();

if (!wparam && lparam &&
CompareStringOrdinal(reinterpret_cast<LPCWSTR>(lparam),
-1,
L"ImmersiveColorSet",
-1,
TRUE) == CSTR_EQUAL)
checkDarkModeChange();
break;

case WM_DISPLAYCHANGE:
checkColorSpaceChange();
break;

// Mouse and Trackpad Messages
Expand Down Expand Up @@ -2397,13 +2490,35 @@ void WindowWin::killTouchTimer()
}
}

void WindowWin::switchColorSpace()
{
ASSERT(m_lastColorProfile);

auto newIcc = readIcc();
if (m_lastICCProfile == newIcc)
return;

auto newColorProfile = readColorSpace();

// Notify only when they're meaningfully different
if (!newColorProfile->gfxColorSpace()->nearlyEqual(*m_lastColorProfile->gfxColorSpace()))
onChangeColorSpace();

m_lastICCProfile = newIcc;
m_lastColorProfile = newColorProfile;
}

void WindowWin::checkColorSpaceChange()
{
// TODO Compare if CS are different.
// os::ColorSpaceRef oldCS = m_lastColorProfile;
// os::ColorSpaceRef newCS = colorSpace();
#if LAF_WINRT
if (m_isReceivingColorSpaceEvents)
return;

if (!g_winrt_error)
m_isReceivingColorSpaceEvents = color_profile_changed(m_hwnd, [&] { switchColorSpace(); });
#endif

onChangeColorSpace();
switchColorSpace();
}

// We only support the Windows 11 dark mode, detecting it and using
Expand Down
6 changes: 6 additions & 0 deletions os/win/window.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,11 @@ class WindowWin : public Window {
void sendDelayedTouchEvents();
void clearDelayedTouchEvents();
void killTouchTimer();
void switchColorSpace();
void checkColorSpaceChange();
void checkDarkModeChange();
std::string readIcc() const;
os::ColorSpaceRef readColorSpace() const;

void openWintabCtx();
void closeWintabCtx();
Expand Down Expand Up @@ -228,6 +231,9 @@ class WindowWin : public Window {
Touch();
}* m_touch;

// Whether we are using WinRT to receive color space events, to avoid checking manually.
bool m_isReceivingColorSpaceEvents;

#if OS_USE_POINTER_API_FOR_MOUSE
// Emulate double-click with pointer API. I guess that this should
// be done by the Interaction Context API but it looks like
Expand Down