Add the thumbnailer COM server for Windows

Note: Moved the desktop integration for Linux to src/desktop/linux
This commit is contained in:
David Capello 2018-01-02 17:03:46 -03:00
parent 80744eb80e
commit 1898ce2886
22 changed files with 561 additions and 28 deletions

View File

@ -448,10 +448,6 @@ if(APPLE)
endif(COMPILER_GCC)
endif(APPLE)
if(WITH_DESKTOP_INTEGRATION)
add_subdirectory(desktop)
endif()
# Third parties
add_subdirectory(third_party)

View File

@ -1,24 +0,0 @@
# Aseprite Desktop Integration Module
# Copyright (C) 2016 Gabriel Rauter
# Copyright (C) 2016 David Capello
#
# Licensed under the the MIT License (https://opensource.org/licenses/MIT).
if(UNIX AND NOT APPLE)
# Desktop shortcut
install(FILES aseprite.desktop
DESTINATION share/applications)
# GNOME Thumbnailer
install(FILES mime/aseprite.xml
DESTINATION share/mime/packages)
install(PROGRAMS aseprite-thumbnailer
DESTINATION bin)
install(FILES gnome/aseprite.thumbnailer
DESTINATION share/thumbnailers)
# Qt Thumbnailer
if(WITH_QT_THUMBNAILER)
add_subdirectory(kde)
endif()
endif()

View File

@ -117,6 +117,10 @@ if(ENABLE_STEAM)
add_subdirectory(steam)
endif()
if(WITH_DESKTOP_INTEGRATION)
add_subdirectory(desktop)
endif()
add_subdirectory(app)
######################################################################

View File

@ -55,6 +55,7 @@ because they don't depend on any other component.
## Level 6
* [main](main/) (app, base, she, ui)
* [desktop](desktop/) (base, doc, dio, render): Integration with the desktop (Windows Explorer, Finder, GNOME, KDE, etc.)
# Debugging Tricks

View File

@ -0,0 +1,12 @@
# Desktop Integration
# Copyright (C) 2017-2018 David Capello
# Windows
if(WIN32)
add_subdirectory(win)
endif()
# Linux-like
if(UNIX AND NOT APPLE)
add_subdirectory(linux)
endif()

20
src/desktop/LICENSE.txt Normal file
View File

@ -0,0 +1,20 @@
Copyright (C) 2017-2018 David Capello
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

4
src/desktop/README.md Normal file
View File

@ -0,0 +1,4 @@
# Desktop Integration
*Copyright (C) 2017-2018 David Capello*
> Distributed under [MIT license](LICENSE.txt)

View File

@ -0,0 +1,15 @@
# Desktop Integration
# Copyright (C) 2016 Gabriel Rauter
# Desktop shortcut
install(FILES aseprite.desktop DESTINATION share/applications)
# GNOME Thumbnailer
install(FILES mime/aseprite.xml DESTINATION share/mime/packages)
install(PROGRAMS aseprite-thumbnailer DESTINATION bin)
install(FILES gnome/aseprite.thumbnailer DESTINATION share/thumbnailers)
# Qt Thumbnailer
if(WITH_QT_THUMBNAILER)
add_subdirectory(kde)
endif()

View File

@ -0,0 +1,13 @@
# Desktop Integration
# Copyright (C) 2017-2018 David Capello
add_library(aseprite-thumbnailer SHARED
dllmain.cpp
exports.def
thumbnail_handler.cpp)
target_link_libraries(aseprite-thumbnailer
laf-base
dio-lib
render-lib
shlwapi)

View File

