[User Interface] IDEA-75238 Microsoft Windows Jump Lists support: WinJumpListBridge project big refactoring.

GitOrigin-RevId: fab9eda6457d2934d1e241c33fe16e3a1aa59831
This commit is contained in:
Nikita Provotorov
2020-11-01 21:02:01 +07:00
committed by intellij-monorepo-bot
parent 20b41882b8
commit 685b62e435
31 changed files with 258 additions and 613 deletions

11
native/WinShellIntegration/.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
*
!.gitignore
!CMakeLists.txt
!README.md
!build/
!build/**
!src/
!src/**

View 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")

View 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

View File

@@ -0,0 +1,3 @@
*
!.gitignore

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}