mirror of
https://github.com/microsoft/terminal.git
synced 2026-02-04 05:35:20 +00:00
416 lines
17 KiB
C++
416 lines
17 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "MyPage.h"
|
|
#include <LibraryResources.h>
|
|
#include "MyPage.g.cpp"
|
|
#include "MySettings.h"
|
|
|
|
#include <Shlwapi.h> // For PathCombine function
|
|
#include <wincodec.h> // Windows Imaging Component
|
|
#include <Shlobj.h>
|
|
#include <Shlobj_core.h>
|
|
#include <wincodec.h>
|
|
#include <propkey.h> // For PKEY_AppUserModel_ID
|
|
#include <propsys.h> // For IPropertyStore and related functions
|
|
|
|
#include "..\..\..\src\types\inc\utils.hpp"
|
|
|
|
using namespace std::chrono_literals;
|
|
using namespace winrt::Microsoft::Terminal;
|
|
|
|
namespace winrt
|
|
{
|
|
namespace MUX = Microsoft::UI::Xaml;
|
|
namespace WUX = Windows::UI::Xaml;
|
|
using IInspectable = Windows::Foundation::IInspectable;
|
|
}
|
|
|
|
struct __declspec(uuid("5b0d3235-4dba-4d44-865e-8f1d0e4fd04d")) __declspec(novtable) IMemoryBufferByteAccess : ::IUnknown
|
|
{
|
|
virtual HRESULT __stdcall GetBuffer(uint8_t** value, uint32_t* capacity) = 0;
|
|
};
|
|
|
|
namespace winrt::SampleApp::implementation
|
|
{
|
|
MyPage::MyPage()
|
|
{
|
|
// Well this immediately fucks up loading an image using
|
|
// `GetFileFromPathAsync`. I'd bet it fucks up all sorts of things.
|
|
|
|
// // instantiate a random guid
|
|
// winrt::guid g{ ::Microsoft::Console::Utils::CreateGuid() };
|
|
// auto str{ ::Microsoft::Console::Utils::GuidToString(g) };
|
|
|
|
// SetCurrentProcessExplicitAppUserModelID(str.c_str());
|
|
|
|
InitializeComponent();
|
|
}
|
|
|
|
void MyPage::Create(uint64_t hwnd)
|
|
{
|
|
_hwnd = reinterpret_cast<HWND>(hwnd);
|
|
auto settings = winrt::make_self<implementation::MySettings>();
|
|
|
|
auto connectionSettings{ TerminalConnection::ConptyConnection::CreateSettings(L"cmd.exe /k echo This TermControl is hosted in-proc...",
|
|
winrt::hstring{},
|
|
L"",
|
|
nullptr,
|
|
32,
|
|
80,
|
|
winrt::guid(),
|
|
winrt::guid()) };
|
|
|
|
// "Microsoft.Terminal.TerminalConnection.ConptyConnection"
|
|
winrt::hstring myClass{ winrt::name_of<TerminalConnection::ConptyConnection>() };
|
|
TerminalConnection::ConnectionInformation connectInfo{ myClass, connectionSettings };
|
|
|
|
TerminalConnection::ITerminalConnection conn{ TerminalConnection::ConnectionInformation::CreateConnection(connectInfo) };
|
|
Control::TermControl control{ *settings, *settings, conn };
|
|
|
|
InProcContent().Children().Append(control);
|
|
|
|
PathInput().Text(L"d:\\dev\\private\\OpenConsole\\res\\terminal.ico");
|
|
}
|
|
|
|
// Method Description:
|
|
// - Gets the title of the currently focused terminal control. If there
|
|
// isn't a control selected for any reason, returns "Windows Terminal"
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - the title of the focused control if there is one, else "Windows Terminal"
|
|
hstring MyPage::Title()
|
|
{
|
|
return { L"Sample Application" };
|
|
}
|
|
|
|
void MyPage::_attemptOne(const winrt::hstring& text)
|
|
{
|
|
IWICImagingFactory* pFactory = nullptr;
|
|
IWICBitmapDecoder* pDecoder = nullptr;
|
|
IWICBitmapFrameDecode* pFrame = nullptr;
|
|
IWICFormatConverter* pConverter = nullptr;
|
|
|
|
HICON hIcon = nullptr;
|
|
|
|
// Create WIC Imaging Factory
|
|
CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFactory));
|
|
|
|
// Load the image from the URI
|
|
HRESULT hr = pFactory->CreateDecoderFromFilename(text.c_str(), nullptr, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &pDecoder);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pDecoder->GetFrame(0, &pFrame);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Convert the image format to a compatible format for icons (e.g., 32bppBGRA)
|
|
hr = pFactory->CreateFormatConverter(&pConverter);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pConverter->Initialize(pFrame, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, nullptr, 0.0f, WICBitmapPaletteTypeCustom);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Get the image dimensions
|
|
UINT width, height;
|
|
pFrame->GetSize(&width, &height);
|
|
|
|
// Create a DIB section to hold the image data
|
|
BITMAPINFO bmi = {};
|
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi.bmiHeader.biWidth = width;
|
|
bmi.bmiHeader.biHeight = -static_cast<LONG>(height); // Negative height indicates top-down bitmap
|
|
bmi.bmiHeader.biPlanes = 1;
|
|
bmi.bmiHeader.biBitCount = 32; // 32bpp for ARGB format
|
|
bmi.bmiHeader.biCompression = BI_RGB;
|
|
|
|
void* pBits = nullptr;
|
|
HBITMAP hBitmap = CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0);
|
|
|
|
if (hBitmap && pBits)
|
|
{
|
|
// Copy the converted image data into the DIB section
|
|
hr = pConverter->CopyPixels(nullptr, width * 4, width * height * 4, static_cast<BYTE*>(pBits));
|
|
|
|
// Create an icon from the DIB section
|
|
ICONINFO iconInfo = {};
|
|
iconInfo.fIcon = TRUE;
|
|
// iconInfo.hbmMask = nullptr; // No mask is required for icons
|
|
iconInfo.hbmMask = CreateBitmap(width, height, 1, 1, 0); // ^ that was a fuckin lie
|
|
iconInfo.hbmColor = hBitmap;
|
|
|
|
hIcon = CreateIconIndirect(&iconInfo);
|
|
|
|
// get last error if it failed
|
|
if (!hIcon)
|
|
{
|
|
auto gle = GetLastError();
|
|
hr = HRESULT_FROM_WIN32(gle);
|
|
}
|
|
|
|
ITaskbarList3* pTaskbarList = nullptr;
|
|
HRESULT hr = CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pTaskbarList));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Set the overlay icon
|
|
hr = pTaskbarList->SetOverlayIcon(_hwnd, hIcon, L"Overlay Icon Description");
|
|
|
|
DestroyIcon(hIcon); // Release the icon
|
|
|
|
pTaskbarList->Release();
|
|
}
|
|
|
|
DeleteObject(hBitmap); // The HICON owns the bitmap now
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Release COM interfaces
|
|
if (pConverter)
|
|
pConverter->Release();
|
|
if (pFrame)
|
|
pFrame->Release();
|
|
if (pDecoder)
|
|
pDecoder->Release();
|
|
if (pFactory)
|
|
pFactory->Release();
|
|
}
|
|
|
|
HICON ConvertSoftwareBitmapToHICON(winrt::Windows::Graphics::Imaging::SoftwareBitmap softwareBitmap)
|
|
{
|
|
// Get the dimensions of the SoftwareBitmap
|
|
int width = softwareBitmap.PixelWidth();
|
|
int height = softwareBitmap.PixelHeight();
|
|
|
|
// Get the pixel data from the SoftwareBitmap
|
|
winrt::Windows::Graphics::Imaging::BitmapBuffer bitmapBuffer = softwareBitmap.LockBuffer(
|
|
winrt::Windows::Graphics::Imaging::BitmapBufferAccessMode::Read);
|
|
winrt::Windows::Foundation::IMemoryBufferReference reference = bitmapBuffer.CreateReference();
|
|
|
|
// winrt::Windows::Foundation::IMemoryBufferByteAccess byteAccess;
|
|
// IMemoryBufferByteAccess byteAccess;
|
|
auto byteAccess = reference.as<IMemoryBufferByteAccess>();
|
|
byte* pixelData = nullptr;
|
|
uint32_t capacity = 0;
|
|
byteAccess->GetBuffer(&pixelData, &capacity);
|
|
|
|
// Create an HBITMAP using CreateDIBSection
|
|
BITMAPINFO bmi = {};
|
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi.bmiHeader.biWidth = width;
|
|
bmi.bmiHeader.biHeight = -height; // Negative height indicates top-down bitmap
|
|
bmi.bmiHeader.biPlanes = 1;
|
|
bmi.bmiHeader.biBitCount = 32; // Assuming 32bpp RGBA format
|
|
bmi.bmiHeader.biCompression = BI_RGB;
|
|
|
|
void* pBits = nullptr;
|
|
HBITMAP hBitmap = CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0);
|
|
|
|
if (hBitmap && pBits)
|
|
{
|
|
// Copy pixel data to the HBITMAP
|
|
memcpy(pBits, pixelData, width * height * 4); // Assuming 32bpp RGBA format
|
|
}
|
|
|
|
ICONINFO iconInfo = {};
|
|
iconInfo.fIcon = TRUE;
|
|
// iconInfo.hbmMask = nullptr; // No mask is required for icons
|
|
iconInfo.hbmMask = CreateBitmap(width, height, 1, 1, 0); // ^ that was a fuckin lie
|
|
// iconInfo.hbmMask = CreateBitmap(64, 64, 1, 1, 0); // ^ that was a fuckin lie
|
|
iconInfo.hbmColor = hBitmap;
|
|
|
|
HICON hIcon = CreateIconIndirect(&iconInfo);
|
|
|
|
// get last error if it failed
|
|
if (!hIcon)
|
|
{
|
|
auto gle = GetLastError();
|
|
auto hr = HRESULT_FROM_WIN32(gle);
|
|
LOG_IF_FAILED(hr);
|
|
}
|
|
|
|
DeleteObject(hBitmap);
|
|
|
|
return hIcon;
|
|
}
|
|
|
|
void MyPage::_setTaskbarBadge(HICON hIcon)
|
|
{
|
|
ITaskbarList3* pTaskbarList = nullptr;
|
|
HRESULT hr = CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pTaskbarList));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Set the overlay icon
|
|
hr = pTaskbarList->SetOverlayIcon(_hwnd, hIcon, L"Overlay Icon Description");
|
|
|
|
DestroyIcon(hIcon); // Release the icon
|
|
|
|
pTaskbarList->Release();
|
|
}
|
|
}
|
|
|
|
void MyPage::_setTaskbarIcon(HICON hIcon)
|
|
{
|
|
SendMessageW(_hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
|
|
SendMessageW(_hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Attempt to get the icon index from the icon path provided
|
|
// Arguments:
|
|
// - iconPath: the full icon path, including the index if present
|
|
// - iconPathWithoutIndex: the place to store the icon path, sans the index if present
|
|
// Return Value:
|
|
// - nullopt if the iconPath is not an exe/dll/lnk file in the first place
|
|
// - 0 if the iconPath is an exe/dll/lnk file but does not contain an index (i.e. we default
|
|
// to the first icon in the file)
|
|
// - the icon index if the iconPath is an exe/dll/lnk file and contains an index
|
|
std::optional<int> _getIconIndex(const winrt::hstring& iconPath, std::wstring_view& iconPathWithoutIndex)
|
|
{
|
|
const auto pathView = std::wstring_view{ iconPath };
|
|
// Does iconPath have a comma in it? If so, split the string on the
|
|
// comma and look for the index and extension.
|
|
const auto commaIndex = pathView.find(L',');
|
|
|
|
// split the path on the comma
|
|
iconPathWithoutIndex = pathView.substr(0, commaIndex);
|
|
|
|
// It's an exe, dll, or lnk, so we need to extract the icon from the file.
|
|
if (!til::ends_with(iconPathWithoutIndex, L".exe") &&
|
|
!til::ends_with(iconPathWithoutIndex, L".dll") &&
|
|
!til::ends_with(iconPathWithoutIndex, L".lnk"))
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (commaIndex != std::wstring::npos)
|
|
{
|
|
// Convert the string iconIndex to a signed int to support negative numbers which represent an Icon's ID.
|
|
const auto index{ til::to_int(pathView.substr(commaIndex + 1)) };
|
|
if (index == til::to_int_error)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
return static_cast<int>(index);
|
|
}
|
|
|
|
// We had a binary path, but no index. Default to 0.
|
|
return 0;
|
|
}
|
|
|
|
winrt::fire_and_forget MyPage::_attemptTwo(winrt::hstring path)
|
|
{
|
|
// First things first:
|
|
// Is the path a path to an exe, a dll, or a resource in one of those files?
|
|
// If so, then we can use the icon from that file, without so much rigamarole.
|
|
|
|
std::wstring_view iconPathWithoutIndex;
|
|
const auto indexOpt = _getIconIndex(path, iconPathWithoutIndex);
|
|
if (indexOpt.has_value())
|
|
{
|
|
// Here, we know we have a path to an exe, dll, or resource in one of those files.
|
|
auto iconSize = 32;
|
|
HICON hIcon = nullptr;
|
|
winrt::hstring iconPath{ iconPathWithoutIndex };
|
|
|
|
LOG_IF_FAILED(SHDefExtractIcon(iconPath.c_str(),
|
|
indexOpt.value(),
|
|
0,
|
|
&hIcon,
|
|
nullptr,
|
|
iconSize));
|
|
if (hIcon)
|
|
{
|
|
_setTaskbarBadge(hIcon);
|
|
}
|
|
co_return;
|
|
}
|
|
|
|
// If not, then we'll have to do the rigamarole.
|
|
try
|
|
{
|
|
// Create a URI from the path
|
|
const Windows::Foundation::Uri uri{ path };
|
|
|
|
winrt::Windows::Storage::IStorageFile file{ nullptr };
|
|
|
|
// Is the URI a ms-appx URI? then load it from the app package
|
|
if (uri.SchemeName() == L"ms-appx")
|
|
{
|
|
file = co_await winrt::Windows::Storage::StorageFile::GetFileFromApplicationUriAsync(uri);
|
|
}
|
|
// // Is it a web URI? then download it else if (uri.SchemeName() ==
|
|
// L"http" || uri.SchemeName() == L"https")
|
|
// {
|
|
// auto downloader = winrt::Windows::Networking::BackgroundTransfer::BackgroundDownloader();
|
|
// auto download = downloader.CreateDownload(uri, nullptr);
|
|
// auto downloadOperation = download.StartAsync();
|
|
// co_await downloadOperation;
|
|
// file = download.ResultFile();
|
|
// }
|
|
//
|
|
// Actually, don't do anything for web URIs. BackgroundDownloader is not
|
|
// supported outside of packaged apps, but I think that also extends to
|
|
// centennial apps. Useless.
|
|
//
|
|
|
|
else
|
|
{
|
|
// Open the file, and load it into a SoftwareBitmap
|
|
file = co_await winrt::Windows::Storage::StorageFile::GetFileFromPathAsync(path);
|
|
}
|
|
|
|
// Get the software bitmap out of the file
|
|
auto stream = co_await file.OpenAsync(winrt::Windows::Storage::FileAccessMode::Read);
|
|
auto decoder = co_await winrt::Windows::Graphics::Imaging::BitmapDecoder::CreateAsync(stream);
|
|
auto softwareBitmap = co_await decoder.GetSoftwareBitmapAsync();
|
|
|
|
// Convert the SoftwareBitmap to an HBITMAP, using Windows Imaging Component
|
|
// auto hBitmap = ConvertSoftwareBitmapToHBITMAP(softwareBitmap);
|
|
// auto hIcon = _convertBitmapToHICON(hBitmap);
|
|
auto hIcon = ConvertSoftwareBitmapToHICON(softwareBitmap);
|
|
|
|
_setTaskbarIcon(hIcon);
|
|
_setTaskbarBadge(hIcon);
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
winrt::fire_and_forget MyPage::OnLoadIconClick(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e)
|
|
{
|
|
const auto text = PathInput().Text();
|
|
|
|
const auto selectedComboItem = GroupSelector().SelectedItem().try_as<winrt::Windows::UI::Xaml::Controls::ComboBoxItem>();
|
|
const auto groupText = winrt::unbox_value<winrt::hstring>(selectedComboItem.Content());
|
|
// auto hr = SetCurrentProcessExplicitAppUserModelID(groupText.c_str());
|
|
// LOG_IF_FAILED(hr);
|
|
IPropertyStore* pPropertyStore = nullptr;
|
|
HRESULT hr = SHGetPropertyStoreForWindow(_hwnd, IID_PPV_ARGS(&pPropertyStore));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Set the System.AppUserModel.ID property (replace "YourAppID" with your desired ID)
|
|
PROPVARIANT propVar;
|
|
PropVariantInit(&propVar);
|
|
propVar.vt = VT_LPWSTR;
|
|
std::wstring s = groupText.c_str();
|
|
auto cstr = s.data();
|
|
propVar.pwszVal = cstr;
|
|
|
|
hr = pPropertyStore->SetValue(PKEY_AppUserModel_ID, propVar);
|
|
|
|
PropVariantClear(&propVar);
|
|
pPropertyStore->Release();
|
|
}
|
|
LOG_IF_FAILED(hr);
|
|
|
|
co_await winrt::resume_background();
|
|
|
|
_attemptTwo(text);
|
|
|
|
co_return;
|
|
}
|
|
}
|