@ -0,0 +1,77 @@
// Desktop Integration
// Copyright (C) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DESKTOP_CLASS_FACTORY_H_INCLUDED
#define DESKTOP_CLASS_FACTORY_H_INCLUDED
#include <cassert>
#include <objbase.h>
#include <shlwapi.h>
namespace desktop {
class ClassFactoryDelegate {
public:
virtual ~ClassFactoryDelegate() { }
virtual HRESULT lockServer(const bool lock) = 0;
virtual HRESULT createInstance(REFIID riid, void** ppvObject) = 0;
};
class ClassFactory : public IClassFactory {
public:
ClassFactory(ClassFactoryDelegate* delegate)
: m_ref(1)
, m_delegate(delegate) {
assert(m_delegate);
m_delegate->lockServer(true);
}
~ClassFactory() {
m_delegate->lockServer(false);
delete m_delegate;
}
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv) {
static const QITAB qit[] = {
QITABENT(ClassFactory, IClassFactory),
{ 0 }
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP_(ULONG) AddRef() {
return InterlockedIncrement(&m_ref);
}
IFACEMETHODIMP_(ULONG) Release() {
long ref = InterlockedDecrement(&m_ref);
if (ref == 0)
delete this;
return ref;
}
// IClassFactory
IFACEMETHODIMP CreateInstance(IUnknown* punkOuter, REFIID riid, void** ppv) {
if (punkOuter)
return CLASS_E_NOAGGREGATION;
else
return m_delegate->createInstance(riid, ppv);
}
IFACEMETHODIMP LockServer(BOOL lock) {
return m_delegate->lockServer(lock ? true: false);
}
private:
long m_ref;
ClassFactoryDelegate* m_delegate;
};
} // namespace desktop
#endif

131
src/desktop/win/dllmain.cpp Normal file
View File

@ -0,0 +1,131 @@
// Desktop Integration
// Copyright (C) 2017-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "base/string.h"
#include "base/win/registry.h"
#include "base/win/win32_exception.h"
#include "desktop/win/class_factory.h"
#include "desktop/win/thumbnail_handler.h"
#include <new>
// This CLSID is defined by Windows for IThumbnailProvider implementations.
#define THUMBNAILHANDLER_SHELL_EXTENSION_CLSID "{E357FCCD-A995-4576-B01F-234630154E96}"
// This CLSID is defined by us, and can be used to create class factory for ThumbnailHandler instances.
#define THUMBNAILHANDLER_CLSID_STRING "{A5E9417E-6E7A-4B2D-85A4-84E114D7A960}"
#define THUMBNAILHANDLER_NAME_STRING "Aseprite Thumbnail Handler"
using namespace desktop;
namespace {
namespace Global {
long refCount = 0;
HINSTANCE hInstance = nullptr;
CLSID clsidThumbnailHandler;
}
template<class T>
class ClassFactoryDelegateImpl : public ClassFactoryDelegate {
public:
HRESULT lockServer(const bool lock) override {
if (lock)
InterlockedIncrement(&Global::refCount);
else
InterlockedDecrement(&Global::refCount);
return S_OK;
}
HRESULT createInstance(REFIID riid, void** ppvObject) override {
return T::CreateInstance(riid, ppvObject);
}
};
} // anonymous namespace
STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void*)
{
if (dwReason == DLL_PROCESS_ATTACH) {
CLSIDFromString(
base::from_utf8(THUMBNAILHANDLER_CLSID_STRING).c_str(),
&Global::clsidThumbnailHandler);
Global::hInstance = hInstance;
DisableThreadLibraryCalls(hInstance);
}
return TRUE;
}
STDAPI DllCanUnloadNow()
{
return (Global::refCount == 0) ? S_OK: S_FALSE;
}
STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid, void** ppv)
{
HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
if (IsEqualCLSID(clsid, Global::clsidThumbnailHandler)) {
ClassFactoryDelegate* delegate = new (std::nothrow)ClassFactoryDelegateImpl<ThumbnailHandler>;
if (!delegate)
return E_OUTOFMEMORY;
IClassFactory* classFactory = new (std::nothrow)ClassFactory(delegate);
if (!classFactory) {
delete delegate;
return E_OUTOFMEMORY;
}
hr = classFactory->QueryInterface(riid, ppv);
classFactory->Release();
}
return hr;
}
STDAPI DllRegisterServer()
{
WCHAR dllPath[MAX_PATH];
if (!GetModuleFileNameW(Global::hInstance, dllPath, sizeof(dllPath)/sizeof(dllPath[0])))
return HRESULT_FROM_WIN32(GetLastError());
auto hkcu = base::hkey::current_user();
auto k = hkcu.create("Software\\Classes\\CLSID\\" THUMBNAILHANDLER_CLSID_STRING);
k.string("", THUMBNAILHANDLER_NAME_STRING);
k = k.create("InProcServer32");
k.string("", base::to_utf8(dllPath));
k.string("ThreadingModel", "Apartment");
k = hkcu.create("Software\\Classes\\AsepriteFile");
k.create("ShellEx\\" THUMBNAILHANDLER_SHELL_EXTENSION_CLSID).string("", THUMBNAILHANDLER_CLSID_STRING);
// TypeOverlay = empty string = don't show the app icon overlay in the thumbnail
k.string("TypeOverlay", "");
// Treatment = 2 = photo border
k.dword("Treatment", 2);
return S_OK;
}
STDAPI DllUnregisterServer()
{
HRESULT hr = S_OK;
auto hkcu = base::hkey::current_user();
try {
hkcu.delete_tree("Software\\Classes\\AsepriteFile\\ShellEx\\" THUMBNAILHANDLER_SHELL_EXTENSION_CLSID);
}
catch (const base::Win32Exception& ex) {
hr = HRESULT_FROM_WIN32(ex.errorCode());
}
try {
hkcu.delete_tree("Software\\Classes\\CLSID\\" THUMBNAILHANDLER_CLSID_STRING);
}
catch (const base::Win32Exception& ex) {
hr = HRESULT_FROM_WIN32(ex.errorCode());
}
return hr;
}

