├── .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
--------------------------------------------------------------------------------