├── .clang-format ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .vscode ├── c_cpp_properties.json └── settings.json ├── Info.plist ├── LICENSE ├── Makefile ├── README.md ├── generate.m ├── go.mod ├── go.sum ├── internal └── internal.go ├── main.c └── test ├── cover.jpg └── cover.png /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | ColumnLimit: 150 4 | --- 5 | Language: ObjC 6 | 7 | # AccessModifierOffset: -2 8 | # AlignAfterOpenBracket: Align 9 | # AlignArrayOfStructures: None 10 | # AlignConsecutiveMacros: None 11 | # AlignConsecutiveAssignments: None 12 | # AlignConsecutiveBitFields: None 13 | # AlignConsecutiveDeclarations: None 14 | # AlignEscapedNewlines: Right 15 | # AlignOperands: Align 16 | # AlignTrailingComments: true 17 | # AllowAllArgumentsOnNextLine: true 18 | # AllowAllConstructorInitializersOnNextLine: true 19 | # AllowAllParametersOfDeclarationOnNextLine: true 20 | # AllowShortEnumsOnASingleLine: true 21 | # AllowShortBlocksOnASingleLine: Never 22 | # AllowShortCaseLabelsOnASingleLine: false 23 | # AllowShortFunctionsOnASingleLine: All 24 | # AllowShortLambdasOnASingleLine: All 25 | # AllowShortIfStatementsOnASingleLine: Never 26 | # AllowShortLoopsOnASingleLine: false 27 | # AlwaysBreakAfterDefinitionReturnType: None 28 | # AlwaysBreakAfterReturnType: None 29 | # AlwaysBreakBeforeMultilineStrings: false 30 | # AlwaysBreakTemplateDeclarations: MultiLine 31 | # AttributeMacros: 32 | # - __capability 33 | # BinPackArguments: true 34 | # BinPackParameters: true 35 | # BraceWrapping: 36 | # AfterCaseLabel: false 37 | # AfterClass: false 38 | # AfterControlStatement: Never 39 | # AfterEnum: false 40 | # AfterFunction: false 41 | # AfterNamespace: false 42 | # AfterObjCDeclaration: false 43 | # AfterStruct: false 44 | # AfterUnion: false 45 | # AfterExternBlock: false 46 | # BeforeCatch: false 47 | # BeforeElse: false 48 | # BeforeLambdaBody: false 49 | # BeforeWhile: false 50 | # IndentBraces: false 51 | # SplitEmptyFunction: true 52 | # SplitEmptyRecord: true 53 | # SplitEmptyNamespace: true 54 | # BreakBeforeBinaryOperators: None 55 | # BreakBeforeConceptDeclarations: true 56 | # BreakBeforeBraces: Attach 57 | # BreakBeforeInheritanceComma: false 58 | # BreakInheritanceList: BeforeColon 59 | # BreakBeforeTernaryOperators: true 60 | # BreakConstructorInitializersBeforeComma: false 61 | # BreakConstructorInitializers: BeforeColon 62 | # BreakAfterJavaFieldAnnotations: false 63 | # BreakStringLiterals: true 64 | # CommentPragmas: '^ IWYU pragma:' 65 | # CompactNamespaces: false 66 | # ConstructorInitializerAllOnOneLineOrOnePerLine: false 67 | # ConstructorInitializerIndentWidth: 4 68 | # ContinuationIndentWidth: 4 69 | # Cpp11BracedListStyle: true 70 | # DeriveLineEnding: true 71 | # DerivePointerAlignment: false 72 | # DisableFormat: false 73 | # EmptyLineAfterAccessModifier: Never 74 | # EmptyLineBeforeAccessModifier: LogicalBlock 75 | # ExperimentalAutoDetectBinPacking: false 76 | # FixNamespaceComments: true 77 | # ForEachMacros: 78 | # - foreach 79 | # - Q_FOREACH 80 | # - BOOST_FOREACH 81 | # IfMacros: 82 | # - KJ_IF_MAYBE 83 | # IncludeBlocks: Preserve 84 | # IncludeCategories: 85 | # - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 86 | # Priority: 2 87 | # SortPriority: 0 88 | # CaseSensitive: false 89 | # - Regex: '^(<|"(gtest|gmock|isl|json)/)' 90 | # Priority: 3 91 | # SortPriority: 0 92 | # CaseSensitive: false 93 | # - Regex: '.*' 94 | # Priority: 1 95 | # SortPriority: 0 96 | # CaseSensitive: false 97 | # IncludeIsMainRegex: '(Test)?$' 98 | # IncludeIsMainSourceRegex: '' 99 | # IndentAccessModifiers: false 100 | # IndentCaseLabels: false 101 | # IndentCaseBlocks: false 102 | # IndentGotoLabels: true 103 | # IndentPPDirectives: None 104 | # IndentExternBlock: AfterExternBlock 105 | # IndentRequires: false 106 | # IndentWidth: 2 107 | # IndentWrappedFunctionNames: false 108 | # InsertTrailingCommas: None 109 | # JavaScriptQuotes: Leave 110 | # JavaScriptWrapImports: true 111 | # KeepEmptyLinesAtTheStartOfBlocks: true 112 | # LambdaBodyIndentation: Signature 113 | # MacroBlockBegin: '' 114 | # MacroBlockEnd: '' 115 | # MaxEmptyLinesToKeep: 1 116 | # NamespaceIndentation: None 117 | # ObjCBinPackProtocolList: Auto 118 | # ObjCBlockIndentWidth: 2 119 | # ObjCBreakBeforeNestedBlockParam: true 120 | # ObjCSpaceAfterProperty: false 121 | # ObjCSpaceBeforeProtocolList: true 122 | # PenaltyBreakAssignment: 2 123 | # PenaltyBreakBeforeFirstCallParameter: 19 124 | # PenaltyBreakComment: 300 125 | # PenaltyBreakFirstLessLess: 120 126 | # PenaltyBreakString: 1000 127 | # PenaltyBreakTemplateDeclaration: 10 128 | # PenaltyExcessCharacter: 1000000 129 | # PenaltyReturnTypeOnItsOwnLine: 60 130 | # PenaltyIndentedWhitespace: 0 131 | # PointerAlignment: Right 132 | # PPIndentWidth: -1 133 | # ReferenceAlignment: Pointer 134 | # ReflowComments: true 135 | # ShortNamespaceLines: 1 136 | # SortIncludes: CaseSensitive 137 | # SortJavaStaticImport: Before 138 | # SortUsingDeclarations: true 139 | # SpaceAfterCStyleCast: false 140 | # SpaceAfterLogicalNot: false 141 | # SpaceAfterTemplateKeyword: true 142 | # SpaceBeforeAssignmentOperators: true 143 | # SpaceBeforeCaseColon: false 144 | # SpaceBeforeCpp11BracedList: false 145 | # SpaceBeforeCtorInitializerColon: true 146 | # SpaceBeforeInheritanceColon: true 147 | # SpaceBeforeParens: ControlStatements 148 | # SpaceAroundPointerQualifiers: Default 149 | # SpaceBeforeRangeBasedForLoopColon: true 150 | # SpaceInEmptyBlock: false 151 | # SpaceInEmptyParentheses: false 152 | # SpacesBeforeTrailingComments: 1 153 | # SpacesInAngles: Never 154 | # SpacesInConditionalStatement: false 155 | # SpacesInContainerLiterals: true 156 | # SpacesInCStyleCastParentheses: false 157 | # SpacesInLineCommentPrefix: 158 | # Minimum: 1 159 | # Maximum: -1 160 | # SpacesInParentheses: false 161 | # SpacesInSquareBrackets: false 162 | # SpaceBeforeSquareBrackets: false 163 | # BitFieldColonSpacing: Both 164 | # Standard: Latest 165 | # StatementAttributeLikeMacros: 166 | # - Q_EMIT 167 | # StatementMacros: 168 | # - Q_UNUSED 169 | # - QT_REQUIRE_VERSION 170 | # TabWidth: 8 171 | # UseCRLF: false 172 | # UseTab: Never 173 | # WhitespaceSensitiveMacros: 174 | # - STRINGIZE 175 | # - PP_STRINGIZE 176 | # - BOOST_PP_STRINGIZE 177 | # - NS_SWIFT_NAME 178 | # - CF_SWIFT_NAME 179 | --- 180 | Language: Cpp 181 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | build: 9 | runs-on: macos-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-go@v2 13 | with: 14 | go-version: "^1.16" 15 | - run: make 16 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: macos-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-go@v2 13 | with: 14 | go-version: "^1.16" 15 | - run: make 16 | 17 | publish-binaries: 18 | needs: build 19 | runs-on: macos-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: actions/setup-go@v2 23 | with: 24 | go-version: "^1.16" 25 | - run: make package 26 | - run: make upload-release-binary 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | GITHUB_RELEASE_ID: ${{ github.event.release.id }} 30 | BINARY_LABEL: Intel 31 | BINARY_NAME: MKA.qlgenerator.intel.zip 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | MKA.qlgenerator/Contents/MacOS/ 3 | *.qlgenerator 4 | *.a 5 | *.zip 6 | test/*.mka 7 | internal.h 8 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "macOS", 5 | "includePath": [".", "${default}"] 6 | } 7 | ], 8 | "version": 4 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "files.exclude": { 4 | "**/*.o": true, 5 | "**/*.a": true, 6 | "**.qlgenerator": true, 7 | "internal.h": true 8 | }, 9 | "[jsonc]": { 10 | "editor.defaultFormatter": "esbenp.prettier-vscode", 11 | "editor.formatOnSave": true 12 | }, 13 | "[json]": { 14 | "editor.defaultFormatter": "esbenp.prettier-vscode", 15 | "editor.formatOnSave": true 16 | }, 17 | "[yaml]": { 18 | "editor.formatOnSave": true 19 | }, 20 | "[objective-c]": { 21 | "editor.formatOnSave": true 22 | }, 23 | "[c]": { 24 | "editor.formatOnSave": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleExecutable 6 | MKA 7 | CFBundleIdentifier 8 | re.mko.qlmka 9 | CFBundleVersion 10 | ${VERSION} 11 | CFBundleShortVersionString 12 | ${SHORT_VERSION} 13 | NSHumanReadableCopyright 14 | Copyright (c) 2022 Remko Tronçon 15 | LSMinimumSystemVersion 16 | 10.6.8 17 | CFBundleDevelopmentRegion 18 | English 19 | CFBundleDocumentTypes 20 | 21 | 22 | CFBundleTypeRole 23 | QLGenerator 24 | LSItemContentTypes 25 | 26 | org.matroska.mka 27 | 28 | 29 | 30 | CFPlugInDynamicRegisterFunction 31 | 32 | CFPlugInDynamicRegistration 33 | NO 34 | CFPlugInFactories 35 | 36 | B4BD4649-D9EA-4451-A10E-94C566982A05 37 | QuickLookGeneratorPluginFactory 38 | 39 | CFPlugInTypes 40 | 41 | 5E2D9680-5022-40FA-B806-43349622E5B9 42 | 43 | B4BD4649-D9EA-4451-A10E-94C566982A05 44 | 45 | 46 | CFPlugInUnloadFunction 47 | 48 | QLSupportsConcurrentRequests 49 | 50 | QLNeedsToBeRunInMainThread 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Remko Tronçon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -Wno-deprecated-declarations 2 | 3 | all: MKA.qlgenerator 4 | 5 | .PHONY: internal 6 | internal: 7 | go build -buildmode=c-archive -o internal.a ./internal 8 | 9 | .PHONY: MKA.qlgenerator 10 | MKA.qlgenerator: internal 11 | @mkdir -p MKA.qlgenerator/Contents/MacOS 12 | clang $(CFLAGS) -framework ImageIO -framework Foundation -framework QuickLook -bundle -o MKA.qlgenerator/Contents/MacOS/MKA main.c generate.m internal.a 13 | cat Info.plist | sed "s/\$${VERSION}/$$(git describe --tags | cut -b 2-)/" | sed "s/\$${SHORT_VERSION}/$$(git describe --abbrev=0 --tags | cut -b 2-)/" > MKA.qlgenerator/Contents/Info.plist 14 | 15 | .PHONY: install 16 | install: MKA.qlgenerator 17 | rsync -a --delete MKA.qlgenerator/ ~/Library/QuickLook/MKA.qlgenerator/ 18 | qlmanage -r 19 | 20 | .PHONY: package 21 | package: MKA.qlgenerator 22 | zip -r $<.zip $< 23 | 24 | .PHONY: upload-release-binary 25 | upload-release-binary: 26 | curl --fail-with-body -X POST -H "Authorization: token $(GITHUB_TOKEN)" -H "Content-Type: application/zip" --data-binary "@MKA.qlgenerator.zip" "https://uploads.github.com/repos/$(GITHUB_REPOSITORY)/releases/$(GITHUB_RELEASE_ID)/assets?name=$(BINARY_NAME)&label=$(BINARY_NAME)%20($(BINARY_LABEL))" 27 | 28 | .PHONY: clean 29 | clean: 30 | -rm -rf *.zip *.a internal.h MKA.qlgenerator 31 | 32 | ################################################################################################## 33 | # Testing 34 | ################################################################################################## 35 | 36 | test-thumbnail-jpg: test/jpeg.mka 37 | /usr/bin/qlmanage -g ./MKA.qlgenerator -c org.matroska.mka -t $< 38 | 39 | test-thumbnail-png: test/png.mka 40 | /usr/bin/qlmanage -g ./MKA.qlgenerator -c org.matroska.mka -t $< 41 | 42 | test-preview: test/jpeg.mka 43 | /usr/bin/qlmanage -g ./MKA.qlgenerator -c org.matroska.mka -p $< 44 | 45 | test/jpeg.mka: test/cover.jpg 46 | ffmpeg -loglevel quiet -y -t 1 -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -c:a aac -attach $< -metadata:s:t mimetype=image/jpeg $@ 47 | 48 | test/png.mka: test/cover.png 49 | ffmpeg -loglevel quiet -y -t 1 -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -c:a aac -attach $< -metadata:s:t mimetype=image/png $@ 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `qlmka`: A macOS Quick Look plugin to display Matroska `.mka` covers 2 | 3 | This plugin adds support for showing thumbnails of Matroska `.mka` files in macOS Finder. 4 | It uses a [custom Matroska parser](https://github.com/remko/go-mkvparse) for efficient scanning. 5 | 6 | ## Installation 7 | 8 | 1. Download and unpack the correct binary for your OS from the [Releases page](https://github.com/remko/qlmka/releases) 9 | 2. Move the `MKA.qlgenerator` package to `~/Library/QuickLook`. In Finder, use Cmd+Shift+G to enter 10 | the target path if necessary. 11 | 3. Restart Finder: Ctrl-Option-clicking the Finder icon in the dock, and choose 'Relaunch'. 12 | 13 | ## Development 14 | 15 | Build the plugin: 16 | 17 | make 18 | 19 | Install the plugin: 20 | 21 | make install 22 | 23 | Test the plugin: 24 | 25 | make test-thumbnail-jpg 26 | make test-thumbnail-png 27 | -------------------------------------------------------------------------------- /generate.m: -------------------------------------------------------------------------------- 1 | // See https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/Quicklook_Programming_Guide 2 | 3 | #import 4 | #import 5 | 6 | #include "internal.h" 7 | 8 | OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, 9 | CFDictionaryRef options, CGSize maxSize) { 10 | 11 | struct GetMKAThumb_return ret = GetMKAThumb((char *)[[((NSURL *)url) path] cStringUsingEncoding:NSUTF8StringEncoding], maxSize.width, maxSize.height); 12 | int err = ret.r0; 13 | UInt8* coverData = (UInt8*) ret.r1; 14 | CFIndex coverLen = ret.r2; 15 | 16 | if (err != 0 || coverData == NULL) { 17 | return noErr; 18 | } 19 | CFDataRef cover = CFDataCreate(kCFAllocatorDefault, coverData, coverLen); 20 | free(coverData); 21 | /* NSDictionary *props = [NSDictionary dictionaryWithObject:@"public.jpeg" forKey:(__bridge NSString *)kCGImageSourceTypeIdentifierHint]; */ 22 | QLThumbnailRequestSetImageWithData(thumbnail, cover, NULL); 23 | CFRelease(cover); 24 | return noErr; 25 | } 26 | 27 | void CancelThumbnailGeneration(void *thisInterface, QLThumbnailRequestRef thumbnail) {} 28 | 29 | OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options) { 30 | // Not implemented right now. Fallback seems to work. 31 | return noErr; 32 | } 33 | 34 | void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview) {} 35 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/remko/qlmka 2 | 3 | go 1.16 4 | 5 | require github.com/remko/go-mkvparse v0.9.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/AllenDang/w32 v0.0.0-20180428130237-ad0a36d80adc/go.mod h1:1rHKulT5eD2DzdKxDXUZRKtBfkTzLmTL42ZmEmOfyrs= 2 | github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ= 3 | github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0= 4 | github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 5 | github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k= 6 | github.com/remko/go-mkvparse v0.9.0 h1:6fjAaY7omBaHgMW7wkEYlhD1jZUwDAeEHzGJbCBIAUI= 7 | github.com/remko/go-mkvparse v0.9.0/go.mod h1:tLjjuNXiQ4WLRBjU/ZUuyUweUO1s/sg7FtVX+yf1F0c= 8 | github.com/skelterjohn/go.wde v0.0.0-20190318181201-adc3f78cdb45/go.mod h1:zXxNsJHeUYIqpg890APBNEn9GoCbA4Cdnvuv3mx4fBk= 9 | -------------------------------------------------------------------------------- /internal/internal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "unsafe" 6 | 7 | "github.com/remko/go-mkvparse" 8 | ) 9 | 10 | import "C" 11 | 12 | //export GetMKAThumb 13 | func GetMKAThumb(cpath *C.char, maxWidth C.float, maxHeight C.float) (code C.int, outData unsafe.Pointer, outLen C.long) { 14 | path := C.GoString(cpath) 15 | data, _, err := mkvparse.ParseCover(path) 16 | if err != nil { 17 | log.Printf("error reading thumb: %v", err) 18 | return -1, nil, 0 19 | } 20 | if data == nil { 21 | return 0, nil, 0 22 | } 23 | return 0, C.CBytes(data), C.long(len(data)) 24 | } 25 | 26 | func main() {} 27 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // 3 | // DO NO MODIFY THE CONTENT OF THIS FILE 4 | // 5 | // This file contains the generic CFPlug-in code necessary for your generator 6 | // 7 | //============================================================================== 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // ----------------------------------------------------------------------------- 15 | // constants 16 | // ----------------------------------------------------------------------------- 17 | 18 | // Don't modify this line 19 | #define PLUGIN_ID "B4BD4649-D9EA-4451-A10E-94C566982A05" 20 | 21 | // 22 | // Below is the generic glue code for all plug-ins. 23 | // 24 | // You should not have to modify this code aside from changing 25 | // names if you decide to change the names defined in the Info.plist 26 | // 27 | 28 | // ----------------------------------------------------------------------------- 29 | // typedefs 30 | // ----------------------------------------------------------------------------- 31 | 32 | OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, 33 | CFDictionaryRef options, CGSize maxSize); 34 | void CancelThumbnailGeneration(void *thisInterface, QLThumbnailRequestRef thumbnail); 35 | 36 | OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options); 37 | void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview); 38 | 39 | // The layout for an instance of QuickLookGeneratorPlugIn 40 | typedef struct __QuickLookGeneratorPluginType { 41 | void *conduitInterface; 42 | CFUUIDRef factoryID; 43 | UInt32 refCount; 44 | } QuickLookGeneratorPluginType; 45 | 46 | // ----------------------------------------------------------------------------- 47 | // prototypes 48 | // ----------------------------------------------------------------------------- 49 | // Forward declaration for the IUnknown implementation. 50 | // 51 | 52 | QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID); 53 | void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance); 54 | HRESULT QuickLookGeneratorQueryInterface(void *thisInstance, REFIID iid, LPVOID *ppv); 55 | void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator, CFUUIDRef typeID); 56 | ULONG QuickLookGeneratorPluginAddRef(void *thisInstance); 57 | ULONG QuickLookGeneratorPluginRelease(void *thisInstance); 58 | 59 | // ----------------------------------------------------------------------------- 60 | // myInterfaceFtbl definition 61 | // ----------------------------------------------------------------------------- 62 | // The QLGeneratorInterfaceStruct function table. 63 | // 64 | static QLGeneratorInterfaceStruct myInterfaceFtbl = { 65 | NULL, QuickLookGeneratorQueryInterface, QuickLookGeneratorPluginAddRef, QuickLookGeneratorPluginRelease, NULL, NULL, NULL, NULL}; 66 | 67 | // ----------------------------------------------------------------------------- 68 | // AllocQuickLookGeneratorPluginType 69 | // ----------------------------------------------------------------------------- 70 | // Utility function that allocates a new instance. 71 | // You can do some initial setup for the generator here if you wish 72 | // like allocating globals etc... 73 | // 74 | QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID) { 75 | QuickLookGeneratorPluginType *theNewInstance; 76 | 77 | theNewInstance = (QuickLookGeneratorPluginType *)malloc(sizeof(QuickLookGeneratorPluginType)); 78 | memset(theNewInstance, 0, sizeof(QuickLookGeneratorPluginType)); 79 | 80 | /* Point to the function table Malloc enough to store the stuff and copy the filler from myInterfaceFtbl over */ 81 | theNewInstance->conduitInterface = malloc(sizeof(QLGeneratorInterfaceStruct)); 82 | memcpy(theNewInstance->conduitInterface, &myInterfaceFtbl, sizeof(QLGeneratorInterfaceStruct)); 83 | 84 | /* Retain and keep an open instance refcount for each factory. */ 85 | theNewInstance->factoryID = CFRetain(inFactoryID); 86 | CFPlugInAddInstanceForFactory(inFactoryID); 87 | 88 | /* This function returns the IUnknown interface so set the refCount to one. */ 89 | theNewInstance->refCount = 1; 90 | return theNewInstance; 91 | } 92 | 93 | // ----------------------------------------------------------------------------- 94 | // DeallocQuickLookGeneratorPluginType 95 | // ----------------------------------------------------------------------------- 96 | // Utility function that deallocates the instance when 97 | // the refCount goes to zero. 98 | // In the current implementation generator interfaces are never deallocated 99 | // but implement this as this might change in the future 100 | // 101 | void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance) { 102 | CFUUIDRef theFactoryID; 103 | 104 | theFactoryID = thisInstance->factoryID; 105 | /* Free the conduitInterface table up */ 106 | free(thisInstance->conduitInterface); 107 | 108 | /* Free the instance structure */ 109 | free(thisInstance); 110 | if (theFactoryID) { 111 | CFPlugInRemoveInstanceForFactory(theFactoryID); 112 | CFRelease(theFactoryID); 113 | } 114 | } 115 | 116 | // ----------------------------------------------------------------------------- 117 | // QuickLookGeneratorQueryInterface 118 | // ----------------------------------------------------------------------------- 119 | // Implementation of the IUnknown QueryInterface function. 120 | // 121 | HRESULT QuickLookGeneratorQueryInterface(void *thisInstance, REFIID iid, LPVOID *ppv) { 122 | CFUUIDRef interfaceID; 123 | 124 | interfaceID = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, iid); 125 | 126 | if (CFEqual(interfaceID, kQLGeneratorCallbacksInterfaceID)) { 127 | /* If the Right interface was requested, bump the ref count, 128 | * set the ppv parameter equal to the instance, and 129 | * return good status. 130 | */ 131 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GenerateThumbnailForURL = 132 | GenerateThumbnailForURL; 133 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->CancelThumbnailGeneration = 134 | CancelThumbnailGeneration; 135 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GeneratePreviewForURL = GeneratePreviewForURL; 136 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->CancelPreviewGeneration = 137 | CancelPreviewGeneration; 138 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->AddRef(thisInstance); 139 | *ppv = thisInstance; 140 | CFRelease(interfaceID); 141 | return S_OK; 142 | } else { 143 | /* Requested interface unknown, bail with error. */ 144 | *ppv = NULL; 145 | CFRelease(interfaceID); 146 | return E_NOINTERFACE; 147 | } 148 | } 149 | 150 | // ----------------------------------------------------------------------------- 151 | // QuickLookGeneratorPluginAddRef 152 | // ----------------------------------------------------------------------------- 153 | // Implementation of reference counting for this type. Whenever an interface 154 | // is requested, bump the refCount for the instance. NOTE: returning the 155 | // refcount is a convention but is not required so don't rely on it. 156 | // 157 | ULONG QuickLookGeneratorPluginAddRef(void *thisInstance) { 158 | ((QuickLookGeneratorPluginType *)thisInstance)->refCount += 1; 159 | return ((QuickLookGeneratorPluginType *)thisInstance)->refCount; 160 | } 161 | 162 | // ----------------------------------------------------------------------------- 163 | // QuickLookGeneratorPluginRelease 164 | // ----------------------------------------------------------------------------- 165 | // When an interface is released, decrement the refCount. 166 | // If the refCount goes to zero, deallocate the instance. 167 | // 168 | ULONG QuickLookGeneratorPluginRelease(void *thisInstance) { 169 | ((QuickLookGeneratorPluginType *)thisInstance)->refCount -= 1; 170 | if (((QuickLookGeneratorPluginType *)thisInstance)->refCount == 0) { 171 | DeallocQuickLookGeneratorPluginType((QuickLookGeneratorPluginType *)thisInstance); 172 | return 0; 173 | } else { 174 | return ((QuickLookGeneratorPluginType *)thisInstance)->refCount; 175 | } 176 | } 177 | 178 | // ----------------------------------------------------------------------------- 179 | // QuickLookGeneratorPluginFactory 180 | // ----------------------------------------------------------------------------- 181 | void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator, CFUUIDRef typeID) { 182 | QuickLookGeneratorPluginType *result; 183 | CFUUIDRef uuid; 184 | 185 | /* If correct type is being requested, allocate an 186 | * instance of kQLGeneratorTypeID and return the IUnknown interface. 187 | */ 188 | if (CFEqual(typeID, kQLGeneratorTypeID)) { 189 | uuid = CFUUIDCreateFromString(kCFAllocatorDefault, CFSTR(PLUGIN_ID)); 190 | result = AllocQuickLookGeneratorPluginType(uuid); 191 | CFRelease(uuid); 192 | return result; 193 | } 194 | /* If the requested type is incorrect, return NULL. */ 195 | return NULL; 196 | } 197 | -------------------------------------------------------------------------------- /test/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remko/qlmka/9754d5cab4b0ae4e73516bc4f0dfe2c86f9fda84/test/cover.jpg -------------------------------------------------------------------------------- /test/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remko/qlmka/9754d5cab4b0ae4e73516bc4f0dfe2c86f9fda84/test/cover.png --------------------------------------------------------------------------------