View File

@ -0,0 +1,6 @@
EXPORTS
DllCanUnloadNow PRIVATE
DllGetClassObject PRIVATE
DllMain PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE

View File

@ -0,0 +1,235 @@
// Desktop Integration
// Copyright (C) 2017-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "base/base.h"
#include "dio/decode_delegate.h"
#include "dio/decode_file.h"
#include "dio/file_interface.h"
#include "doc/color.h"
#include "doc/document.h"
#include "doc/image_ref.h"
#include "doc/image_traits.h"
#include "doc/pixel_format.h"
#include "doc/sprite.h"
#include "render/render.h"
#include "desktop/win/thumbnail_handler.h"
#include <cassert>
#include <new>
#include <objbase.h>
#include <shlwapi.h>
namespace desktop {
namespace {
class DecodeDelegate : public dio::DecodeDelegate {
public:
DecodeDelegate() : m_sprite(nullptr) { }
~DecodeDelegate() { delete m_sprite; }
bool decodeOneFrame() override { return true; }
void onSprite(doc::Sprite* sprite) override {
m_sprite = sprite;
}
doc::Sprite* sprite() { return m_sprite; }
private:
doc::Sprite* m_sprite;
};
class StreamAdaptor : public dio::FileInterface {
public:
StreamAdaptor(IStream* stream)
: m_stream(stream)
, m_ok(m_stream != nullptr) {
}
bool ok() const {
return m_ok;
}
size_t tell() {
LARGE_INTEGER delta;
delta.QuadPart = 0;
ULARGE_INTEGER newPos;
HRESULT hr = m_stream->Seek(delta, STREAM_SEEK_CUR, &newPos);
if (FAILED(hr)) {
m_ok = false;
return 0;
}
return newPos.QuadPart;
}
void seek(size_t absPos) {
LARGE_INTEGER pos;
pos.QuadPart = absPos;
ULARGE_INTEGER newPos;
HRESULT hr = m_stream->Seek(pos, STREAM_SEEK_SET, &newPos);
if (FAILED(hr))
m_ok = false;
}
uint8_t read8() {
if (!m_ok)
return 0;
unsigned char byte = 0;
ULONG count;
HRESULT hr = m_stream->Read((void*)&byte, 1, &count);
if (FAILED(hr) || count != 1) {
m_ok = false;
return 0;
}
return byte;
}
size_t readBytes(uint8_t* buf, size_t n) {
if (!m_ok)
return 0;
ULONG count;
HRESULT hr = m_stream->Read((void*)buf, (ULONG)n, &count);
if (FAILED(hr) || count != n)
m_ok = false;
return count;
}
void write8(uint8_t value) {
// Do nothing, we don't write in the file
}
IStream* m_stream;
bool m_ok;
};
} // anonymous namespace
// static
HRESULT ThumbnailHandler::CreateInstance(REFIID riid, void** ppv)
{
*ppv = nullptr;
ThumbnailHandler* obj = new (std::nothrow)ThumbnailHandler;
if (!obj)
return E_OUTOFMEMORY;
HRESULT hr = obj->QueryInterface(riid, ppv);
obj->Release();
return hr;
}
ThumbnailHandler::ThumbnailHandler()
: m_ref(1)
{
}
ThumbnailHandler::~ThumbnailHandler()
{
}
// IUnknown
HRESULT ThumbnailHandler::QueryInterface(REFIID riid, void** ppv)
{
*ppv = nullptr;
static const QITAB qit[] = {
QITABENT(ThumbnailHandler, IInitializeWithStream),
QITABENT(ThumbnailHandler, IThumbnailProvider),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
ULONG ThumbnailHandler::AddRef()
{
return InterlockedIncrement(&m_ref);
}
ULONG ThumbnailHandler::Release()
{
ULONG ref = InterlockedDecrement(&m_ref);
if (!ref)
delete this;
return ref;
}
// IInitializeWithStream
HRESULT ThumbnailHandler::Initialize(IStream* pStream, DWORD grfMode)
{
if (!pStream)
return E_INVALIDARG;
m_stream.reset();
return pStream->QueryInterface(IID_IStream, (void**)&m_stream);
}
// IThumbnailProvider
HRESULT ThumbnailHandler::GetThumbnail(UINT cx, HBITMAP* phbmp, WTS_ALPHATYPE* pdwAlpha)
{
if (!m_stream.get())
return E_FAIL;
doc::ImageRef image;
int w, h;
try {
DecodeDelegate delegate;
StreamAdaptor adaptor(m_stream.get());
if (!dio::decode_file(&delegate, &adaptor))
return E_FAIL;
doc::Sprite* spr = delegate.sprite();
w = spr->width();
h = spr->height();
image.reset(doc::Image::create(doc::IMAGE_RGB, w, h));
#undef TRANSPARENT // Windows defines TRANSPARENT macro
render::Render render;
render.setBgType(render::BgType::TRANSPARENT);
render.renderSprite(image.get(), spr, 0);
}
catch (const std::exception&) {
// TODO convert exception into a HRESULT
return E_FAIL;
}
BITMAPINFO bi;
ZeroMemory(&bi, sizeof(bi));
bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
bi.bmiHeader.biWidth = w;
bi.bmiHeader.biHeight = -h;
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = 32;
bi.bmiHeader.biCompression = BI_RGB;
unsigned char* data = nullptr;
*phbmp = CreateDIBSection(nullptr, &bi, DIB_RGB_COLORS, (void**)&data, nullptr, 0);
if (!*phbmp)
return E_FAIL;
for (int y=0; y<h; ++y) {
doc::RgbTraits::address_t row =
(doc::RgbTraits::address_t)image->getPixelAddress(0, y);
for (int x=0; x<w; ++x, ++row) {
doc::color_t c = *row;
*(data++) = doc::rgba_getb(c);
*(data++) = doc::rgba_getg(c);
*(data++) = doc::rgba_getr(c);
*(data++) = doc::rgba_geta(c);
}
}
*pdwAlpha = WTSAT_ARGB;
return S_OK;
}
} // namespace desktop

View File

@ -0,0 +1,43 @@
// Desktop Integration
// Copyright (C) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DESKTOP_THUMBNAIL_HANDLER_H_INCLUDED
#define DESKTOP_THUMBNAIL_HANDLER_H_INCLUDED
#include "base/win/comptr.h"
#include <propsys.h>
#include <thumbcache.h>
namespace desktop {
class ThumbnailHandler : public IInitializeWithStream,
public IThumbnailProvider {
public:
static HRESULT CreateInstance(REFIID riid, void** ppvObject);
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv);
IFACEMETHODIMP_(ULONG) AddRef();
IFACEMETHODIMP_(ULONG) Release();
// IInitializeWithStream
IFACEMETHODIMP Initialize(IStream* pStream, DWORD grfMode);
// IThumbnailProvider
IFACEMETHODIMP GetThumbnail(UINT cx, HBITMAP* phbmp, WTS_ALPHATYPE* pdwAlpha);
private:
ThumbnailHandler();
virtual ~ThumbnailHandler();
long m_ref;
base::ComPtr<IStream> m_stream;
};
} // namespace desktop
#endif