mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
[User Interface] IDEA-75238 Microsoft Windows Jump Lists support: WinJumpListBridge project big refactoring.
GitOrigin-RevId: fab9eda6457d2934d1e241c33fe16e3a1aa59831
This commit is contained in:
committed by
intellij-monorepo-bot
parent
20b41882b8
commit
685b62e435
11
native/WinShellIntegration/.gitignore
vendored
Normal file
11
native/WinShellIntegration/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
*
|
||||
|
||||
!.gitignore
|
||||
!CMakeLists.txt
|
||||
!README.md
|
||||
|
||||
!build/
|
||||
!build/**
|
||||
|
||||
!src/
|
||||
!src/**
|
||||
19
native/WinShellIntegration/CMakeLists.txt
Normal file
19
native/WinShellIntegration/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
# Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
if (NOT WIN32)
|
||||
message(FATAL_ERROR "This project is intended for Windows only.")
|
||||
endif()
|
||||
|
||||
cmake_minimum_required(VERSION 3.15) # 3.15 is required for the CMP0092 policy
|
||||
project(WinShellIntegration CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
add_subdirectory("src/winshellintegration")
|
||||
add_subdirectory("src/winshellintegrationbridge")
|
||||
|
||||
#enable_testing()
|
||||
#add_subdirectory("tests")
|
||||
26
native/WinShellIntegration/README.md
Normal file
26
native/WinShellIntegration/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# WinShellIntegration
|
||||
|
||||
This project based on the [origin jumplistbridge project](https://github.com/JetBrains/intellij-community/tree/4635352640ed54ef9379082171f33837d099ebb8/native/jumplistbridge)
|
||||
by Denis Fokin.
|
||||
|
||||
TODO: description
|
||||
|
||||
## Build dependencies
|
||||
* C++17-compatible compiler
|
||||
* [CMake](https://cmake.org/download/) >= `v3.15` (Note: it is already shipped with CLion)
|
||||
* [Optional for building [winshellintegrationbridge](src/winshellintegrationbridge) target]:
|
||||
An implementation of Java SE 11 or newer. It's highly recommended to use the JDK you are using to build IDEA;
|
||||
|
||||
## Build
|
||||
Just use CMake normally.
|
||||
|
||||
If you want to build [winshellintegrationbridge](src/winshellintegrationbridge) target too
|
||||
you have to set the variable `JDK_PATH` to the path of JDK which will be used for build.
|
||||
For example if your `javac.exe` is located at `C:\Soft\jdk\bin\javac.exe` you should set the variable like
|
||||
`-DJDK_PATH="C:\Soft\jdk"`.
|
||||
|
||||
## Integration with CLion
|
||||
TODO: description
|
||||
|
||||
## Current binaries in repository
|
||||
TODO: description of environment for building
|
||||
3
native/WinShellIntegration/build/.gitignore
vendored
Normal file
3
native/WinShellIntegration/build/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*
|
||||
|
||||
!.gitignore
|
||||
@@ -0,0 +1,17 @@
|
||||
# Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
function(enable_target_compile_extra_warnings target)
|
||||
if (MSVC)
|
||||
target_compile_options(${target}
|
||||
PRIVATE /W4 # warnings level
|
||||
PRIVATE /WX # treat all warnings as errors
|
||||
)
|
||||
else()
|
||||
target_compile_options(${target}
|
||||
PRIVATE -Wall # basic set of warnings
|
||||
PRIVATE -Wextra # additional warnings
|
||||
PRIVATE -pedantic # modern C++ inspections
|
||||
PRIVATE -Werror # treat all warnings as errors
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -0,0 +1,57 @@
|
||||
# Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
add_library(winshellintegration STATIC
|
||||
"include/winshellintegration.h"
|
||||
|
||||
"include/winshellintegration/winapi.h"
|
||||
|
||||
"include/winshellintegration/wide_string.h"
|
||||
|
||||
"include/winshellintegration/COM_errors.h"
|
||||
"src/COM_errors.cpp"
|
||||
|
||||
"include/winshellintegration/COM_is_initialized.h"
|
||||
|
||||
"include/winshellintegration/jump_task.h"
|
||||
"src/jump_task.cpp"
|
||||
|
||||
"include/winshellintegration/jump_item.h"
|
||||
"src/jump_item.cpp"
|
||||
|
||||
"include/winshellintegration/jump_list.h"
|
||||
"src/jump_list.cpp"
|
||||
|
||||
"include/winshellintegration/app_user_model_id.h"
|
||||
|
||||
"include/winshellintegration/application.h"
|
||||
"src/application.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(winshellintegration
|
||||
PUBLIC "include"
|
||||
)
|
||||
|
||||
target_link_libraries(winshellintegration
|
||||
PUBLIC "shell32.lib"
|
||||
)
|
||||
|
||||
target_compile_definitions(winshellintegration
|
||||
# Force Unicode version of WinAPI.
|
||||
# It is necessary for:
|
||||
# * fixing of WinAPI SHARDAPPIDINFOLINK structure.
|
||||
PUBLIC UNICODE
|
||||
PUBLIC _UNICODE
|
||||
|
||||
# Exclude rarely-used stuff from Windows headers
|
||||
PRIVATE WIN32_LEAN_AND_MEAN
|
||||
|
||||
# Make the project supports Windows 8 and later.
|
||||
# See https://docs.microsoft.com/ru-ru/cpp/porting/modifying-winver-and-win32-winnt for more info.
|
||||
# 0x0602 is the identifier of Windows 8
|
||||
PUBLIC WINSHELLINTEGRATION_WINVER=0x0602
|
||||
PUBLIC WINVER=WINSHELLINTEGRATION_WINVER
|
||||
PUBLIC _WIN32_WINNT=WINSHELLINTEGRATION_WINVER
|
||||
)
|
||||
|
||||
include("${PROJECT_SOURCE_DIR}/src/build/EnableCompilerExtraWarnings.cmake")
|
||||
enable_target_compile_extra_warnings(winshellintegration)
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef WINSHELLINTEGRATION_WINSHELLINTEGRATION_H
|
||||
#define WINSHELLINTEGRATION_WINSHELLINTEGRATION_H
|
||||
|
||||
#include "winshellintegration/winapi.h"
|
||||
#include "winshellintegration/wide_string.h"
|
||||
#include "winshellintegration/COM_errors.h"
|
||||
#include "winshellintegration/COM_is_initialized.h"
|
||||
#include "winshellintegration/jump_task.h"
|
||||
#include "winshellintegration/jump_item.h"
|
||||
#include "winshellintegration/jump_list.h"
|
||||
#include "winshellintegration/app_user_model_id.h"
|
||||
#include "winshellintegration/application.h"
|
||||
|
||||
#endif // ndef WINSHELLINTEGRATION_WINSHELLINTEGRATION_H
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* Stuffs for integration COM calls return codes (HRESULT) into std::system_error exception.
|
||||
*
|
||||
* Intended use case:
|
||||
* const auto comResult = SomeCOMCall(params...);
|
||||
* if (comResult != S_OK)
|
||||
* errors::throwCOMException(comResult, "SomeCOMCall failed", __func__);
|
||||
*
|
||||
* Or more flexible:
|
||||
* const auto comResult = SomeCOMCall(params...);
|
||||
* if (comResult != S_OK)
|
||||
* throw std::runtime_error{ errors::COMResultHandle{comResult}, "my custom description" };
|
||||
*
|
||||
* @author Nikita Provotorov
|
||||
*/
|
||||
|
||||
#ifndef WINSHELLINTEGRATION_COM_ERRORS_H
|
||||
#define WINSHELLINTEGRATION_COM_ERRORS_H
|
||||
|
||||
#include "winapi.h" // HRESULT
|
||||
#include <system_error> // std::error_code, std::error_category, std::is_error_code_enum
|
||||
#include <type_traits> // std::true_type
|
||||
#include <string_view> // std::string_view
|
||||
|
||||
|
||||
namespace intellij::ui::win::errors
|
||||
{
|
||||
/// Transparent wrapper around HRESULT
|
||||
class COMResultHandle final
|
||||
{
|
||||
public:
|
||||
explicit COMResultHandle(HRESULT hResult) noexcept
|
||||
: hResult_(hResult)
|
||||
{}
|
||||
|
||||
bool operator==(HRESULT rhs) const noexcept { return (hResult_ == rhs); }
|
||||
bool operator!=(HRESULT rhs) const noexcept { return (!(*this == rhs)); }
|
||||
|
||||
[[nodiscard]] HRESULT getHandle() const noexcept { return hResult_; }
|
||||
explicit operator HRESULT() const noexcept { return getHandle(); }
|
||||
|
||||
private:
|
||||
HRESULT hResult_;
|
||||
};
|
||||
|
||||
const std::error_category& getCOMErrorCategory() noexcept;
|
||||
|
||||
|
||||
// this is for a code like:
|
||||
// const auto hResult = SomeCOMCall(params...);
|
||||
// if (comErrC != S_OK)
|
||||
// throw std::system_error(errors::COMResultHandle{hResult});
|
||||
//
|
||||
// (it will be found through ADL)
|
||||
std::error_code make_error_code(COMResultHandle comResultHandle) noexcept;
|
||||
|
||||
|
||||
// TODO: docs
|
||||
[[noreturn]] void throwCOMException(
|
||||
HRESULT comReturned,
|
||||
std::string_view description,
|
||||
// __func__
|
||||
std::string_view pass_func_here) noexcept(false);
|
||||
|
||||
// TODO: docs
|
||||
[[noreturn]] void throwCOMException(
|
||||
HRESULT comReturned,
|
||||
std::string_view description,
|
||||
// __func__
|
||||
std::string_view pass_func_here,
|
||||
std::string_view classOrNamespaceName) noexcept(false);
|
||||
|
||||
} // namespace intellij::ui::win::errors
|
||||
|
||||
// Enables SFINAE overload intellij::ui::win::errors::make_error_code of std::make_error_code
|
||||
template<>
|
||||
struct std::is_error_code_enum<intellij::ui::win::errors::COMResultHandle> : std::true_type {};
|
||||
|
||||
|
||||
#endif // ndef WINSHELLINTEGRATION_COM_ERRORS_H
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* intellij::ui::win::COMIsInitializedInThisThreadTag struct implementation and
|
||||
* intellij::ui::win::COM_IS_INITIALIZED_IN_THIS_THREAD variable definition.
|
||||
*
|
||||
* See below for the documentation.
|
||||
*
|
||||
* @author Nikita Provotorov
|
||||
*/
|
||||
|
||||
#ifndef WINSHELLINTEGRATION_COM_IS_INITIALIZED_H
|
||||
#define WINSHELLINTEGRATION_COM_IS_INITIALIZED_H
|
||||
|
||||
|
||||
namespace intellij::ui::win
|
||||
{
|
||||
|
||||
/// Tag to explicitly indicating the dependence of functions/classes on the COM library.
|
||||
struct COMIsInitializedInThisThreadTag
|
||||
{
|
||||
explicit constexpr COMIsInitializedInThisThreadTag(int) {}
|
||||
};
|
||||
|
||||
inline constexpr COMIsInitializedInThisThreadTag COM_IS_INITIALIZED_IN_THIS_THREAD{42};
|
||||
|
||||
} // namespace intellij::ui::win
|
||||
|
||||
#endif // ndef WINSHELLINTEGRATION_COM_IS_INITIALIZED_H
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef WINSHELLINTEGRATION_APP_USER_MODEL_ID_H
|
||||
#define WINSHELLINTEGRATION_APP_USER_MODEL_ID_H
|
||||
|
||||
#include "wide_string.h"
|
||||
|
||||
|
||||
namespace intellij::ui::win
|
||||
{
|
||||
|
||||
using AppUserModelId = WideString;
|
||||
|
||||
} // namespace intellij::ui::win
|
||||
|
||||
#endif // ndef WINSHELLINTEGRATION_APP_USER_MODEL_ID_H
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* intellij::ui::win::Application class implementation.
|
||||
*
|
||||
* See below for the documentation.
|
||||
*
|
||||
* @author Nikita Provotorov
|
||||
*/
|
||||
|
||||
#ifndef WINSHELLINTEGRATION_APPLICATION_H
|
||||
#define WINSHELLINTEGRATION_APPLICATION_H
|
||||
|
||||
#include "app_user_model_id.h" // AppUserModelId
|
||||
#include "COM_is_initialized.h" // COMIsInitializedInThisThreadTag
|
||||
#include <optional> // std::optional
|
||||
#include <filesystem> // std::filesystem::path
|
||||
#include <utility> // std::pair
|
||||
|
||||
|
||||
struct ICustomDestinationList; // forward declaration
|
||||
|
||||
|
||||
namespace intellij::ui::win
|
||||
{
|
||||
|
||||
class JumpItem; // forward declaration
|
||||
class JumpTask; // forward declaration
|
||||
class JumpList; // forward declaration
|
||||
|
||||
|
||||
/// @brief Application class is the entry point for all platform-specific calls
|
||||
/// that explicitly affect the current application.
|
||||
///
|
||||
/// For now it provides access to the following features:
|
||||
/// * managing AppUserModelID property of the application (see documentation for the Application::UserModelId type);
|
||||
/// * managing list of the recently used documents, directories, actions by user;
|
||||
/// * managing Windows custom Jump Lists.
|
||||
///
|
||||
/// @note Some member functions require the COM library to be initialized in the invoking thread.
|
||||
/// All of them receive an additional tag parameter of the intellij::ui::win::COMIsInitializedInThisThreadTag type.
|
||||
class Application final
|
||||
{
|
||||
public: // nested types
|
||||
/// Application User Model IDs (AppUserModelIDs) are used extensively by the taskbar
|
||||
/// in Windows 7 and later systems to associate processes, files, and windows with a particular application.
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/shell/appids for detailed description
|
||||
using UserModelId = AppUserModelId;
|
||||
|
||||
public: // ctors/dtor
|
||||
// non-copyable
|
||||
Application(const Application&) = delete;
|
||||
// non-movable
|
||||
Application(Application&&) = delete;
|
||||
|
||||
static Application& getInstance() noexcept;
|
||||
|
||||
public: // assignments
|
||||
// non-copyable
|
||||
Application& operator=(const Application&) = delete;
|
||||
// non-movable
|
||||
Application& operator=(Application&&) = delete;
|
||||
|
||||
public: // modifiers
|
||||
/// @brief Specifies a unique application-defined Application User Model ID (AppUserModelID) that
|
||||
/// identifies the current process to the taskbar.
|
||||
///
|
||||
/// See docs for Application::UserModelId type for more info.
|
||||
///
|
||||
/// @exception std::system_error in case of failed system call
|
||||
/// @exception exceptions derived from std::exception in case of some internal errors
|
||||
void setAppUserModelId(const UserModelId& appId) noexcept(false);
|
||||
|
||||
/// @brief Notifies the system that an document has been accessed,
|
||||
/// for the purposes of tracking those items used most recently and most frequently.
|
||||
///
|
||||
/// @attention This method will work only if the application is registered at HKEY_CLASSES_ROOT/Applications.
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shaddtorecentdocs
|
||||
/// for detailed info about Recent items.
|
||||
///
|
||||
/// @exception std::system_error in case of failed system call
|
||||
/// @exception exceptions derived from std::exception in case of some internal errors
|
||||
void registerRecentlyUsed(const std::filesystem::path& path) noexcept(false);
|
||||
|
||||
/// @brief Notifies the system that an item has been accessed,
|
||||
/// for the purposes of tracking those items used most recently and most frequently.
|
||||
///
|
||||
/// @attention This method will work only if the application is registered at HKEY_CLASSES_ROOT/Applications.
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shaddtorecentdocs
|
||||
/// for detailed info about Recent items.
|
||||
///
|
||||
/// @exception std::system_error in case of failed system call
|
||||
/// @exception exceptions derived from std::exception in case of some internal errors
|
||||
void registerRecentlyUsed(const JumpItem& recentJumpItem, COMIsInitializedInThisThreadTag) noexcept(false);
|
||||
|
||||
/// @brief Notifies the system that an action has been accessed,
|
||||
/// for the purposes of tracking those items used most recently and most frequently.
|
||||
///
|
||||
/// @remark Recent JumpTasks are not added to the Windows Explorer's Recent folder,
|
||||
/// although they are reflected in an application's Jump List (see
|
||||
/// https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shaddtorecentdocs
|
||||
/// for more info).
|
||||
///
|
||||
/// @exception std::system_error in case of failed system call
|
||||
/// @exception exceptions derived from std::exception in case of some internal errors
|
||||
void registerRecentlyUsed(const JumpTask& recentJumpTask, COMIsInitializedInThisThreadTag) noexcept(false);
|
||||
|
||||
/// @brief Clears all usage data on all Recent and Frequent items
|
||||
/// and also removes all items of Recent and Frequent categories of the Jump List.
|
||||
///
|
||||
/// @exception std::system_error in case of failed system call
|
||||
/// @exception exceptions derived from std::exception in case of some internal errors
|
||||
void clearRecentsAndFrequents(COMIsInitializedInThisThreadTag) noexcept(false);
|
||||
|
||||
/// Applies the passed Jump List to the current application. Includes call of deleteJumpList method.
|
||||
///
|
||||
/// @exception std::system_error in case of failed system call
|
||||
/// @exception exceptions derived from std::exception in case of some internal errors
|
||||
void setJumpList(const JumpList& jumpList, COMIsInitializedInThisThreadTag) noexcept(false);
|
||||
|
||||
/// Erase any custom Jump List set previously.
|
||||
///
|
||||
/// @exception std::system_error in case of failed system call
|
||||
/// @exception exceptions derived from std::exception in case of some internal errors
|
||||
void deleteJumpList(COMIsInitializedInThisThreadTag) noexcept(false);
|
||||
|
||||
public: // getters
|
||||
/// @brief Returns a unique application-defined Application User Model ID (AppUserModelID)
|
||||
/// previously set by Application::setAppUserModelId method.
|
||||
///
|
||||
/// See docs for Application::UserModelId type for more info about AppUserModelIDs.
|
||||
///
|
||||
/// @returns empty optional if UserModelId was not set previously
|
||||
///
|
||||
/// @exception std::system_error in case of failed system call
|
||||
/// @exception exceptions derived from std::exception in case of some internal errors
|
||||
[[nodiscard]] std::optional<UserModelId> obtainAppUserModelId() const noexcept(false);
|
||||
|
||||
private:
|
||||
Application() noexcept;
|
||||
~Application() noexcept;
|
||||
|
||||
private: // helpers
|
||||
void refreshJumpListHandle() noexcept(false);
|
||||
|
||||
private:
|
||||
std::pair< std::optional<UserModelId>, CComPtr<ICustomDestinationList> > idAndJumpListHandle_;
|
||||
};
|
||||
|
||||
} // namespace intellij::ui::win
|
||||
|
||||
#endif // ndef WINSHELLINTEGRATION_APPLICATION_H
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* intellij::ui::win::JumpItem class implementation.
|
||||
*
|
||||
* See below for the documentation.
|
||||
*
|
||||
* @author Nikita Provotorov
|
||||
*/
|
||||
|
||||
#ifndef WINSHELLINTEGRATION_JUMPITEM_H
|
||||
#define WINSHELLINTEGRATION_JUMPITEM_H
|
||||
|
||||
#include "winapi.h" // IShellItemW, CComPtr
|
||||
#include "COM_is_initialized.h" // COMIsInitializedInThisThreadTag
|
||||
#include <filesystem> // std::filesystem::path
|
||||
|
||||
|
||||
namespace intellij::ui::win
|
||||
{
|
||||
|
||||
// TODO: docs
|
||||
class JumpItem
|
||||
{
|
||||
public: // nested types
|
||||
using SharedNativeHandle = CComPtr<IShellItem>;
|
||||
|
||||
public: // ctors/dtor
|
||||
JumpItem(std::filesystem::path path, COMIsInitializedInThisThreadTag) noexcept(false);
|
||||
|
||||
/// non-copyable
|
||||
JumpItem(const JumpItem& src) = delete;
|
||||
JumpItem(JumpItem&& src) noexcept;
|
||||
|
||||
public: // assignments
|
||||
/// non-copyable
|
||||
JumpItem& operator=(const JumpItem& rhs) = delete;
|
||||
JumpItem& operator=(JumpItem&& rhs) noexcept;
|
||||
|
||||
public: // getters
|
||||
[[nodiscard]] SharedNativeHandle shareNativeHandle(COMIsInitializedInThisThreadTag) const noexcept(false);
|
||||
|
||||
[[nodiscard]] const std::filesystem::path& getPath() const noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]] static CComPtr<IShellItem> createHandleFrom(
|
||||
const std::filesystem::path& path,
|
||||
COMIsInitializedInThisThreadTag
|
||||
) noexcept(false);
|
||||
|
||||
private:
|
||||
CComPtr<IShellItem> handle_;
|
||||
std::filesystem::path path_;
|
||||
};
|
||||
|
||||
} // namespace intellij::ui::win
|
||||
|
||||
#endif // ndef WINSHELLINTEGRATION_JUMPITEM_H
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* intellij::ui::win::JumpList class implementation.
|
||||
*
|
||||
* See below for the documentation.
|
||||
*
|
||||
* @author Nikita Provotorov
|
||||
*/
|
||||
|
||||
#ifndef WINSHELLINTEGRATION_JUMPLIST_H
|
||||
#define WINSHELLINTEGRATION_JUMPLIST_H
|
||||
|
||||
#include "jump_task.h" // JumpTask
|
||||
#include "jump_item.h" // JumpItem
|
||||
#include "wide_string.h" // WideString, WideStringView
|
||||
#include "COM_is_initialized.h" // COMIsInitializedInThisThreadTag
|
||||
#include <variant> // std::variant
|
||||
#include <deque> // std::deque
|
||||
#include <list> // std::list
|
||||
#include <utility> // std::pair
|
||||
|
||||
|
||||
namespace intellij::ui::win
|
||||
{
|
||||
|
||||
/// @brief JumpList encapsulates methods and properties that allow an application to provide
|
||||
/// a custom Jump List, including destinations and tasks, for display in the taskbar.
|
||||
///
|
||||
/// How to use it:
|
||||
/// 1. Create an instance of JumpList;
|
||||
/// 2. Tune it via its modifiers (see below for their documentation);
|
||||
/// 3. Apply it to the Taskbar via intellij::ui::win::Application::setJumpList method.
|
||||
///
|
||||
/// @note Some member functions require the COM library to be initialized in the invoking thread.
|
||||
/// All of them receive an additional tag parameter of the intellij::ui::win::COMIsInitializedInThisThreadTag type.
|
||||
class JumpList
|
||||
{
|
||||
public: // nested types
|
||||
// JumpList holds JumpItem s and/or JumpTask s
|
||||
using value_type = std::variant<JumpItem, JumpTask>;
|
||||
|
||||
using UserTasksCategoryContainer = std::deque<value_type>;
|
||||
|
||||
using CustomCategoryContainer = std::deque<value_type>;
|
||||
// We are using std::list not std::map/std::unordered_map because we should keep the insertion order
|
||||
using CustomCategoriesContainer = std::list< std::pair<const WideString, CustomCategoryContainer> >;
|
||||
|
||||
public: // ctors/dtor
|
||||
/// isRecentCategoryVisible() returns false
|
||||
/// isFrequentCategoryVisible() returns false
|
||||
JumpList() noexcept;
|
||||
|
||||
/// JumpLists are non-copyable
|
||||
JumpList(const JumpList&) = delete;
|
||||
JumpList(JumpList&&);
|
||||
|
||||
~JumpList() noexcept;
|
||||
|
||||
public: // assignments
|
||||
/// JumpLists are non-copyable
|
||||
JumpList& operator=(const JumpList&) = delete;
|
||||
JumpList& operator=(JumpList&&);
|
||||
|
||||
public: // modifiers
|
||||
/// Sets a value that indicates whether recently used items are displayed in the Jump List (Recent category).
|
||||
/// You can call the Application::registerRecentlyUsed method (SHAddToRecentDocs system call)
|
||||
/// to request that the Windows shell add items to the Recent items list.
|
||||
///
|
||||
/// @returns *this
|
||||
JumpList& setRecentCategoryVisible(bool visible) noexcept;
|
||||
/// Sets a value that indicates whether frequently used items are displayed in the Jump List (Frequent category).
|
||||
/// You can call the Application::registerRecentlyUsed method (SHAddToRecentDocs system call)
|
||||
/// to request that the Windows shell add items to the Frequent items list.
|
||||
///
|
||||
/// @returns *this
|
||||
JumpList& setFrequentCategoryVisible(bool visible) noexcept;
|
||||
|
||||
/// Specifies items to include in the "Tasks" category.
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-icustomdestinationlist-addusertasks
|
||||
/// for detailed info about "Tasks" category.
|
||||
///
|
||||
/// @returns *this
|
||||
JumpList& appendToUserTasks(JumpTask jumpTask, COMIsInitializedInThisThreadTag);
|
||||
/// Specifies items to include in the "Tasks" category.
|
||||
/// @note You probably need to use overload with JumpTask parameter. User tasks use JumpItem is a really rare case.
|
||||
///
|
||||
/// @returns *this
|
||||
JumpList& appendToUserTasks(JumpItem jumpItem, COMIsInitializedInThisThreadTag);
|
||||
|
||||
/// Defines a custom category and the destinations that it contains, for inclusion in a custom Jump List.
|
||||
///
|
||||
/// @returns *this
|
||||
JumpList& appendToCustomCategory(WideStringView categoryName, JumpItem jumpItem, COMIsInitializedInThisThreadTag);
|
||||
/// Defines a custom category and the destinations that it contains, for inclusion in a custom Jump List.
|
||||
///
|
||||
/// @returns *this
|
||||
JumpList& appendToCustomCategory(WideStringView categoryName, JumpTask jumpTask, COMIsInitializedInThisThreadTag);
|
||||
|
||||
public: // getters
|
||||
/// See setRecentCategoryVisible for the documentation.
|
||||
[[nodiscard]] bool isRecentCategoryVisible() const noexcept;
|
||||
/// See setFrequentCategoryVisible for the documentation.
|
||||
[[nodiscard]] bool isFrequentCategoryVisible() const noexcept;
|
||||
|
||||
[[nodiscard]] const UserTasksCategoryContainer& getUserTasksCategory() const noexcept;
|
||||
|
||||
[[nodiscard]] const CustomCategoriesContainer& getAllCustomCategories() const noexcept;
|
||||
|
||||
/// returns nullptr if a custom category with the passed name is not found
|
||||
[[nodiscard]] const CustomCategoryContainer* findCustomCategory(WideStringView categoryName) const noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]] CustomCategoryContainer* findCustomCategory(WideStringView categoryName) noexcept;
|
||||
|
||||
private:
|
||||
UserTasksCategoryContainer userTasksCategory_;
|
||||
CustomCategoriesContainer customCategories_;
|
||||
bool isRecentCategoryVisible_;
|
||||
bool isFrequentCategoryVisible_;
|
||||
};
|
||||
|
||||
} // namespace intellij::ui::win
|
||||
|
||||
#endif // ndef WINSHELLINTEGRATION_JUMPLIST_H
|
||||
@@ -0,0 +1,173 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* intellij::ui::win::JumpTask and intellij::ui::win::JumpTask::BuildSession classes implementation.
|
||||
*
|
||||
* See below for the documentation.
|
||||
*
|
||||
* @author Nikita Provotorov
|
||||
*/
|
||||
|
||||
#ifndef WINSHELLINTEGRATION_JUMPTASK_H
|
||||
#define WINSHELLINTEGRATION_JUMPTASK_H
|
||||
|
||||
#include "winapi.h" // IShellLinkW, CComPtr
|
||||
#include "COM_is_initialized.h" // COMIsInitializedInThisThreadTag
|
||||
#include "wide_string.h" // WideString, WideStringView
|
||||
#include <filesystem> // std::filesystem
|
||||
#include <optional> // std::optional
|
||||
#include <utility> // std::forward
|
||||
|
||||
|
||||
namespace intellij::ui::win
|
||||
{
|
||||
|
||||
// TODO: docs
|
||||
class JumpTask
|
||||
{
|
||||
public: // nested types
|
||||
using SharedNativeHandle = CComPtr<IShellLinkW>;
|
||||
|
||||
// see declaration below
|
||||
class BuildSession;
|
||||
|
||||
public: // ctors/dtor
|
||||
/// JumpTask is non-copyable.
|
||||
JumpTask(const JumpTask&) = delete;
|
||||
|
||||
JumpTask(JumpTask&& other) noexcept;
|
||||
|
||||
|
||||
/// Starts the process of JumpTask building.
|
||||
///
|
||||
/// @param[in] appPath - the path to the application to execute. Must not be empty.
|
||||
/// @param[in] title - the text displayed for the task in the Jump List. Must not be empty;
|
||||
/// the task will be unclickable otherwise.
|
||||
///
|
||||
/// @exception std::runtime_error in case of empty one of the passed parameters
|
||||
[[nodiscard]] static BuildSession startBuilding(
|
||||
std::filesystem::path appPath,
|
||||
WideString title
|
||||
) noexcept(false);
|
||||
|
||||
|
||||
~JumpTask() noexcept;
|
||||
|
||||
public: // assignments
|
||||
/// JumpTask is non-copyable.
|
||||
JumpTask& operator=(const JumpTask&) = delete;
|
||||
|
||||
JumpTask& operator=(JumpTask&& lhs);
|
||||
|
||||
public: // getters
|
||||
[[nodiscard]] SharedNativeHandle shareNativeHandle(COMIsInitializedInThisThreadTag) const noexcept(false);
|
||||
|
||||
private:
|
||||
/// Use JumpTask::startBuilding to create a task
|
||||
explicit JumpTask(CComPtr<IShellLinkW>&& nativeHandle, COMIsInitializedInThisThreadTag) noexcept;
|
||||
|
||||
private:
|
||||
CComPtr<IShellLinkW> handle_;
|
||||
};
|
||||
|
||||
|
||||
// TODO: docs
|
||||
class JumpTask::BuildSession
|
||||
{
|
||||
friend class JumpTask;
|
||||
|
||||
public: // ctors/dtor
|
||||
// non-copyable
|
||||
BuildSession(const BuildSession&) = delete;
|
||||
// non-movable
|
||||
BuildSession(BuildSession&&) = delete;
|
||||
|
||||
public: // assignments
|
||||
// non-copyable
|
||||
BuildSession& operator=(const BuildSession&) = delete;
|
||||
// non-movable
|
||||
BuildSession& operator=(BuildSession&&) = delete;
|
||||
|
||||
public: // modifiers of optional parameters
|
||||
/// Sets the arguments passed to the application on startup.
|
||||
/// @returns *this
|
||||
BuildSession& setApplicationArguments(WideString allArgs) noexcept;
|
||||
/// Conditionally sets the arguments passed to the application on startup.
|
||||
///
|
||||
/// @param[in] condition - if true then this invokes setApplicationArguments({std::forward<Ts>(allArgs)...});
|
||||
/// nothing will be performed otherwise.
|
||||
///
|
||||
/// @returns *this
|
||||
template<typename... Ts>
|
||||
BuildSession& setApplicationArgumentsIf(bool condition, Ts&&... allArgs)
|
||||
{
|
||||
if (condition)
|
||||
return setApplicationArguments({std::forward<Ts>(allArgs)...});
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Sets the working directory of the application on startup.
|
||||
/// @returns *this
|
||||
BuildSession& setApplicationWorkingDirectory(std::filesystem::path wdPath) noexcept;
|
||||
/// Conditionally sets the working directory of the application on startup.
|
||||
///
|
||||
/// @param[in] condition - if true then this invokes setApplicationWorkingDirectory({std::forward<Ts>(allArgs)...});
|
||||
/// nothing will be performed otherwise.
|
||||
///
|
||||
/// @returns *this
|
||||
template<typename... Ts>
|
||||
BuildSession& setApplicationWorkingDirectoryIf(bool condition, Ts&&... allArgs)
|
||||
{
|
||||
if (condition)
|
||||
return setApplicationWorkingDirectory({std::forward<Ts>(allArgs)...});
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Sets the text displayed in the tooltip for the tasks in the Jump List.
|
||||
/// @returns *this
|
||||
BuildSession& setDescription(WideString description) noexcept;
|
||||
/// Conditionally sets the text displayed in the tooltip for the tasks in the Jump List.
|
||||
///
|
||||
/// @param[in] condition - if true then this invokes setDescription({std::forward<Ts>(allArgs)...});
|
||||
/// nothing will be performed otherwise.
|
||||
///
|
||||
/// @returns *this
|
||||
template<typename... Ts>
|
||||
BuildSession& setDescriptionIf(bool condition, Ts&&... allArgs)
|
||||
{
|
||||
if (condition)
|
||||
return setDescription({std::forward<Ts>(allArgs)...});
|
||||
return *this;
|
||||
}
|
||||
|
||||
public: // getters
|
||||
[[nodiscard]] JumpTask buildTask(COMIsInitializedInThisThreadTag) const noexcept(false);
|
||||
|
||||
private:
|
||||
/// Use JumpTask::startBuilding method
|
||||
///
|
||||
/// @exception std::runtime_error in case of empty one of the passed parameters
|
||||
BuildSession(std::filesystem::path appPath, WideString title) noexcept(false);
|
||||
|
||||
//BuildSession(BuildSession&&) noexcept;
|
||||
//BuildSession& operator=(BuildSession&&) noexcept;
|
||||
|
||||
private: // helpers
|
||||
[[nodiscard]] JumpTask createJumpTask(COMIsInitializedInThisThreadTag) const noexcept(false);
|
||||
void copyAppPathToJumpTask(JumpTask& task) const noexcept(false);
|
||||
void copyAppArgsToJumpTask(JumpTask& task) const noexcept(false);
|
||||
void copyWorkDirToJumpTask(JumpTask& task) const noexcept(false);
|
||||
void copyTitleToJumpTask(JumpTask& task) const noexcept(false);
|
||||
void copyDescriptionToJumpTask(JumpTask& task) const noexcept(false);
|
||||
|
||||
private:
|
||||
const std::filesystem::path appPath_;
|
||||
const WideString title_;
|
||||
std::optional<WideString> appArguments_;
|
||||
std::optional<std::filesystem::path> appWorkDir_;
|
||||
std::optional<WideString> description_;
|
||||
};
|
||||
|
||||
} // namespace intellij::ui::win
|
||||
|
||||
#endif // ndef WINSHELLINTEGRATION_JUMPTASK_H
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef WINSHELLINTEGRATION_WIDE_STRING_H
|
||||
#define WINSHELLINTEGRATION_WIDE_STRING_H
|
||||
|
||||
#include "winapi.h" // WCHAR
|
||||
#include <string> // std::basic_string
|
||||
#include <string_view> // std::basic_string_view
|
||||
|
||||
|
||||
namespace intellij::ui::win
|
||||
{
|
||||
|
||||
// Null-terminated
|
||||
using WideString = std::basic_string<WCHAR>;
|
||||
using WideStringView = std::basic_string_view<WideString::value_type>;
|
||||
|
||||
} // namespace intellij::ui::win
|
||||
|
||||
#endif // ndef WINSHELLINTEGRATION_WIDE_STRING_H
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* All WinAPI headers used by the project.
|
||||
*
|
||||
* @author Nikita Provotorov
|
||||
*/
|
||||
|
||||
// TODO: make all defines in CMake
|
||||
|
||||
#ifndef WINSHELLINTEGRATION_WINAPI_H
|
||||
#define WINSHELLINTEGRATION_WINAPI_H
|
||||
|
||||
// Force Unicode version of WinAPI.
|
||||
// It is necessary for:
|
||||
// * fix WinAPI SHARDAPPIDINFOLINK structure.
|
||||
#ifndef UNICODE
|
||||
#define UNICODE
|
||||
#endif // ndef UNICODE
|
||||
#ifndef _UNICODE
|
||||
#define _UNICODE
|
||||
#endif // ndef _UNICODE
|
||||
|
||||
|
||||
// Exclude rarely-used stuff from Windows headers
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif // ndef WIN32_LEAN_AND_MEAN
|
||||
|
||||
|
||||
// Make the project supports Windows 8 and later.
|
||||
// See https://docs.microsoft.com/ru-ru/cpp/porting/modifying-winver-and-win32-winnt for more info.
|
||||
#ifndef WINSHELLINTEGRATION_WINVER
|
||||
#define WINSHELLINTEGRATION_WINVER 0x0602 // 0x0602 is the identifier of Windows 8
|
||||
#endif // ndef WINSHELLINTEGRATION_WINVER
|
||||
|
||||
#include <WinSDKVer.h>
|
||||
#ifndef WINVER
|
||||
#define WINVER WINSHELLINTEGRATION_WINVER
|
||||
#endif // ndef WINVER
|
||||
#if (WINVER < WINSHELLINTEGRATION_WINVER)
|
||||
#error "WINVER define is too small. Only 0x0602 (Windows 8) or greater are supported"
|
||||
#endif // (WINVER < WINSHELLINTEGRATION_WINVER)
|
||||
|
||||
#ifndef _WIN32_WINNT
|
||||
#define _WIN32_WINNT WINSHELLINTEGRATION_WINVER
|
||||
#endif // ndef _WIN32_WINNT
|
||||
#if (_WIN32_WINNT < WINSHELLINTEGRATION_WINVER)
|
||||
#error "_WIN32_WINNT define is too small. Only 0x0602 (Windows 8) or greater are supported"
|
||||
#endif // (_WIN32_WINNT < WINSHELLINTEGRATION_WINVER)
|
||||
#include <sdkddkver.h>
|
||||
|
||||
|
||||
// Used WinAPI headers
|
||||
#include <Windows.h>
|
||||
#include <Shobjidl.h> // COM
|
||||
#include <Shlobj.h> // SHAddToRecentDocs, SHCreateItemFromParsingName ; to link with Shell32.lib
|
||||
#include <propsys.h> // IPropertyStore
|
||||
#include <Propidl.h> // PROPVARIANT
|
||||
#include <propkey.h> // PKEY_Title
|
||||
#include <propvarutil.h> // InitPropVariantFromString
|
||||
#include <atlbase.h> // CComPtr
|
||||
|
||||
#endif // ndef WINSHELLINTEGRATION_WINAPI_H
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
#include "winshellintegration/COM_errors.h"
|
||||
#include <string> // std::string
|
||||
#include <array> // std::array
|
||||
#include <charconv> // std::to_chars
|
||||
#include <utility> // std::move
|
||||
|
||||
|
||||
namespace intellij::ui::win::errors
|
||||
{
|
||||
|
||||
const std::error_category& getCOMErrorCategory() noexcept
|
||||
{
|
||||
struct Category final : std::error_category
|
||||
{
|
||||
[[nodiscard]] const char* name() const noexcept override { return "COM"; }
|
||||
|
||||
[[nodiscard]] std::string message(int condition) const override
|
||||
{
|
||||
// TODO: return good string description instead of "HRESULT=..."
|
||||
|
||||
const auto hResult = static_cast<std::make_unsigned<HRESULT>::type>(condition);
|
||||
|
||||
std::array<char, 51> buf; // NOLINT(cppcoreguidelines-pro-type-member-init)
|
||||
|
||||
const auto [pastTheEnd, errC] = std::to_chars(&buf.front(), &buf.back(), hResult, 16);
|
||||
if ( errC != std::errc() )
|
||||
return "HRESULT=<unknown>";
|
||||
|
||||
std::string result = "HRESULT=0x";
|
||||
result.append(&buf.front(), pastTheEnd);
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
static const Category result;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
std::error_code make_error_code(COMResultHandle comResultHandle) noexcept
|
||||
{
|
||||
return { static_cast<int>(comResultHandle.getHandle()), getCOMErrorCategory() };
|
||||
}
|
||||
|
||||
|
||||
void throwCOMException(
|
||||
HRESULT comReturned,
|
||||
std::string_view description,
|
||||
std::string_view funcName) noexcept(false)
|
||||
{
|
||||
throwCOMException(comReturned, description, funcName, {});
|
||||
}
|
||||
|
||||
void throwCOMException(
|
||||
HRESULT comReturned,
|
||||
std::string_view description,
|
||||
std::string_view funcName,
|
||||
std::string_view classOrNamespaceName) noexcept(false)
|
||||
{
|
||||
constexpr std::string_view atStr = " at ";
|
||||
constexpr std::string_view nameQualifier = "::";
|
||||
|
||||
const auto reservingLength = description.length()
|
||||
+ atStr.length()
|
||||
+ classOrNamespaceName.length()
|
||||
+ nameQualifier.length()
|
||||
+ funcName.length()
|
||||
+ 1; // '\0'
|
||||
|
||||
std::string what;
|
||||
what.reserve(reservingLength);
|
||||
|
||||
what.append(description)
|
||||
.append(atStr)
|
||||
.append(classOrNamespaceName)
|
||||
.append(nameQualifier)
|
||||
.append(funcName);
|
||||
|
||||
throw std::system_error{COMResultHandle(comReturned), std::move(what)}; // NOLINT(performance-move-const-arg)
|
||||
}
|
||||
|
||||
} // namespace intellij::ui::win::errors
|
||||
@@ -0,0 +1,529 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
#include "winshellintegration/application.h"
|
||||
#include "winshellintegration/winapi.h" // SetCurrentProcessExplicitAppUserModelID,
|
||||
// GetCurrentProcessExplicitAppUserModelID,
|
||||
// COM,
|
||||
// Shell
|
||||
|
||||
#include "winshellintegration/COM_errors.h" // errors::throwCOMException
|
||||
#include "winshellintegration/jump_item.h" // JumpItem
|
||||
#include "winshellintegration/jump_task.h" // JumpTask
|
||||
#include "winshellintegration/jump_list.h" // JumpList
|
||||
#include <string_view> // std::string_view
|
||||
#include <exception> // std::uncaught_exceptions
|
||||
#include <stdexcept> // std::logic_error
|
||||
#include <cassert> // assert
|
||||
|
||||
|
||||
namespace intellij::ui::win
|
||||
{
|
||||
|
||||
// ================================================================================================================
|
||||
// JumpListTransaction
|
||||
// ================================================================================================================
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class JumpListTransaction final
|
||||
{
|
||||
public:
|
||||
static constexpr std::string_view jumpListTransactionCtxName = "intellij::ui::win::Application::JumpListTransaction";
|
||||
|
||||
UINT maxSlots;
|
||||
CComPtr<IObjectArray> removedObjects;
|
||||
|
||||
public: // ctors/dtor
|
||||
explicit JumpListTransaction(ICustomDestinationList* jumpListHandle) noexcept(false)
|
||||
: handle_(nullptr)
|
||||
, maxSlots(0)
|
||||
{
|
||||
auto hr = jumpListHandle->BeginList(&maxSlots, IID_PPV_ARGS(&removedObjects));
|
||||
|
||||
if (hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"ICustomDestinationList::BeginList failed",
|
||||
__func__,
|
||||
jumpListTransactionCtxName
|
||||
);
|
||||
|
||||
assert( (removedObjects != nullptr) );
|
||||
|
||||
handle_ = jumpListHandle;
|
||||
}
|
||||
|
||||
// non-copyable
|
||||
JumpListTransaction(const JumpListTransaction&) = delete;
|
||||
|
||||
// non-movable
|
||||
JumpListTransaction(JumpListTransaction&&) = delete;
|
||||
|
||||
|
||||
~JumpListTransaction() noexcept(false)
|
||||
{
|
||||
if (handle_ == nullptr)
|
||||
return;
|
||||
|
||||
auto *const localHandle = handle_;
|
||||
handle_ = nullptr;
|
||||
|
||||
const auto hr = localHandle->AbortList();
|
||||
|
||||
if (std::uncaught_exceptions() != 0)
|
||||
assert( (hr == S_OK) );
|
||||
else if (hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"ICustomDestinationList::AbortList failed",
|
||||
__func__,
|
||||
jumpListTransactionCtxName
|
||||
);
|
||||
}
|
||||
|
||||
public:
|
||||
// non-copyable
|
||||
JumpListTransaction& operator=(const JumpListTransaction&) = delete;
|
||||
|
||||
// non-movable
|
||||
JumpListTransaction& operator=(JumpListTransaction&&) = delete;
|
||||
|
||||
public:
|
||||
void commit() noexcept(false)
|
||||
{
|
||||
if (handle_ == nullptr)
|
||||
return;
|
||||
|
||||
if (const auto hr = handle_->CommitList(); hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"ICustomDestinationList::CommitList failed",
|
||||
__func__,
|
||||
jumpListTransactionCtxName
|
||||
);
|
||||
|
||||
handle_ = nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
[[nodiscard]] ICustomDestinationList* operator->() const noexcept {
|
||||
assert( (handle_ != nullptr) );
|
||||
return handle_;
|
||||
}
|
||||
|
||||
[[nodiscard]] ICustomDestinationList& operator*() const noexcept {
|
||||
assert( (handle_ != nullptr) );
|
||||
return *handle_;
|
||||
}
|
||||
|
||||
private:
|
||||
ICustomDestinationList* handle_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
// ================================================================================================================
|
||||
// Some helpers
|
||||
// ================================================================================================================
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
CComPtr<IObjectCollection> createCategoryNativeContainer(
|
||||
const std::string_view callerFuncName,
|
||||
const std::string_view callerCtxName) noexcept(false)
|
||||
{
|
||||
CComPtr<IObjectCollection> result;
|
||||
|
||||
auto hr = result.CoCreateInstance(
|
||||
CLSID_EnumerableObjectCollection,
|
||||
nullptr,
|
||||
CLSCTX_INPROC
|
||||
);
|
||||
|
||||
if (hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"CoCreateInstance(CLSID_EnumerableObjectCollection) failed",
|
||||
callerFuncName,
|
||||
callerCtxName
|
||||
);
|
||||
|
||||
assert( (result != nullptr) );
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void insertToCategoryNativeContainer(
|
||||
IObjectCollection& nativeContainer,
|
||||
const JumpList::value_type& item,
|
||||
const std::string_view callerFuncName,
|
||||
const std::string_view callerCtxName,
|
||||
COMIsInitializedInThisThreadTag com) noexcept(false)
|
||||
{
|
||||
std::visit(
|
||||
[&nativeContainer, callerFuncName, callerCtxName, com](const auto& unwrappedItem) {
|
||||
auto hr = nativeContainer.AddObject(unwrappedItem.shareNativeHandle(com));
|
||||
if (hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"IObjectCollection::AddObject failed",
|
||||
callerFuncName,
|
||||
callerCtxName
|
||||
);
|
||||
},
|
||||
|
||||
item
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] CComPtr<ICustomDestinationList> createJumpListHandle(
|
||||
const Application::UserModelId* appId,
|
||||
const std::string_view callerFuncName,
|
||||
const std::string_view callerCtxName) noexcept(false)
|
||||
{
|
||||
CComPtr<ICustomDestinationList> result = nullptr;
|
||||
|
||||
auto hr = result.CoCreateInstance(
|
||||
CLSID_DestinationList,
|
||||
nullptr,
|
||||
CLSCTX_INPROC
|
||||
);
|
||||
|
||||
if (hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"CoCreateInstance(CLSID_DestinationList) failed",
|
||||
callerFuncName,
|
||||
callerCtxName
|
||||
);
|
||||
|
||||
assert( (result != nullptr) );
|
||||
|
||||
if (appId != nullptr)
|
||||
{
|
||||
if (hr = result->SetAppID(appId->c_str()); hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"ICustomDestinationList::SetAppID failed",
|
||||
callerFuncName,
|
||||
callerCtxName
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] CComPtr<IApplicationDestinations> createRecentsAndFrequentsHandle(
|
||||
const Application::UserModelId* appId,
|
||||
const std::string_view callerFuncName,
|
||||
const std::string_view callerCtxName) noexcept(false)
|
||||
{
|
||||
CComPtr<IApplicationDestinations> result = nullptr;
|
||||
|
||||
auto hr = result.CoCreateInstance(
|
||||
CLSID_ApplicationDestinations,
|
||||
nullptr,
|
||||
CLSCTX_INPROC
|
||||
);
|
||||
|
||||
if (hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"CoCreateInstance(CLSID_ApplicationDestinations) failed",
|
||||
callerFuncName,
|
||||
callerCtxName
|
||||
);
|
||||
|
||||
assert( (result != nullptr) );
|
||||
|
||||
if (appId != nullptr)
|
||||
{
|
||||
if (hr = result->SetAppID(appId->c_str()); hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"IApplicationDestinations::SetAppID failed",
|
||||
callerFuncName,
|
||||
callerCtxName
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void appendCustomCategoryTo(
|
||||
ICustomDestinationList& jumpListHandle,
|
||||
const WideString& categoryName,
|
||||
const JumpList::CustomCategoryContainer& items,
|
||||
const std::string_view callerFuncName,
|
||||
const std::string_view callerCtxName,
|
||||
COMIsInitializedInThisThreadTag com) noexcept(false)
|
||||
{
|
||||
if (items.empty())
|
||||
return;
|
||||
|
||||
const auto nativeContainer = createCategoryNativeContainer(callerFuncName, callerCtxName);
|
||||
|
||||
for (const auto& item: items)
|
||||
insertToCategoryNativeContainer(*nativeContainer, item, callerFuncName, callerCtxName, com);
|
||||
|
||||
if (const auto hr = jumpListHandle.AppendCategory(categoryName.c_str(), nativeContainer); hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"ICustomDestinationList::AppendCategory failed",
|
||||
callerFuncName,
|
||||
callerCtxName
|
||||
);
|
||||
}
|
||||
|
||||
void appendUserTasksTo(
|
||||
ICustomDestinationList& jumpListHandle,
|
||||
const JumpList::UserTasksCategoryContainer& items,
|
||||
const std::string_view callerFuncName,
|
||||
const std::string_view callerCtxName,
|
||||
COMIsInitializedInThisThreadTag com) noexcept(false)
|
||||
{
|
||||
if (items.empty())
|
||||
return;
|
||||
|
||||
const auto nativeContainer = createCategoryNativeContainer(callerFuncName, callerCtxName);
|
||||
|
||||
for (const auto& item: items)
|
||||
insertToCategoryNativeContainer(*nativeContainer, item, callerFuncName, callerCtxName, com);
|
||||
|
||||
if (const auto hr = jumpListHandle.AddUserTasks(nativeContainer); hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"ICustomDestinationList::AddUserTasks failed",
|
||||
callerFuncName,
|
||||
callerCtxName
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
// ================================================================================================================
|
||||
// Application
|
||||
// ================================================================================================================
|
||||
|
||||
static constexpr std::string_view applicationCtxName = "intellij::ui::win::Application";
|
||||
|
||||
|
||||
Application::Application() noexcept = default;
|
||||
|
||||
Application::~Application() noexcept = default;
|
||||
|
||||
|
||||
Application& Application::getInstance() noexcept
|
||||
{
|
||||
static Application instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
void Application::setAppUserModelId(const UserModelId& appId) noexcept(false)
|
||||
{
|
||||
if (const auto hr = SetCurrentProcessExplicitAppUserModelID(appId.c_str()); hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"SetCurrentProcessExplicitAppUserModelID failed",
|
||||
__func__,
|
||||
applicationCtxName
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void Application::registerRecentlyUsed(const std::filesystem::path& path) noexcept(false)
|
||||
{
|
||||
SHAddToRecentDocs(SHARD_PATHW, path.c_str());
|
||||
}
|
||||
|
||||
|
||||
void Application::registerRecentlyUsed(
|
||||
const JumpItem& recentJumpItem, COMIsInitializedInThisThreadTag com) noexcept(false)
|
||||
{
|
||||
refreshJumpListHandle();
|
||||
auto& [appId_, jumpListHandle_] = idAndJumpListHandle_;
|
||||
|
||||
const auto itemHandle = recentJumpItem.shareNativeHandle(com);
|
||||
|
||||
if (appId_.has_value())
|
||||
{
|
||||
SHARDAPPIDINFO jumpItemInfo{};
|
||||
jumpItemInfo.psi = itemHandle;
|
||||
jumpItemInfo.pszAppID = appId_->c_str();
|
||||
|
||||
SHAddToRecentDocs(SHARD_APPIDINFO, &jumpItemInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
SHAddToRecentDocs(SHARD_SHELLITEM, itemHandle);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::registerRecentlyUsed(
|
||||
const JumpTask& recentJumpTask,
|
||||
COMIsInitializedInThisThreadTag com) noexcept(false)
|
||||
{
|
||||
refreshJumpListHandle();
|
||||
auto& [appId_, jumpListHandle_] = idAndJumpListHandle_;
|
||||
|
||||
const auto taskHandle = recentJumpTask.shareNativeHandle(com);
|
||||
|
||||
if (appId_.has_value())
|
||||
{
|
||||
SHARDAPPIDINFOLINK jumpTaskInfo{};
|
||||
jumpTaskInfo.psl = taskHandle;
|
||||
jumpTaskInfo.pszAppID = appId_->c_str();
|
||||
|
||||
SHAddToRecentDocs(SHARD_APPIDINFOLINK, &jumpTaskInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
SHAddToRecentDocs(SHARD_LINK, taskHandle);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::clearRecentsAndFrequents(COMIsInitializedInThisThreadTag) noexcept(false)
|
||||
{
|
||||
refreshJumpListHandle();
|
||||
|
||||
auto& [appId_, jumpListHandle_] = idAndJumpListHandle_;
|
||||
|
||||
const auto rfHandle = createRecentsAndFrequentsHandle(
|
||||
(appId_.has_value() ? &(*appId_) : nullptr),
|
||||
__func__,
|
||||
applicationCtxName
|
||||
);
|
||||
|
||||
if (const auto hr = rfHandle->RemoveAllDestinations(); hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"IApplicationDestinations::RemoveAllDestinations failed",
|
||||
__func__,
|
||||
applicationCtxName
|
||||
);
|
||||
|
||||
// clears all usage data on all Recent items
|
||||
SHAddToRecentDocs(SHARD_PIDL, nullptr);
|
||||
}
|
||||
|
||||
|
||||
void Application::setJumpList(const JumpList& jumpList, COMIsInitializedInThisThreadTag com) noexcept(false)
|
||||
{
|
||||
deleteJumpList(com); // implies refreshJumpListHandle
|
||||
|
||||
auto& [appId_, jumpListHandle_] = idAndJumpListHandle_;
|
||||
|
||||
if (jumpListHandle_ == nullptr)
|
||||
throw std::logic_error("Application::setJumpList: jumpListHandle_ can not be nullptr");
|
||||
|
||||
JumpListTransaction tr{jumpListHandle_};
|
||||
|
||||
appendUserTasksTo(*tr, jumpList.getUserTasksCategory(), __func__, applicationCtxName, com);
|
||||
|
||||
const auto& allCustomCategories = jumpList.getAllCustomCategories();
|
||||
for (const auto& customCategory : allCustomCategories)
|
||||
appendCustomCategoryTo(*tr, customCategory.first, customCategory.second, __func__, applicationCtxName, com);
|
||||
|
||||
if (jumpList.isRecentCategoryVisible())
|
||||
{
|
||||
if (const auto hr = tr->AppendKnownCategory(KDC_RECENT); hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"ICustomDestinationList::AppendKnownCategory(KDC_RECENT) failed",
|
||||
__func__,
|
||||
applicationCtxName
|
||||
);
|
||||
}
|
||||
|
||||
if (jumpList.isFrequentCategoryVisible())
|
||||
{
|
||||
if (const auto hr = tr->AppendKnownCategory(KDC_FREQUENT); hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"ICustomDestinationList::AppendKnownCategory(KDC_FREQUENT) failed",
|
||||
__func__,
|
||||
applicationCtxName
|
||||
);
|
||||
}
|
||||
|
||||
tr.commit();
|
||||
}
|
||||
|
||||
|
||||
void Application::deleteJumpList(COMIsInitializedInThisThreadTag) noexcept(false)
|
||||
{
|
||||
refreshJumpListHandle();
|
||||
|
||||
auto& [appId_, jumpListHandle_] = idAndJumpListHandle_;
|
||||
|
||||
auto* const appIdCStr = appId_.has_value() ? appId_->c_str() : nullptr;
|
||||
|
||||
if (const auto hr = jumpListHandle_->DeleteList(appIdCStr); hr != S_OK)
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"ICustomDestinationList::DeleteList failed",
|
||||
__func__,
|
||||
applicationCtxName
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] std::optional<Application::UserModelId> Application::obtainAppUserModelId() const noexcept(false)
|
||||
{
|
||||
struct COMStr
|
||||
{
|
||||
PWSTR data = nullptr;
|
||||
~COMStr()
|
||||
{
|
||||
if (data != nullptr)
|
||||
{
|
||||
CoTaskMemFree(data);
|
||||
}
|
||||
}
|
||||
} comStr;
|
||||
|
||||
if (const auto hr = GetCurrentProcessExplicitAppUserModelID(&comStr.data); hr != S_OK)
|
||||
{
|
||||
// E_FAIL is returned when appId was not set (via SetCurrentProcessExplicitAppUserModelID)
|
||||
if (hr == E_FAIL)
|
||||
return std::nullopt;
|
||||
|
||||
errors::throwCOMException(
|
||||
hr,
|
||||
"GetCurrentProcessExplicitAppUserModelID failed",
|
||||
__func__,
|
||||
applicationCtxName
|
||||
);
|
||||
}
|
||||
|
||||
assert( (comStr.data != nullptr) );
|
||||
|
||||
return comStr.data;
|
||||
}
|
||||
|
||||
|
||||
void Application::refreshJumpListHandle() noexcept(false)
|
||||
{
|
||||
auto newAppId = obtainAppUserModelId();
|
||||
|
||||
auto& [appId_, jumpListHandle_] = idAndJumpListHandle_;
|
||||
|
||||
if ( (jumpListHandle_ == nullptr) || (appId_ != newAppId) )
|
||||
{
|
||||
jumpListHandle_ = createJumpListHandle(
|
||||
(newAppId.has_value() ? &(*newAppId) : nullptr),
|
||||
__func__,
|
||||
applicationCtxName
|
||||
);
|
||||
appId_ = std::move(newAppId);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace intellij::ui::win
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
#include "winshellintegration/jump_item.h"
|
||||
#include "winshellintegration/COM_errors.h" // errors::throwCOMException
|
||||
|
||||
|
||||
namespace intellij::ui::win
|
||||
{
|
||||
static constexpr std::string_view jumpItemCtxName = "intellij::ui::win::JumpItem";
|
||||
|
||||
|
||||
JumpItem::JumpItem(std::filesystem::path path, COMIsInitializedInThisThreadTag com) noexcept(false)
|
||||
: handle_(createHandleFrom(path, com))
|
||||
, path_(std::move(path))
|
||||
{}
|
||||
|
||||
JumpItem::JumpItem(JumpItem&& src) noexcept = default;
|
||||
|
||||
|
||||
JumpItem& JumpItem::operator=(JumpItem&& rhs) noexcept = default;
|
||||
|
||||
|
||||
JumpItem::SharedNativeHandle JumpItem::shareNativeHandle(COMIsInitializedInThisThreadTag) const noexcept(false)
|
||||
{
|
||||
return handle_;
|
||||
}
|
||||
|
||||
const std::filesystem::path& JumpItem::getPath() const noexcept
|
||||
{
|
||||
return path_;
|
||||
}
|
||||
|
||||
|
||||
CComPtr<IShellItem> JumpItem::createHandleFrom(
|
||||
const std::filesystem::path& path,
|
||||
COMIsInitializedInThisThreadTag) noexcept(false)
|
||||
{
|
||||
static_assert(std::is_same_v<std::filesystem::path::value_type, WCHAR>, "std::filesystem::path must use WCHARs");
|
||||
|
||||
CComPtr<IShellItem> result;
|
||||
|
||||
auto comResult = SHCreateItemFromParsingName(path.c_str(), nullptr, IID_PPV_ARGS(&result));
|
||||
|
||||
if (comResult != S_OK)
|
||||
errors::throwCOMException(comResult,
|
||||
"SHCreateItemFromParsingName failed",
|
||||
__func__,
|
||||
jumpItemCtxName
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace intellij::ui::win
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
#include "winshellintegration/jump_list.h"
|
||||
|
||||
|
||||
namespace intellij::ui::win
|
||||
{
|
||||
|
||||
JumpList::JumpList() noexcept
|
||||
: isRecentCategoryVisible_(false)
|
||||
, isFrequentCategoryVisible_(false)
|
||||
{}
|
||||
|
||||
JumpList::JumpList(JumpList&&) = default;
|
||||
|
||||
|
||||
JumpList::~JumpList() noexcept = default;
|
||||
|
||||
|
||||
JumpList& JumpList::operator=(JumpList&&) = default;
|
||||
|
||||
|
||||
JumpList& JumpList::setRecentCategoryVisible(bool visible) noexcept
|
||||
{
|
||||
isRecentCategoryVisible_ = visible;
|
||||
return *this;
|
||||
}
|
||||
|
||||
JumpList& JumpList::setFrequentCategoryVisible(bool visible) noexcept
|
||||
{
|
||||
isFrequentCategoryVisible_ = visible;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
JumpList& JumpList::appendToUserTasks(JumpTask jumpTask, COMIsInitializedInThisThreadTag)
|
||||
{
|
||||
userTasksCategory_.emplace_back(std::move(jumpTask));
|
||||
return *this;
|
||||
}
|
||||
|
||||
JumpList& JumpList::appendToUserTasks(JumpItem jumpItem, COMIsInitializedInThisThreadTag)
|
||||
{
|
||||
userTasksCategory_.emplace_back(std::move(jumpItem));
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
JumpList& JumpList::appendToCustomCategory(const WideStringView categoryName, JumpItem jumpItem, COMIsInitializedInThisThreadTag)
|
||||
{
|
||||
auto* category = findCustomCategory(categoryName);
|
||||
if (category == nullptr)
|
||||
category = &customCategories_.emplace_back(WideString{categoryName}, CustomCategoryContainer{}).second;
|
||||
|
||||
category->emplace_back(std::move(jumpItem));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
JumpList& JumpList::appendToCustomCategory(const WideStringView categoryName, JumpTask jumpTask, COMIsInitializedInThisThreadTag)
|
||||
{
|
||||
auto* category = findCustomCategory(categoryName);
|
||||
if (category == nullptr)
|
||||
category = &customCategories_.emplace_back(WideString{categoryName}, CustomCategoryContainer{}).second;
|
||||
|
||||
category->emplace_back(std::move(jumpTask));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
bool JumpList::isRecentCategoryVisible() const noexcept
|
||||
{
|
||||
return isRecentCategoryVisible_;
|
||||
}
|
||||
|
||||
bool JumpList::isFrequentCategoryVisible() const noexcept
|
||||
{
|
||||
return isFrequentCategoryVisible_;
|
||||
}
|
||||
|
||||
|
||||
const JumpList::UserTasksCategoryContainer& JumpList::getUserTasksCategory() const noexcept
|
||||
{
|
||||
return userTasksCategory_;
|
||||
}
|
||||
|
||||
const JumpList::CustomCategoriesContainer& JumpList::getAllCustomCategories() const noexcept
|
||||
{
|
||||
return customCategories_;
|
||||
}
|
||||
|
||||
|
||||
/// returns nullptr if a custom category with the passed name is not found
|
||||
const JumpList::CustomCategoryContainer* JumpList::findCustomCategory(WideStringView categoryName) const noexcept
|
||||
{
|
||||
for (const auto& [itCategoryName, itCategoryItems]: customCategories_)
|
||||
if (categoryName == itCategoryName)
|
||||
return &itCategoryItems;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// returns nullptr if a custom category with the passed name is not found
|
||||
JumpList::CustomCategoryContainer* JumpList::findCustomCategory(WideStringView categoryName) noexcept
|
||||
{
|
||||
for (auto& [itCategoryName, itCategoryItems]: customCategories_)
|
||||
if (categoryName == itCategoryName)
|
||||
return &itCategoryItems;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace intellij::ui::win
|
||||
@@ -0,0 +1,206 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
#include "winshellintegration/jump_task.h"
|
||||
#include "winshellintegration/COM_errors.h" // errors::throwCOMException
|
||||
#include <string_view> // std::string_view
|
||||
#include <type_traits> // std::is_same_v
|
||||
#include <cassert> // assert
|
||||
|
||||
|
||||
namespace intellij::ui::win
|
||||
{
|
||||
|
||||
// ================================================================================================================
|
||||
// JumpTask
|
||||
// ================================================================================================================
|
||||
|
||||
JumpTask::JumpTask(CComPtr<IShellLinkW>&& nativeHandle, COMIsInitializedInThisThreadTag) noexcept
|
||||
: handle_(std::move(nativeHandle))
|
||||
{}
|
||||
|
||||
JumpTask::JumpTask(JumpTask &&other) noexcept = default;
|
||||
|
||||
|
||||
JumpTask::BuildSession JumpTask::startBuilding(std::filesystem::path appPath, WideString title) noexcept(false)
|
||||
{
|
||||
return { std::move(appPath), std::move(title) };
|
||||
}
|
||||
|
||||
|
||||
JumpTask::~JumpTask() noexcept = default;
|
||||
|
||||
|
||||
JumpTask& JumpTask::operator=(JumpTask&& lhs) = default;
|
||||
|
||||
|
||||
JumpTask::SharedNativeHandle JumpTask::shareNativeHandle(COMIsInitializedInThisThreadTag) const noexcept(false)
|
||||
{
|
||||
return handle_;
|
||||
}
|
||||
|
||||
|
||||
// ================================================================================================================
|
||||
// JumpTask::BuildSession
|
||||
// ================================================================================================================
|
||||
|
||||
static constexpr std::string_view buildSessionCtxName = "intellij::ui::JumpTask::BuildSession";
|
||||
|
||||
|
||||
JumpTask::BuildSession::BuildSession(std::filesystem::path appPath, WideString title) noexcept(false)
|
||||
: appPath_(std::move(appPath))
|
||||
, title_(std::move(title))
|
||||
{
|
||||
if (appPath_.empty())
|
||||
throw std::runtime_error{
|
||||
"Attempting to construct JumpTask::BuildSession with an empty path to the application"
|
||||
};
|
||||
|
||||
if (title_.empty())
|
||||
throw std::runtime_error{
|
||||
"Attempting to construct JumpTask::BuildSession with an empty title"
|
||||
};
|
||||
}
|
||||
|
||||
//JumpTask::BuildSession::BuildSession(BuildSession&&) noexcept = default;
|
||||
//JumpTask::BuildSession& JumpTask::BuildSession::operator=(BuildSession&&) noexcept = default;
|
||||
|
||||
|
||||
JumpTask::BuildSession& JumpTask::BuildSession::setApplicationArguments(WideString allArgs) noexcept
|
||||
{
|
||||
appArguments_ = std::move(allArgs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
JumpTask::BuildSession& JumpTask::BuildSession::setApplicationWorkingDirectory(std::filesystem::path wdPath) noexcept
|
||||
{
|
||||
appWorkDir_ = std::move(wdPath);
|
||||
return *this;
|
||||
}
|
||||
|
||||
JumpTask::BuildSession& JumpTask::BuildSession::setDescription(WideString description) noexcept
|
||||
{
|
||||
description = std::move(description);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
JumpTask JumpTask::BuildSession::buildTask(COMIsInitializedInThisThreadTag com) const noexcept(false)
|
||||
{
|
||||
auto result = createJumpTask(com);
|
||||
|
||||
copyAppPathToJumpTask(result);
|
||||
copyAppArgsToJumpTask(result);
|
||||
copyWorkDirToJumpTask(result);
|
||||
copyTitleToJumpTask(result);
|
||||
copyDescriptionToJumpTask(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
JumpTask JumpTask::BuildSession::createJumpTask(COMIsInitializedInThisThreadTag com) const noexcept(false)
|
||||
{
|
||||
CComPtr<IShellLinkW> nativeHandle;
|
||||
|
||||
auto comResult = nativeHandle.CoCreateInstance(
|
||||
// request IShellLink instance
|
||||
CLSID_ShellLink,
|
||||
|
||||
// ...that is not owned by IUnknown s
|
||||
nullptr,
|
||||
|
||||
// The code that creates and manages objects of this class
|
||||
// is a DLL that runs in the same process as the caller of
|
||||
// the function specifying the class context.
|
||||
CLSCTX_INPROC
|
||||
);
|
||||
|
||||
if (comResult != S_OK)
|
||||
errors::throwCOMException(comResult, "CoCreateInstance failed", __func__, buildSessionCtxName);
|
||||
|
||||
return JumpTask{ std::move(nativeHandle), com };
|
||||
}
|
||||
|
||||
void JumpTask::BuildSession::copyAppPathToJumpTask(JumpTask& task) const noexcept(false)
|
||||
{
|
||||
static_assert(std::is_same_v<std::filesystem::path::value_type, WCHAR>, "std::filesystem::path must use WCHARs");
|
||||
|
||||
if (const auto comResult = task.handle_->SetPath(appPath_.c_str()); comResult != S_OK)
|
||||
errors::throwCOMException(comResult, "IShellLinkW::SetPath failed", __func__, buildSessionCtxName);
|
||||
}
|
||||
|
||||
void JumpTask::BuildSession::copyAppArgsToJumpTask(JumpTask& task) const noexcept(false)
|
||||
{
|
||||
if (appArguments_.has_value())
|
||||
{
|
||||
if (const auto comResult = task.handle_->SetArguments(appArguments_->c_str()); comResult != S_OK)
|
||||
errors::throwCOMException(comResult, "IShellLinkW::SetArguments failed", __func__, buildSessionCtxName);
|
||||
}
|
||||
}
|
||||
|
||||
void JumpTask::BuildSession::copyWorkDirToJumpTask(JumpTask& task) const noexcept(false)
|
||||
{
|
||||
static_assert(std::is_same_v<std::filesystem::path::value_type, WCHAR>, "std::filesystem::path must use WCHARs");
|
||||
|
||||
if (appWorkDir_.has_value())
|
||||
{
|
||||
if (const auto comResult = task.handle_->SetWorkingDirectory(appWorkDir_->c_str()); comResult != S_OK)
|
||||
errors::throwCOMException(comResult, "IShellLinkW::SetWorkingDirectory failed", __func__, buildSessionCtxName);
|
||||
}
|
||||
}
|
||||
|
||||
void JumpTask::BuildSession::copyTitleToJumpTask(JumpTask& task) const noexcept(false)
|
||||
{
|
||||
constexpr std::string_view funcName = __func__;
|
||||
|
||||
const auto properties = [&task, funcName] {
|
||||
CComPtr<IPropertyStore> result;
|
||||
if (const auto comResult = task.handle_.QueryInterface(&result); comResult != S_OK)
|
||||
errors::throwCOMException(
|
||||
comResult,
|
||||
"QueryInterface failed",
|
||||
funcName,
|
||||
buildSessionCtxName
|
||||
);
|
||||
return result;
|
||||
}();
|
||||
assert( (properties != nullptr) );
|
||||
|
||||
const struct TitleProperty
|
||||
{
|
||||
PROPVARIANT value{};
|
||||
|
||||
explicit TitleProperty(const WideString& title) noexcept(false)
|
||||
{
|
||||
if (const auto comResult = InitPropVariantFromString(title.c_str(), &value); comResult != S_OK)
|
||||
errors::throwCOMException(
|
||||
comResult,
|
||||
"InitPropVariantFromString failed",
|
||||
__func__,
|
||||
buildSessionCtxName
|
||||
);
|
||||
}
|
||||
|
||||
~TitleProperty() noexcept
|
||||
{
|
||||
assert( (PropVariantClear(&value) == S_OK) );
|
||||
}
|
||||
} titleProperty(title_);
|
||||
|
||||
if (const auto comResult = properties->SetValue(PKEY_Title, titleProperty.value); comResult != S_OK)
|
||||
errors::throwCOMException(comResult, "IPropertyStore::SetValue failed", funcName, buildSessionCtxName);
|
||||
|
||||
if (const auto comResult = properties->Commit(); comResult != S_OK)
|
||||
errors::throwCOMException(comResult, "IPropertyStore::Commit failed", funcName, buildSessionCtxName);
|
||||
}
|
||||
|
||||
void JumpTask::BuildSession::copyDescriptionToJumpTask(JumpTask& task) const noexcept(false)
|
||||
{
|
||||
if (description_.has_value())
|
||||
{
|
||||
if (const auto comResult = task.handle_->SetDescription(description_->c_str()); comResult != S_OK)
|
||||
errors::throwCOMException(comResult, "IShellLinkW::SetDescription failed", __func__, buildSessionCtxName);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace intellij::ui::win
|
||||
@@ -0,0 +1,40 @@
|
||||
# Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
if (NOT JDK_PATH)
|
||||
message(SEND_ERROR "Path to the JDK is not specified. winshellintegrationbridge target will not be generated.")
|
||||
endif()
|
||||
|
||||
add_library(winshellintegrationbridge SHARED
|
||||
"COM_guard.h"
|
||||
"COM_guard.cpp"
|
||||
|
||||
"com_intellij_ui_win_RecentTasks.h"
|
||||
"com_intellij_ui_win_RecentTasks.cpp"
|
||||
|
||||
"dllmain.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(winshellintegrationbridge
|
||||
PRIVATE "${JDK_PATH}/include"
|
||||
PRIVATE "${JDK_PATH}/include/win32"
|
||||
)
|
||||
|
||||
target_link_libraries(winshellintegrationbridge
|
||||
PRIVATE "winshellintegration"
|
||||
PRIVATE "Ole32.lib"
|
||||
)
|
||||
|
||||
if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
|
||||
set_target_properties(winshellintegrationbridge PROPERTIES
|
||||
OUTPUT_NAME "winshellintegrationbridge64"
|
||||
)
|
||||
else()
|
||||
set_target_properties(winshellintegrationbridge PROPERTIES
|
||||
OUTPUT_NAME "winshellintegrationbridge"
|
||||
)
|
||||
endif()
|
||||
|
||||
include("${PROJECT_SOURCE_DIR}/src/build/EnableCompilerExtraWarnings.cmake")
|
||||
enable_target_compile_extra_warnings(winshellintegrationbridge)
|
||||
|
||||
# TODO: insert copyrights to DLL->Properties->Details
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
#include "COM_guard.h"
|
||||
#include "winshellintegration/COM_errors.h" // errors::throwCOMException
|
||||
#include <Objbase.h> // CoInitializeEx, CoUninitialize
|
||||
|
||||
namespace intellij::ui::win
|
||||
{
|
||||
// ================================================================================================================
|
||||
// COMGuard::Impl
|
||||
// ================================================================================================================
|
||||
|
||||
struct COMGuard::Impl final
|
||||
{
|
||||
explicit Impl(DWORD initFlags) noexcept(false)
|
||||
{
|
||||
if (const auto comResult = CoInitializeEx(nullptr, initFlags); comResult != S_OK)
|
||||
{
|
||||
// S_FALSE means the COM library is already initialized on this thread
|
||||
if (comResult != S_FALSE)
|
||||
errors::throwCOMException(
|
||||
comResult,
|
||||
"CoInitializeEx failed",
|
||||
__func__,
|
||||
"intellij::ui::win::COMGuard::Impl"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
~Impl() noexcept
|
||||
{
|
||||
CoUninitialize();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// ================================================================================================================
|
||||
// COMGuard
|
||||
// ================================================================================================================
|
||||
|
||||
COMGuard::COMGuard(DWORD initFlags) noexcept(false)
|
||||
: impl_(std::make_shared<Impl>(initFlags))
|
||||
{}
|
||||
|
||||
|
||||
COMGuard::COMGuard(const COMGuard&) noexcept = default;
|
||||
|
||||
COMGuard::COMGuard(COMGuard&& other) noexcept
|
||||
: COMGuard(static_cast<const COMGuard&>(other)) // avoid "empty" state of some instance of COMGuard
|
||||
{}
|
||||
|
||||
|
||||
COMGuard& COMGuard::operator=(const COMGuard&) noexcept = default;
|
||||
|
||||
COMGuard& COMGuard::operator=(COMGuard&& rhs) noexcept
|
||||
{
|
||||
*this = static_cast<const COMGuard&>(rhs); // avoid "empty" state of some instance of COMGuard
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
COMGuard::~COMGuard() noexcept = default;
|
||||
|
||||
} // namespace intellij::ui::win
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* RAII wrapper around CoInitializeEx, CoUninitialize.
|
||||
*
|
||||
* @author Nikita Provotorov
|
||||
*/
|
||||
|
||||
#ifndef WINSHELLINTEGRATIONBRIDGE_COM_GUARD_H
|
||||
#define WINSHELLINTEGRATIONBRIDGE_COM_GUARD_H
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif // ndef WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h> // DWORD
|
||||
#include <memory> // std::shared_ptr
|
||||
|
||||
|
||||
namespace intellij::ui::win
|
||||
{
|
||||
|
||||
/// RAII wrapper around CoInitializeEx, CoUninitialize
|
||||
class COMGuard
|
||||
{
|
||||
public: // ctors/dtor
|
||||
/// Initializes the COM library via CoInitializeEx function.
|
||||
///
|
||||
/// @param[in] initFlags - see possible values at CoInitializeEx's MSDN
|
||||
/// (https://docs.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex)
|
||||
///
|
||||
/// @exception std::system_error - if CoInitializeEx failed
|
||||
/// @exception other exceptions from std namespace - in case of some internal errors
|
||||
explicit COMGuard(DWORD initFlags) noexcept(false);
|
||||
|
||||
COMGuard(const COMGuard&) noexcept;
|
||||
/// @warning COPIES the passed object, DOES NOT MOVE it (for avoiding "empty" COMGuard instances)
|
||||
COMGuard(COMGuard&&) noexcept;
|
||||
|
||||
~COMGuard() noexcept;
|
||||
|
||||
public: // assignments
|
||||
COMGuard& operator=(const COMGuard&) noexcept;
|
||||
/// @warning COPIES the passed object, DOES NOT MOVE it (for avoiding "empty" COMGuard instances)
|
||||
COMGuard& operator=(COMGuard&&) noexcept;
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
|
||||
std::shared_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace intellij::ui::win
|
||||
|
||||
|
||||
#endif // ndef WINSHELLINTEGRATIONBRIDGE_COM_GUARD_H
|
||||
@@ -0,0 +1,602 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* Implementation of JNI methods of com.intellij.ui.win.RecentTasks class.
|
||||
*
|
||||
* @author Nikita Provotorov
|
||||
*/
|
||||
|
||||
#include "com_intellij_ui_win_RecentTasks.h"
|
||||
#include "COM_guard.h" // COMGuard
|
||||
#include "winshellintegration.h" // intellij::ui::win::*
|
||||
#include <string_view> // std::string_view
|
||||
#include <thread> // std::thread::id
|
||||
#include <stdexcept> // std::system_error, std::runtime_error, std::logic_error, std::exception
|
||||
#include <optional> // std::optional
|
||||
#include <vector> // std::vector
|
||||
#include <sstream> // std::stringstream
|
||||
#include <type_traits> // std::decay_t, std::is_base_of
|
||||
#include <cassert> // assert
|
||||
|
||||
|
||||
namespace intellij::ui::win::jni
|
||||
{
|
||||
|
||||
class RecentTasks final
|
||||
{
|
||||
private:
|
||||
struct PrivateCtorTag final { explicit PrivateCtorTag() noexcept = default; };
|
||||
|
||||
public:
|
||||
template<typename... Ts>
|
||||
RecentTasks(PrivateCtorTag, Ts&&... args)
|
||||
: RecentTasks(std::forward<Ts>(args)...)
|
||||
{}
|
||||
|
||||
public:
|
||||
static void initialize(JNIEnv* jEnv, jclass jClass, jstring jAppId) noexcept;
|
||||
|
||||
static void addTasksNativeForCategory(
|
||||
JNIEnv* jEnv,
|
||||
jclass jClass,
|
||||
jstring jCategoryName,
|
||||
jobjectArray jTasks
|
||||
) noexcept;
|
||||
|
||||
static jstring getShortenPath(JNIEnv* jEnv, jclass jClass, jstring jGeneralPath) noexcept;
|
||||
|
||||
static void clearNative(JNIEnv* jEnv, jclass jClass) noexcept;
|
||||
|
||||
private:
|
||||
static std::optional<RecentTasks>& accessStorage() noexcept(false);
|
||||
static RecentTasks& getInstance() noexcept(false);
|
||||
|
||||
private: // exception handling
|
||||
template<typename E>
|
||||
static void handleException(
|
||||
const E& exception,
|
||||
JNIEnv* jEnv,
|
||||
std::string_view pass_func_here /* __func__ */
|
||||
) noexcept;
|
||||
|
||||
static void handleUnknownException(JNIEnv* jEnv, std::string_view pass_func_here /* __func__ */) noexcept;
|
||||
|
||||
private:
|
||||
static constexpr std::string_view thisCtxName = "intellij::ui::win::jni::RecentTasks";
|
||||
|
||||
private: // impl
|
||||
void addTasksNativeForCategoryImpl(
|
||||
JNIEnv* jEnv,
|
||||
jclass jClass,
|
||||
jstring jCategoryName,
|
||||
jobjectArray jTasks
|
||||
) noexcept(false);
|
||||
|
||||
jstring getShortenPathImpl(JNIEnv* jEnv, jclass jClass, jstring jGeneralPath) noexcept(false);
|
||||
|
||||
void clearNativeImpl(JNIEnv* jEnv, jclass jClass) noexcept(false);
|
||||
|
||||
private:
|
||||
RecentTasks() noexcept(false);
|
||||
|
||||
private:
|
||||
const COMGuard comGuard_;
|
||||
};
|
||||
|
||||
} // namespace intellij::ui::win::jni
|
||||
|
||||
|
||||
/*
|
||||
* Class: com_intellij_ui_win_RecentTasks
|
||||
* Method: initialize
|
||||
* Signature: (Ljava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_intellij_ui_win_RecentTasks_initialize(
|
||||
JNIEnv* jEnv,
|
||||
jclass jClass,
|
||||
jstring jAppId)
|
||||
{
|
||||
return (void)intellij::ui::win::jni::RecentTasks::initialize(jEnv, jClass, jAppId);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: com_intellij_ui_win_RecentTasks
|
||||
* Method: addTasksNativeForCategory
|
||||
* Signature: (Ljava/lang/String;[Lcom/intellij/ui/win/Task;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_intellij_ui_win_RecentTasks_addTasksNativeForCategory(
|
||||
JNIEnv* jEnv,
|
||||
jclass jClass,
|
||||
jstring jCategoryName,
|
||||
jobjectArray jTasks)
|
||||
{
|
||||
return (void)intellij::ui::win::jni::RecentTasks::addTasksNativeForCategory(jEnv, jClass, jCategoryName, jTasks);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: com_intellij_ui_win_RecentTasks
|
||||
* Method: getShortenPath
|
||||
* Signature: (Ljava/lang/String;)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_intellij_ui_win_RecentTasks_getShortenPath(
|
||||
JNIEnv* jEnv,
|
||||
jclass jClass,
|
||||
jstring jGeneralPath)
|
||||
{
|
||||
return intellij::ui::win::jni::RecentTasks::getShortenPath(jEnv, jClass, jGeneralPath);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: com_intellij_ui_win_RecentTasks
|
||||
* Method: clearNative
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_intellij_ui_win_RecentTasks_clearNative(
|
||||
JNIEnv* jEnv,
|
||||
jclass jClass)
|
||||
{
|
||||
return (void)intellij::ui::win::jni::RecentTasks::clearNative(jEnv, jClass);
|
||||
}
|
||||
|
||||
|
||||
namespace intellij::ui::win::jni
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void ensureJNINoErrors(JNIEnv& jEnv) noexcept(false)
|
||||
{
|
||||
if (auto err = jEnv.ExceptionOccurred(); err != nullptr)
|
||||
throw err;
|
||||
}
|
||||
|
||||
|
||||
WideString jStringToWideString(JNIEnv *jEnv, jstring jStr)
|
||||
{
|
||||
static_assert(
|
||||
sizeof(jchar) == sizeof(WideString::value_type),
|
||||
"Implementation relies on sizeof(jchar) == sizeof(WideString::value_type)"
|
||||
);
|
||||
|
||||
const auto resultLength = jEnv->GetStringLength(jStr);
|
||||
|
||||
WideString result;
|
||||
|
||||
result.resize(resultLength + 1, 0);
|
||||
|
||||
jEnv->GetStringRegion(jStr, 0, resultLength, reinterpret_cast<jchar *>(result.data()));
|
||||
ensureJNINoErrors(*jEnv);
|
||||
|
||||
result.resize(resultLength);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
std::string&& cleanExceptionDescription(std::string&& description)
|
||||
{
|
||||
// remove \r\n and \n at the end of the description
|
||||
while (!description.empty())
|
||||
{
|
||||
if (description.back() != '\n')
|
||||
break;
|
||||
|
||||
description.pop_back();
|
||||
|
||||
if (description.empty())
|
||||
break;
|
||||
|
||||
if (description.back() == '\r')
|
||||
description.pop_back();
|
||||
}
|
||||
|
||||
// remove \r\n and \n at the start of the description
|
||||
std::string::size_type prefixToRemoveLength = 0;
|
||||
while (prefixToRemoveLength < description.size())
|
||||
{
|
||||
if ( (description[prefixToRemoveLength] != '\r') && (description[prefixToRemoveLength] != '\n') )
|
||||
break;
|
||||
|
||||
++prefixToRemoveLength;
|
||||
}
|
||||
description.erase(0, prefixToRemoveLength);
|
||||
|
||||
return std::move(description);
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
|
||||
RecentTasks::RecentTasks() noexcept(false)
|
||||
: comGuard_(COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE | COINIT_SPEED_OVER_MEMORY /*NOLINT*/)
|
||||
{
|
||||
(void)Application::getInstance();
|
||||
}
|
||||
|
||||
|
||||
void RecentTasks::initialize(
|
||||
JNIEnv* jEnv,
|
||||
[[maybe_unused]] jclass jClass,
|
||||
[[maybe_unused]] jstring jAppId) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
assert( (jEnv != nullptr) );
|
||||
|
||||
auto& storage = accessStorage();
|
||||
|
||||
if (storage.has_value())
|
||||
throw std::logic_error("intellij::ui::win::jni::RecentTasks has already been initialized");
|
||||
|
||||
const auto appId = jStringToWideString(jEnv, jAppId);
|
||||
|
||||
if (Application::getInstance().obtainAppUserModelId() != appId)
|
||||
Application::getInstance().setAppUserModelId(appId);
|
||||
|
||||
storage.emplace(PrivateCtorTag{});
|
||||
}
|
||||
catch (const jthrowable& javaErrWillBeThrown)
|
||||
{
|
||||
jEnv->DeleteLocalRef(javaErrWillBeThrown);
|
||||
}
|
||||
catch (const std::system_error& err)
|
||||
{
|
||||
return (void)handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (const std::runtime_error& err)
|
||||
{
|
||||
return (void)handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (const std::logic_error& err)
|
||||
{
|
||||
return (void)handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
return (void)handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return (void)handleUnknownException(jEnv, __func__);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RecentTasks::addTasksNativeForCategory(
|
||||
JNIEnv* jEnv,
|
||||
jclass jClass,
|
||||
jstring jCategoryName,
|
||||
jobjectArray jTasks) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return (void)getInstance().addTasksNativeForCategoryImpl(jEnv, jClass, jCategoryName, jTasks);
|
||||
}
|
||||
catch (const jthrowable& javaErrWillBeThrown)
|
||||
{
|
||||
jEnv->DeleteLocalRef(javaErrWillBeThrown);
|
||||
}
|
||||
catch (const std::system_error& err)
|
||||
{
|
||||
handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (const std::runtime_error& err)
|
||||
{
|
||||
handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (const std::logic_error& err)
|
||||
{
|
||||
handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
handleUnknownException(jEnv, __func__);
|
||||
}
|
||||
}
|
||||
|
||||
void RecentTasks::addTasksNativeForCategoryImpl( // NOLINT(readability-convert-member-functions-to-static)
|
||||
JNIEnv* jEnv,
|
||||
[[maybe_unused]] jclass jClass,
|
||||
jstring jCategoryName,
|
||||
jobjectArray jTasks) noexcept(false)
|
||||
{
|
||||
assert( (jEnv != nullptr) );
|
||||
|
||||
ensureJNINoErrors(*jEnv);
|
||||
|
||||
auto categoryName = jStringToWideString(jEnv, jCategoryName);
|
||||
|
||||
const jsize tasksCount = jEnv->GetArrayLength(jTasks);
|
||||
|
||||
std::vector<JumpTask> nativeTasks;
|
||||
nativeTasks.reserve(tasksCount);
|
||||
|
||||
for (jsize i = 0; i < tasksCount; ++i)
|
||||
{
|
||||
auto jTask = jEnv->GetObjectArrayElement(jTasks, i);
|
||||
ensureJNINoErrors(*jEnv);
|
||||
|
||||
auto jTaskClass = jEnv->GetObjectClass(jTask);
|
||||
ensureJNINoErrors(*jEnv);
|
||||
|
||||
auto pathFieldId = jEnv->GetFieldID(jTaskClass, "path", "Ljava/lang/String;");
|
||||
ensureJNINoErrors(*jEnv);
|
||||
|
||||
auto argsFieldId = jEnv->GetFieldID(jTaskClass, "args", "Ljava/lang/String;");
|
||||
ensureJNINoErrors(*jEnv);
|
||||
|
||||
auto descriptionFieldId = jEnv->GetFieldID(jTaskClass, "description", "Ljava/lang/String;");
|
||||
ensureJNINoErrors(*jEnv);
|
||||
|
||||
auto jTaskPath = static_cast<jstring>(jEnv->GetObjectField(jTask, pathFieldId));
|
||||
ensureJNINoErrors(*jEnv);
|
||||
|
||||
auto jTaskArgs = static_cast<jstring>(jEnv->GetObjectField(jTask, argsFieldId));
|
||||
ensureJNINoErrors(*jEnv);
|
||||
|
||||
auto jTaskDescription = static_cast<jstring>(jEnv->GetObjectField(jTask, descriptionFieldId));
|
||||
ensureJNINoErrors(*jEnv);
|
||||
|
||||
auto nativeTaskPath = jStringToWideString(jEnv, jTaskPath);
|
||||
auto nativeTaskArgs = jStringToWideString(jEnv, jTaskArgs);
|
||||
auto nativeTaskDescription = jStringToWideString(jEnv, jTaskDescription);
|
||||
|
||||
// TODO: jEnv->DeleteLocalRef ?
|
||||
|
||||
nativeTasks.emplace_back(
|
||||
JumpTask::startBuilding(std::move(nativeTaskPath),
|
||||
std::move(nativeTaskDescription))
|
||||
.setApplicationArguments(std::move(nativeTaskArgs))
|
||||
.buildTask(COM_IS_INITIALIZED_IN_THIS_THREAD)
|
||||
);
|
||||
}
|
||||
|
||||
for (const auto& task : nativeTasks)
|
||||
Application::getInstance().registerRecentlyUsed(task, COM_IS_INITIALIZED_IN_THIS_THREAD);
|
||||
}
|
||||
|
||||
|
||||
jstring RecentTasks::getShortenPath(
|
||||
JNIEnv* jEnv,
|
||||
jclass jClass,
|
||||
jstring jGeneralPath) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return getInstance().getShortenPathImpl(jEnv, jClass, jGeneralPath);
|
||||
}
|
||||
catch (const jthrowable& javaErrWillBeThrown)
|
||||
{
|
||||
jEnv->DeleteLocalRef(javaErrWillBeThrown);
|
||||
}
|
||||
catch (const std::system_error& err)
|
||||
{
|
||||
handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (const std::runtime_error& err)
|
||||
{
|
||||
handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (const std::logic_error& err)
|
||||
{
|
||||
handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
handleUnknownException(jEnv, __func__);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
jstring RecentTasks::getShortenPathImpl(
|
||||
JNIEnv* jEnv,
|
||||
[[maybe_unused]] jclass jClass,
|
||||
jstring jGeneralPath) noexcept(false)
|
||||
{
|
||||
assert( (jEnv != nullptr) );
|
||||
|
||||
ensureJNINoErrors(*jEnv);
|
||||
|
||||
const WideString generalPath = jStringToWideString(jEnv, jGeneralPath);
|
||||
|
||||
auto length = GetShortPathNameW(generalPath.c_str(), nullptr, 0);
|
||||
if (length < 1)
|
||||
throw std::system_error{
|
||||
static_cast<int>(GetLastError()),
|
||||
std::system_category(),
|
||||
"GetShortPathNameW(..., nullptr, 0) failed"
|
||||
};
|
||||
|
||||
WideString result;
|
||||
result.resize(static_cast<WideString::size_type>(length), 0);
|
||||
|
||||
length = GetShortPathNameW( generalPath.c_str(), result.data(), static_cast<DWORD>(result.length()) );
|
||||
if (length < 1)
|
||||
throw std::system_error{
|
||||
static_cast<int>(GetLastError()),
|
||||
std::system_category(),
|
||||
"GetShortPathNameW(..., result.data, result.length) failed"
|
||||
};
|
||||
|
||||
result.resize(result.length() - 1);
|
||||
|
||||
static_assert(
|
||||
sizeof(WideString::value_type) == sizeof(jchar),
|
||||
"Implementation relies on sizeof(WideString::value_type) == sizeof(jchar)"
|
||||
);
|
||||
|
||||
auto jResult = jEnv->NewString(
|
||||
reinterpret_cast<const jchar*>(result.c_str()),
|
||||
static_cast<jsize>(result.length())
|
||||
);
|
||||
|
||||
ensureJNINoErrors(*jEnv);
|
||||
|
||||
return jResult;
|
||||
}
|
||||
|
||||
|
||||
void RecentTasks::clearNative(
|
||||
JNIEnv* jEnv,
|
||||
jclass jClass) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return (void)getInstance().clearNativeImpl(jEnv, jClass);
|
||||
}
|
||||
catch (const jthrowable& javaErrWillBeThrown)
|
||||
{
|
||||
jEnv->DeleteLocalRef(javaErrWillBeThrown);
|
||||
}
|
||||
catch (const std::system_error& err)
|
||||
{
|
||||
handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (const std::runtime_error& err)
|
||||
{
|
||||
handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (const std::logic_error& err)
|
||||
{
|
||||
handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
handleException(err, jEnv, __func__);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
handleUnknownException(jEnv, __func__);
|
||||
}
|
||||
}
|
||||
|
||||
void RecentTasks::clearNativeImpl(
|
||||
JNIEnv* jEnv,
|
||||
[[maybe_unused]] jclass jClass) noexcept(false)
|
||||
{
|
||||
assert( (jEnv != nullptr) );
|
||||
|
||||
ensureJNINoErrors(*jEnv);
|
||||
|
||||
//Application::getInstance().deleteJumpList(COM_IS_INITIALIZED_IN_THIS_THREAD);
|
||||
Application::getInstance().clearRecentsAndFrequents(COM_IS_INITIALIZED_IN_THIS_THREAD);
|
||||
}
|
||||
|
||||
|
||||
std::optional<RecentTasks>& RecentTasks::accessStorage() noexcept(false)
|
||||
{
|
||||
static const auto initializerThreadId = std::this_thread::get_id();
|
||||
|
||||
if (initializerThreadId != std::this_thread::get_id())
|
||||
throw std::logic_error{ "Try to access to the storage from a non-initializer thread" };
|
||||
|
||||
static std::optional<RecentTasks> result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
RecentTasks& RecentTasks::getInstance() noexcept(false)
|
||||
{
|
||||
auto& storage = accessStorage();
|
||||
|
||||
if (!storage.has_value())
|
||||
throw std::logic_error{ "Instance of RecentTasks has not yet been initialized" };
|
||||
|
||||
return *storage;
|
||||
}
|
||||
|
||||
|
||||
template<typename E>
|
||||
void RecentTasks::handleException(
|
||||
const E& exception,
|
||||
JNIEnv* jEnv,
|
||||
std::string_view funcName /* __func__ */) noexcept
|
||||
{
|
||||
using ExceptionType = std::decay_t<E>;
|
||||
|
||||
constexpr std::string_view exceptionTypeName =
|
||||
std::is_base_of_v<std::system_error, ExceptionType> ? "std::system_error"
|
||||
: std::is_base_of_v<std::runtime_error, ExceptionType> ? "std::runtime_error"
|
||||
: std::is_base_of_v<std::logic_error, ExceptionType> ? "std::logic_error"
|
||||
: std::is_base_of_v<std::exception, ExceptionType> ? "std::exception"
|
||||
: "<failed-to-deduce-exception-type>";
|
||||
|
||||
constexpr const char* catchDescription =
|
||||
std::is_base_of_v<std::system_error, ExceptionType> ? "RecentTasks::handleException: failed to handle std::system_error exception"
|
||||
: std::is_base_of_v<std::runtime_error, ExceptionType> ? "RecentTasks::handleException: failed to handle std::runtime_error exception"
|
||||
: std::is_base_of_v<std::logic_error, ExceptionType> ? "RecentTasks::handleException: failed to handle std::logic_error exception"
|
||||
: std::is_base_of_v<std::exception, ExceptionType> ? "RecentTasks::handleException: failed to handle std::exception exception"
|
||||
: "RecentTasks::handleException: failed to handle <failed-to-deduce-exception-type> exception";
|
||||
|
||||
try
|
||||
{
|
||||
assert( (jEnv != nullptr) );
|
||||
|
||||
if (jEnv->ExceptionCheck() == JNI_TRUE)
|
||||
return;
|
||||
|
||||
std::stringstream description;
|
||||
|
||||
description << "Caught " << exceptionTypeName << " in \"" << thisCtxName << "::" << funcName << '\"';
|
||||
if constexpr (std::is_base_of_v<std::system_error, ExceptionType>)
|
||||
{
|
||||
description << " with code " << exception.code();
|
||||
}
|
||||
description << " meaning \"" << cleanExceptionDescription(exception.what()) << '\"';
|
||||
|
||||
jEnv->ThrowNew(
|
||||
jEnv->FindClass("java/lang/RuntimeException"),
|
||||
description.str().c_str()
|
||||
);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
jEnv->ThrowNew(
|
||||
jEnv->FindClass("java/lang/RuntimeException"),
|
||||
catchDescription
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void RecentTasks::handleUnknownException(
|
||||
JNIEnv* jEnv,
|
||||
std::string_view funcName /* __func__ */) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
assert( (jEnv != nullptr) );
|
||||
|
||||
if (jEnv->ExceptionCheck() == JNI_TRUE)
|
||||
return;
|
||||
|
||||
std::stringstream description;
|
||||
|
||||
description << "Caught an unknown exception in \"" << thisCtxName << "::" << funcName << '\"';
|
||||
|
||||
jEnv->ThrowNew(
|
||||
jEnv->FindClass("java/lang/RuntimeException"),
|
||||
description.str().c_str()
|
||||
);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
jEnv->ThrowNew(
|
||||
jEnv->FindClass("java/lang/RuntimeException"),
|
||||
"RecentTasks::handleException: failed to handle an unknown exception"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace intellij::ui::win::jni
|
||||
@@ -0,0 +1,46 @@
|
||||
/* DO NOT EDIT THIS FILE - it is machine generated
|
||||
* ( by Amazon Correto JDK, version Corretto-11.0.8.10.1 (build 11.0.8+10-LTS) ) */
|
||||
#include <jni.h>
|
||||
/* Header for class com_intellij_ui_win_RecentTasks */
|
||||
|
||||
#ifndef _Included_com_intellij_ui_win_RecentTasks
|
||||
#define _Included_com_intellij_ui_win_RecentTasks
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/*
|
||||
* Class: com_intellij_ui_win_RecentTasks
|
||||
* Method: initialize
|
||||
* Signature: (Ljava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_intellij_ui_win_RecentTasks_initialize
|
||||
(JNIEnv *, jclass, jstring);
|
||||
|
||||
/*
|
||||
* Class: com_intellij_ui_win_RecentTasks
|
||||
* Method: addTasksNativeForCategory
|
||||
* Signature: (Ljava/lang/String;[Lcom/intellij/ui/win/Task;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_intellij_ui_win_RecentTasks_addTasksNativeForCategory
|
||||
(JNIEnv *, jclass, jstring, jobjectArray);
|
||||
|
||||
/*
|
||||
* Class: com_intellij_ui_win_RecentTasks
|
||||
* Method: getShortenPath
|
||||
* Signature: (Ljava/lang/String;)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_intellij_ui_win_RecentTasks_getShortenPath
|
||||
(JNIEnv *, jclass, jstring);
|
||||
|
||||
/*
|
||||
* Class: com_intellij_ui_win_RecentTasks
|
||||
* Method: clearNative
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_intellij_ui_win_RecentTasks_clearNative
|
||||
(JNIEnv *, jclass);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* DLL entry point.
|
||||
*
|
||||
* @author Nikita Provotorov
|
||||
*/
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
|
||||
BOOL WINAPI DllMain(
|
||||
[[maybe_unused]] HINSTANCE dllHandle, // handle to DLL module
|
||||
[[maybe_unused]] DWORD entryReason, // reason for calling function
|
||||
LPVOID // reserved
|
||||
)
|
||||
{
|
||||
switch (entryReason)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
break;
|
||||
case DLL_THREAD_ATTACH:
|
||||
break;
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
Reference in New Issue
Block a user