Skip to content

Commit

Permalink
Thumbnail generator
Browse files Browse the repository at this point in the history
  • Loading branch information
IsaacMarovitz committed Sep 10, 2023
1 parent ccc172b commit edcb5cb
Show file tree
Hide file tree
Showing 17 changed files with 354 additions and 64 deletions.
211 changes: 200 additions & 11 deletions Whisky.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

29 changes: 3 additions & 26 deletions Whisky/Views/Bottle Views/BottleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import SwiftUI
import UniformTypeIdentifiers
import QuickLookThumbnailing
import WhiskyKit

struct BottleView: View {
@Binding var bottle: Bottle
Expand Down Expand Up @@ -275,19 +276,7 @@ struct ShellLinkView: View {
if let linkInfo = link.linkInfo, let program = linkInfo.program {
do {
let peFile = try PEFile(data: Data(contentsOf: program.url))
var icons: [NSImage] = []
if let resourceSection = peFile.resourceSection {
for entries in resourceSection.allEntries where entries.icon.isValid {
icons.append(entries.icon)
}
} else {
print("No resource section")
return
}

if icons.count > 0 {
image = icons.max(by: { $0.size.height < $1.size.height })
}
image = peFile.bestIcon()
} catch {
print(error)
}
Expand Down Expand Up @@ -334,19 +323,7 @@ struct ShortcutView: View {
bottle: bottle)
do {
let peFile = try PEFile(data: Data(contentsOf: program.url))
var icons: [NSImage] = []
if let resourceSection = peFile.resourceSection {
for entries in resourceSection.allEntries where entries.icon.isValid {
icons.append(entries.icon)
}
} else {
print("No resource section")
return
}

if icons.count > 0 {
image = icons.max(by: { $0.size.height < $1.size.height })
}
image = peFile.bestIcon()
} catch {
print(error)
}
Expand Down
15 changes: 2 additions & 13 deletions Whisky/Views/ProgramView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import SwiftUI
import WhiskyKit

struct ProgramView: View {
@Binding var program: Program
Expand Down Expand Up @@ -86,19 +87,7 @@ struct ProgramView: View {
.onAppear {
do {
let peFile = try PEFile(data: Data(contentsOf: program.url))
var icons: [NSImage] = []
if let resourceSection = peFile.resourceSection {
for entries in resourceSection.allEntries where entries.icon.isValid {
icons.append(entries.icon)
}
} else {
print("No resource section")
return
}

if icons.count > 0 {
image = icons.max(by: { $0.size.height < $1.size.height })
}
image = peFile.bestIcon()
} catch {
print(error)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
//
// DataExtensions.swift
// Whisky
// WhiskyKit
//
// Created by Isaac Marovitz on 01/04/2023.
//

import Foundation

extension Data {
func extract<T>(_ type: T.Type, offset: Int = 0) -> T? {
public func extract<T>(_ type: T.Type, offset: Int = 0) -> T? {
if offset + MemoryLayout<T>.size < self.count {
let data = self[offset..<offset + MemoryLayout<T>.size]
return data.withUnsafeBytes { $0.loadUnaligned(as: T.self) }
Expand All @@ -18,7 +18,7 @@ extension Data {
}

// Thanks ChatGPT
func nullTerminatedStrings(using encoding: String.Encoding = .utf8) -> [String] {
public func nullTerminatedStrings(using encoding: String.Encoding = .utf8) -> [String] {
var strings = [String]()
self.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) in
if let baseAddress = ptr.baseAddress {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
//
// URLExtensions.swift
// Whisky
// WhiskyKit
//
// Created by Isaac Marovitz on 13/06/2023.
//

import Foundation

extension String {
var esc: String {
public var esc: String {
let esc = ["\\", "\"", "'", " ", "(", ")", "[", "]", "{", "}", "&", "|",
";", "<", ">", "`", "$", "!", "*", "?", "#", "~", "="]
var str = self
Expand All @@ -20,11 +20,11 @@ extension String {
}

extension URL {
var esc: String {
public var esc: String {
path.esc
}

func prettyPath() -> String {
public func prettyPath() -> String {
var prettyPath = path
prettyPath = prettyPath
.replacingOccurrences(of: Bundle.main.bundleIdentifier ?? "com.isaacmarovitz.Whisky", with: "Whisky")
Expand Down
2 changes: 1 addition & 1 deletion WhiskyKit/GPTK/GPTKDownloader.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// GPTKDownloader.swift
// Whisky
// WhiskyKit
//
// Created by Isaac Marovitz on 19/06/2023.
//
Expand Down
2 changes: 1 addition & 1 deletion WhiskyKit/GPTK/GPTKInstaller.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// GPTKInstaller.swift
// Whisky
// WhiskyKit
//
// Created by Isaac Marovitz on 14/06/2023.
//
Expand Down
2 changes: 1 addition & 1 deletion WhiskyKit/PE/BitmapInfo.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// BitmapInfo.swift
// Whisky
// WhiskyKit
//
// Created by Isaac Marovitz on 09/09/2023.
//
Expand Down
22 changes: 20 additions & 2 deletions WhiskyKit/PE/PortableExecutable.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//
// PortableExecutable.swift
// Whisky
// WhiskyKit
//
// Created by Isaac Marovitz on 03/04/2023.
//

import Foundation
import AppKit

public struct PEError: Error {
public let message: String
Expand Down Expand Up @@ -226,7 +227,7 @@ public struct PEFile: Hashable {
public let coffFileHeader: COFFFileHeader
public let resourceSection: ResourceSection?

init(data: Data) throws {
public init(data: Data) throws {
// Verify it is a PE file by checking for the PE header
let offsetToPEHeader = data.extract(UInt32.self, offset: 0x3C) ?? 0
let peHeader = data.extract(UInt32.self, offset: Int(offsetToPEHeader))
Expand All @@ -238,4 +239,21 @@ public struct PEFile: Hashable {
sectionTable: coffFileHeader.sectionTable,
imageBase: coffFileHeader.optionalHeader.imageBase)
}

public func bestIcon() -> NSImage? {
var icons: [NSImage] = []
if let resourceSection = resourceSection {
for entries in resourceSection.allEntries where entries.icon.isValid {
icons.append(entries.icon)
}
} else {
print("No resource section")
}

if icons.count > 0 {
return icons.max(by: { $0.size.height < $1.size.height })
}

return NSImage()
}
}
2 changes: 1 addition & 1 deletion WhiskyKit/PE/ResourceSection.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// ResourceSection.swift
// Whisky
// WhiskyKit
//

import Foundation
Expand Down
2 changes: 1 addition & 1 deletion WhiskyKit/Tar.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// Tar.swift
// Whisky
// WhiskyKit
//
// Created by Isaac Marovitz on 14/06/2023.
//
Expand Down
6 changes: 6 additions & 0 deletions WhiskyThumbnail/Icons.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions WhiskyThumbnail/Icons.xcassets/Icon.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "512R512x1.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
22 changes: 22 additions & 0 deletions WhiskyThumbnail/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>QLSupportedContentTypes</key>
<array>
<string>com.microsoft.windows-executable</string>
</array>
<key>QLThumbnailMinimumDimension</key>
<integer>0</integer>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.quicklook.thumbnail</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ThumbnailProvider</string>
</dict>
</dict>
</plist>
58 changes: 58 additions & 0 deletions WhiskyThumbnail/ThumbnailProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// ThumbnailProvider.swift
// Whisky
//
// Created by Isaac Marovitz on 09/09/2023.
//

import Foundation
import QuickLookThumbnailing
import AppKit
import WhiskyKit

class ThumbnailProvider: QLThumbnailProvider {
override func provideThumbnail(for request: QLFileThumbnailRequest,
_ handler: @escaping (QLThumbnailReply?, Error?) -> Void) {
let thumbnailSize = CGSize(width: request.maximumSize.width,
height: request.maximumSize.height)

// % of thumbnail occupied by icon
let iconScaleFactor = 0.9
let whiskyIconScaleFactor = 0.4

// AppKit coordinate system origin is in the bottom-left
// Icon is centered
let iconFrame = CGRect(x: (request.maximumSize.width - request.maximumSize.width * iconScaleFactor) / 2.0,
y: (request.maximumSize.height - request.maximumSize.height * iconScaleFactor) / 2.0,
width: request.maximumSize.width * iconScaleFactor,
height: request.maximumSize.height * iconScaleFactor)

// Whisky icon is aligned bottom-right
let whiskyIconFrame = CGRect(x: request.maximumSize.width - request.maximumSize.width * whiskyIconScaleFactor,
y: 0,
width: request.maximumSize.width * whiskyIconScaleFactor,
height: request.maximumSize.height * whiskyIconScaleFactor)
do {
var image: NSImage?

let peFile = try PEFile(data: Data(contentsOf: request.fileURL))
image = peFile.bestIcon()

let reply: QLThumbnailReply = QLThumbnailReply.init(contextSize: thumbnailSize) { () -> Bool in
if let image = image {
image.draw(in: iconFrame)
let whiskyIcon = NSImage(named: NSImage.Name("Icon"))
whiskyIcon?.draw(in: whiskyIconFrame, from: .zero, operation: .sourceOver, fraction: 1)
return true
}

// We didn't draw anything
return false
}

handler(reply, nil)
} catch {
handler(nil, nil)
}
}
}
10 changes: 10 additions & 0 deletions WhiskyThumbnail/WhiskyThumbnail.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>

0 comments on commit edcb5cb

Please sign in to comment.