v1.2.0: Fix app icons and DockTilePlugin rename

- Rename WarpDockTilePlugin to GalaxyDockTilePlugin
- Fix runtime icon switching to load from compiled-in assets
- Add NSDockTilePlugIn key to embedded Info.plist
- Replace all channel icons with padded Galaxy variants
- Update build.rs references for renamed plugin
- No longer requires post-bundle steps for icon switching
- Bump version to 1.2.0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ryan Ward
2026-05-19 12:39:43 -05:00
co-authored by Claude Opus 4.6
parent ccc67e6a33
commit 53914688c0
32 changed files with 291 additions and 1146 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ description = "Galaxy - AI-powered terminal"
edition = "2021"
autobins = false
name = "galaxy"
version = "1.1.0"
version = "1.2.0"
publish.workspace = true
license.workspace = true
@@ -1,7 +1,7 @@
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
@interface WarpDockTilePlugIn : NSObject <NSDockTilePlugIn>
@interface GalaxyDockTilePlugIn : NSObject <NSDockTilePlugIn>
{
id iconChangedObserver;
id defaultsObserver;
@@ -1,6 +1,6 @@
#include "WarpDockTilePlugin.h"
#include "GalaxyDockTilePlugin.h"
@implementation WarpDockTilePlugIn {
@implementation GalaxyDockTilePlugIn {
NSFileHandle *_logFileHandle;
}
@@ -25,14 +25,13 @@
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd_HH-mm-ss"];
NSString *timestamp = [formatter stringFromDate:[NSDate date]];
NSString *logPath = [NSString stringWithFormat:@"/tmp/warp_docktile_%@.log", timestamp];
NSError *error = nil;
NSString *logPath = [NSString stringWithFormat:@"/tmp/galaxy_docktile_%@.log", timestamp];
[[NSFileManager defaultManager] createFileAtPath:logPath contents:nil attributes:nil];
_logFileHandle = [NSFileHandle fileHandleForWritingAtPath:logPath];
[self logMessage:@"WarpDockTilePlugin initialized"];
[self logMessage:@"GalaxyDockTilePlugin initialized"];
} @catch (NSException *exception) {
NSLog(@"Exception during initialization: %@\nStack trace: %@",
exception.reason,
NSLog(@"Exception during initialization: %@\nStack trace: %@",
exception.reason,
exception.callStackSymbols);
}
}
@@ -42,90 +41,60 @@
- (void)updateAppIcon:(NSDockTile *)tile {
@try {
[self logMessage:@"updateAppIcon called"];
// Retrieve the bundle ID for the main app from the Info.plist file in the plugin bundle
NSBundle *pluginBundle = [NSBundle bundleForClass:[self class]];
NSBundle *pluginBundle = [NSBundle bundleForClass:[self class]];
NSString *path = [[pluginBundle bundlePath] stringByAppendingPathComponent:@"Contents/Info.plist"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSString *bundleId = dict[@"MainAppBundleIdentifier"];
BOOL isDev = [bundleId containsString:@"Dev"];
BOOL isPreview = [bundleId containsString:@"Preview"];
BOOL isLocal = [bundleId containsString:@"Local"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSString *bundleId = dict[@"MainAppBundleIdentifier"];
[self logMessage:[NSString stringWithFormat:@"Plugin Bundle ID: %@", bundleId]];
// Initialize the user defaults for the main app
NSUserDefaults *hostDefaults = [[NSUserDefaults alloc] initWithSuiteName:bundleId];
[hostDefaults synchronize];
// Get the icon name from the user defaults
NSString* appIconName = [hostDefaults stringForKey:@"AppIcon"];
// Check if the user has set a non-default icon. If the AppIcon key is nil, empty, or "Default", reset to the
// icon bundled with the app by setting the content to "nil". Using the icon bundled in the app will allow macOS
// to handle the icon, including respecting "Icon & Widget Style" setting and applying a color filter. Non-
// default icons do not respect that setting.
NSString* cleanName = [[appIconName stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\""]] lowercaseString];
if (!appIconName || [appIconName length] == 0 || [cleanName isEqualToString:@"default"]) {
if (!appIconName || [appIconName length] == 0 || [cleanName isEqualToString:@"galaxy"]) {
[self logMessage:@"User has default icon, resetting dock tile to system default"];
[tile setContentView:nil];
[tile display];
return;
}
NSString* iconFileName = [self convertAppIconNameToFileName:appIconName isDev:isDev isLocal:isLocal isPreview:isPreview];
NSString* iconFileName = [self convertAppIconNameToFileName:appIconName];
[self logMessage:[NSString stringWithFormat:@"Icon file name: %@", iconFileName]];
// Load the icon image
NSImage* currentImage = [self LoadDockTileImage:iconFileName];
// Set the image on the dock tile
// 256 x 256 is the preferred size for retina dock icons.
NSImageView* imageView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 256, 256)];
[imageView setImage:currentImage];
[imageView setImageScaling:NSImageScaleProportionallyUpOrDown];
[tile setContentView:imageView];
[tile display];
[self logMessage:[NSString stringWithFormat:@"Dock tile updated with icon: %@", iconFileName]];
} @catch (NSException *exception) {
[self logMessage:[NSString stringWithFormat:@"Exception updating dock tile icon: %@\nStack trace: %@\nTile: %@",
exception.reason,
[self logMessage:[NSString stringWithFormat:@"Exception updating dock tile icon: %@\nStack trace: %@\nTile: %@",
exception.reason,
exception.callStackSymbols,
tile ? @"valid" : @"nil"]];
}
}
// See app_icon.rs for the rust version of this conversion.
- (NSString*)convertAppIconNameToFileName:(NSString*)appIconName isDev:(BOOL)isDev isLocal:(BOOL)isLocal isPreview:(BOOL)isPreview {
// First remove quotes and convert to lowercase
- (NSString*)convertAppIconNameToFileName:(NSString*)appIconName {
NSString* cleanName = [[appIconName stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\""]] lowercaseString];
NSDictionary* mapping = @{
@"aurora": @"aurora",
@"classic1": @"classic_1",
@"classic2": @"classic_2",
@"classic3": @"classic_3",
@"comets": @"comets",
@"glasssky": @"glass_sky",
@"glitch": @"glitch",
@"glow": @"glow",
@"holographic": @"holographic",
@"mono": @"mono",
@"neon": @"neon",
@"original": @"original",
@"starburst": @"starburst",
@"sticker": @"sticker",
@"warpone": @"blue",
@"cow": @"cow"
};
NSString* fileName = mapping[cleanName];
// If the mapping doesn't exist, return the default icon
// conditional on whether this is a local, dev, or preview build.
return fileName ?: isLocal ? @"local" : isDev ? @"dev" : isPreview ? @"preview" : @"warp_2";
NSDictionary* mapping = @{
@"galaxy": @"galaxy",
@"dotmatrix": @"galaxy_dotmatrix",
@"explorer": @"galaxy_explorer",
@"rainbow": @"galaxy_rainbow",
@"wormhole": @"galaxy_wormhole",
};
NSString* fileName = mapping[cleanName];
return fileName ?: @"galaxy";
}
// Helper function to load named image from the plugin's resource bundle
- (NSImage*)LoadDockTileImage:(NSString*)imageName {
NSBundle* pluginBundle = [NSBundle bundleForClass:[self class]];
NSString* imagePath = [pluginBundle pathForResource:imageName ofType:@"png"];
@@ -137,21 +106,17 @@
return [[NSImage alloc] initWithContentsOfFile:imagePath];
}
// Protocol method that is invoked by the system when the dock for Warp is updated.
// Note that we listen for direct changes to the AppIcon key in the user defaults.
- (void)setDockTile:(NSDockTile *)dockTile {
@try {
[self logMessage:[NSString stringWithFormat:@"setDockTile called with tile: %@", dockTile ? @"valid" : @"nil"]];
if (dockTile) {
// Get the bundle ID for setting up user defaults observation
NSBundle *pluginBundle = [NSBundle bundleForClass:[WarpDockTilePlugIn class]];
NSBundle *pluginBundle = [NSBundle bundleForClass:[GalaxyDockTilePlugIn class]];
NSString *path = [[pluginBundle bundlePath] stringByAppendingPathComponent:@"Contents/Info.plist"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSString *bundleId = dict[@"MainAppBundleIdentifier"];
[self logMessage:[NSString stringWithFormat:@"Main app bundleId: %@", bundleId]];
// Set up user defaults observer
NSUserDefaults *hostDefaults = [[NSUserDefaults alloc] initWithSuiteName:bundleId];
[hostDefaults addObserver:self
forKeyPath:@"AppIcon"
@@ -161,8 +126,7 @@
[self logMessage:[NSString stringWithFormat:@"Host defaults: %@", hostDefaults]];
// Make sure the icon is updated from the get-go as well.
[self updateAppIcon:dockTile];
[self updateAppIcon:dockTile];
} else {
[self logMessage:@"No docktile, clearing icon observer"];
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self.iconChangedObserver];
@@ -173,8 +137,8 @@
}
}
} @catch (NSException *exception) {
[self logMessage:[NSString stringWithFormat:@"Exception in setDockTile: %@\nStack trace: %@\nDockTile: %@",
exception.reason,
[self logMessage:[NSString stringWithFormat:@"Exception in setDockTile: %@\nStack trace: %@\nDockTile: %@",
exception.reason,
exception.callStackSymbols,
dockTile ? @"valid" : @"nil"]];
}
@@ -182,7 +146,7 @@
- (void)dealloc {
@try {
[self logMessage:@"WarpDockTilePlugin deallocating"];
[self logMessage:@"GalaxyDockTilePlugin deallocating"];
if (self.iconChangedObserver) {
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self.iconChangedObserver];
self.iconChangedObserver = nil;
@@ -191,20 +155,19 @@
[(NSUserDefaults *)self.defaultsObserver removeObserver:self forKeyPath:@"AppIcon"];
self.defaultsObserver = nil;
}
if (_logFileHandle) {
[self logMessage:@"Closing log file"];
[_logFileHandle closeFile];
_logFileHandle = nil;
}
} @catch (NSException *exception) {
NSLog(@"Exception during deallocation: %@\nStack trace: %@",
exception.reason,
NSLog(@"Exception during deallocation: %@\nStack trace: %@",
exception.reason,
exception.callStackSymbols);
}
}
// KVO callback method
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
@@ -216,8 +179,8 @@
[self updateAppIcon:dockTile];
}
} @catch (NSException *exception) {
[self logMessage:[NSString stringWithFormat:@"Exception in KVO handler: %@\nStack trace: %@\nKeyPath: %@\nObject: %@\nChange: %@",
exception.reason,
[self logMessage:[NSString stringWithFormat:@"Exception in KVO handler: %@\nStack trace: %@\nKeyPath: %@\nObject: %@\nChange: %@",
exception.reason,
exception.callStackSymbols,
keyPath,
object,
+2 -2
View File
@@ -5,7 +5,7 @@
<key>CFBundleIdentifier</key>
<string>samsung.galaxy.GalaxyDockTilePlugin</string>
<key>CFBundleExecutable</key>
<string>WarpDockTilePlugin</string>
<string>GalaxyDockTilePlugin</string>
<key>CFBundleName</key>
<string>GalaxyDockTilePlugin</string>
<key>CFBundlePackageType</key>
@@ -15,7 +15,7 @@
<key>CFBundleVersion</key>
<string>1</string>
<key>NSPrincipalClass</key>
<string>WarpDockTilePlugin</string>
<string>GalaxyDockTilePlugIn</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
</dict>
+3 -3
View File
@@ -1,5 +1,5 @@
BUNDLE_NAME = WarpDockTilePlugin.docktileplugin
OBJC_FILES = WarpDockTilePlugin.m
BUNDLE_NAME = GalaxyDockTilePlugin.docktileplugin
OBJC_FILES = GalaxyDockTilePlugin.m
FRAMEWORKS = -framework Cocoa -framework AppKit -framework Foundation
# Compile a universal binary for both arm64 and x86_64.
CFLAGS = -fobjc-arc -bundle -mmacosx-version-min=$(MACOSX_DEPLOYMENT_TARGET) -arch arm64 -arch x86_64
@@ -11,7 +11,7 @@ all: $(BUNDLE_NAME)
$(BUNDLE_NAME): $(OBJC_FILES)
mkdir -p $(BUNDLE_NAME)/Contents/MacOS
clang $(CFLAGS) $(LDFLAGS) $(FRAMEWORKS) $(OBJC_FILES) -o $(BUNDLE_NAME)/Contents/MacOS/WarpDockTilePlugin
clang $(CFLAGS) $(LDFLAGS) $(FRAMEWORKS) $(OBJC_FILES) -o $(BUNDLE_NAME)/Contents/MacOS/GalaxyDockTilePlugin
cp Info.plist $(BUNDLE_NAME)/Contents/
mkdir -p $(BUNDLE_NAME)/Contents/Resources
cp Resources/* $(BUNDLE_NAME)/Contents/Resources/
Binary file not shown.

Before

Width:  |  Height:  |  Size: 787 KiB

After

Width:  |  Height:  |  Size: 568 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1024 KiB

After

Width:  |  Height:  |  Size: 721 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 718 KiB

After

Width:  |  Height:  |  Size: 517 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 841 KiB

After

Width:  |  Height:  |  Size: 607 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 789 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 787 KiB

After

Width:  |  Height:  |  Size: 568 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1024 KiB

After

Width:  |  Height:  |  Size: 721 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 718 KiB

After

Width:  |  Height:  |  Size: 517 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 841 KiB

After

Width:  |  Height:  |  Size: 607 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 789 KiB

+5 -4
View File
@@ -47,8 +47,8 @@ fn main() -> Result<()> {
.compile("warp_objc");
// Build the dock tile plugin
println!("cargo:rerun-if-changed=DockTilePlugin/WarpDockTilePlugin.m");
println!("cargo:rerun-if-changed=DockTilePlugin/WarpDockTilePlugin.h");
println!("cargo:rerun-if-changed=DockTilePlugin/GalaxyDockTilePlugin.m");
println!("cargo:rerun-if-changed=DockTilePlugin/GalaxyDockTilePlugin.h");
println!("cargo:rerun-if-changed=DockTilePlugin/Info.plist");
println!("cargo:rerun-if-changed=DockTilePlugin/Makefile");
@@ -66,8 +66,9 @@ fn main() -> Result<()> {
// Copy the dock tile plugin to the output directory
let profile = get_build_profile_name();
let target_dir = app_target_dir(&profile).expect("Failed to get app target directory");
let plugin_src = Path::new("DockTilePlugin/WarpDockTilePlugin.docktileplugin");
let plugin_dst = target_dir.join("WarpDockTilePlugin.docktileplugin");
let plugin_src = Path::new("DockTilePlugin/GalaxyDockTilePlugin.docktileplugin");
let plugin_dst = target_dir.join("GalaxyDockTilePlugin.docktileplugin");
if !status.success() {
fs::remove_dir_all(plugin_src).expect("Failed to clean up plugin directory");
Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB

After

Width:  |  Height:  |  Size: 172 KiB

+7 -23
View File
@@ -239,32 +239,16 @@ impl AppearanceManager {
let icon_name = AppIconSettings::get_base_icon_file_name(icon);
log::debug!("Setting app icon in memory to: {icon_name}");
// Locate the plugin bundle.
let plugins_path: id = msg_send![bundle, builtInPlugInsPath];
let plugin_name = make_nsstring("WarpDockTilePlugin.docktileplugin");
let plugin_path: id =
msg_send![plugins_path, stringByAppendingPathComponent: plugin_name];
let plugin_bundle: id = msg_send![class!(NSBundle), bundleWithPath: plugin_path];
if plugin_bundle == nil {
log::warn!("Failed to get dock tile plugin bundle");
// Load icon from the compiled-in assets.
let asset_path = format!("bundled/png/{icon_name}.png");
let Ok(icon_data) = crate::ASSETS.get(&asset_path) else {
log::warn!("Failed to load icon asset: {asset_path}");
return;
}
};
// Read the images from the plugin bundle.
let image_name = make_nsstring(icon_name);
let extension = make_nsstring("png");
let image_path: id =
msg_send![plugin_bundle, pathForResource:image_name ofType:extension];
if image_path == nil {
log::warn!("Failed to get image path for icon: {icon_name}");
return;
}
// Create the image from the file.
let ns_data: id = msg_send![class!(NSData), dataWithBytes:icon_data.as_ptr() length:icon_data.len()];
let image: id = msg_send![class!(NSImage), alloc];
let image: id = msg_send![image, initWithContentsOfFile:image_path];
let image: id = msg_send![image, initWithData:ns_data];
if image == nil {
log::warn!("Failed to create image for icon: {icon_name}");
+2
View File
@@ -62,6 +62,8 @@ embed_plist::embed_info_plist_bytes!(r#"
<array><dict><key>CFBundleURLName</key><string>Galaxy</string><key>CFBundleURLSchemes</key><array><string>galaxyai</string></array></dict></array>
<key>NSHumanReadableCopyright</key>
<string>© 2026, Samsung Electronics Co., Ltd.</string>
<key>NSDockTilePlugIn</key>
<string>GalaxyDockTilePlugin.docktileplugin</string>
</dict>
</plist>
"#.as_bytes());
+1 -1
View File
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
/// The app icon to use (mac-only).
///
/// IMPORTANT NOTE: If you add a new icon, you will need to update the logic in WarpDockTilePlugin.m
/// IMPORTANT NOTE: If you add a new icon, you will need to update the logic in GalaxyDockTilePlugin.m
/// to read the new icon and also add the icon to app/DockTilePlugin/Resources.
#[derive(
Default,