mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
config: macos-icon configurations
This commit is contained in:
@ -342,6 +342,7 @@ typedef struct {
|
|||||||
uint8_t b;
|
uint8_t b;
|
||||||
} ghostty_config_color_s;
|
} ghostty_config_color_s;
|
||||||
|
|
||||||
|
// config.ColorList
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const ghostty_config_color_s* colors;
|
const ghostty_config_color_s* colors;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
@ -528,7 +528,7 @@ class AppDelegate: NSObject,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let colorizedIcon = ColorizedGhosttyIcon(
|
if let colorizedIcon = ColorizedGhosttyIcon(
|
||||||
screenColors: [.purple, .blue],
|
screenColors: [],
|
||||||
ghostColor: .yellow
|
ghostColor: .yellow
|
||||||
).makeImage() {
|
).makeImage() {
|
||||||
self.appIcon = colorizedIcon
|
self.appIcon = colorizedIcon
|
||||||
|
@ -47,6 +47,37 @@ extension OSColor {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create an OSColor from a hex string.
|
||||||
|
convenience init?(hex: String) {
|
||||||
|
var cleanedHex = hex.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
|
// Remove `#` if present
|
||||||
|
if cleanedHex.hasPrefix("#") {
|
||||||
|
cleanedHex.removeFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
guard cleanedHex.count == 6 || cleanedHex.count == 8 else { return nil }
|
||||||
|
|
||||||
|
let scanner = Scanner(string: cleanedHex)
|
||||||
|
var hexNumber: UInt64 = 0
|
||||||
|
guard scanner.scanHexInt64(&hexNumber) else { return nil }
|
||||||
|
|
||||||
|
let red, green, blue, alpha: CGFloat
|
||||||
|
if cleanedHex.count == 8 {
|
||||||
|
alpha = CGFloat((hexNumber & 0xFF000000) >> 24) / 255
|
||||||
|
red = CGFloat((hexNumber & 0x00FF0000) >> 16) / 255
|
||||||
|
green = CGFloat((hexNumber & 0x0000FF00) >> 8) / 255
|
||||||
|
blue = CGFloat(hexNumber & 0x000000FF) / 255
|
||||||
|
} else { // 6 characters
|
||||||
|
alpha = 1.0
|
||||||
|
red = CGFloat((hexNumber & 0xFF0000) >> 16) / 255
|
||||||
|
green = CGFloat((hexNumber & 0x00FF00) >> 8) / 255
|
||||||
|
blue = CGFloat(hexNumber & 0x0000FF) / 255
|
||||||
|
}
|
||||||
|
|
||||||
|
self.init(red: red, green: green, blue: blue, alpha: alpha)
|
||||||
|
}
|
||||||
|
|
||||||
func darken(by amount: CGFloat) -> OSColor {
|
func darken(by amount: CGFloat) -> OSColor {
|
||||||
var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
|
var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
|
||||||
self.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
|
self.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
|
||||||
|
@ -16,6 +16,7 @@ const build_config = @import("../build_config.zig");
|
|||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
const build_config = @import("../build_config.zig");
|
||||||
const global_state = &@import("../global.zig").state;
|
const global_state = &@import("../global.zig").state;
|
||||||
const fontpkg = @import("../font/main.zig");
|
const fontpkg = @import("../font/main.zig");
|
||||||
const inputpkg = @import("../input.zig");
|
const inputpkg = @import("../input.zig");
|
||||||
@ -1675,6 +1676,40 @@ keybind: Keybinds = .{},
|
|||||||
/// you may want to disable it.
|
/// you may want to disable it.
|
||||||
@"macos-secure-input-indication": bool = true,
|
@"macos-secure-input-indication": bool = true,
|
||||||
|
|
||||||
|
/// Customize the macOS app icon.
|
||||||
|
///
|
||||||
|
/// This only affects the icon that appears in the dock, application
|
||||||
|
/// switcher, Activity Monitor, etc. This does not affect the icon
|
||||||
|
/// in Finder because that is controlled by a hardcoded value in the
|
||||||
|
/// signed application bundle and can't be changed at runtime.
|
||||||
|
///
|
||||||
|
/// Valid values:
|
||||||
|
///
|
||||||
|
/// * `official` - Use the official Ghostty icon.
|
||||||
|
/// * `custom-color` - Use the official Ghostty icon but with custom
|
||||||
|
/// colors applied to various layers. The custom colors must be
|
||||||
|
/// specified using `macos-icon-layer-color`.
|
||||||
|
///
|
||||||
|
@"macos-icon": MacAppIcon = .official,
|
||||||
|
|
||||||
|
/// The color of the ghost in the macOS app icon.
|
||||||
|
///
|
||||||
|
/// The format of the color is the same as the `background` configuration;
|
||||||
|
/// see that for more information.
|
||||||
|
///
|
||||||
|
/// This only has an effect when `macos-icon` is set to `custom-color`.
|
||||||
|
@"macos-icon-ghost-color": ?Color = null,
|
||||||
|
|
||||||
|
/// The color of the screen in the macOS app icon.
|
||||||
|
///
|
||||||
|
/// The screen is a gradient so you can specify multiple colors that
|
||||||
|
/// make up the gradient. Colors should be separated by commas. The
|
||||||
|
/// format of the color is the same as the `background` configuration;
|
||||||
|
/// see that for more information.
|
||||||
|
///
|
||||||
|
/// This only has an effect when `macos-icon` is set to `custom-color`.
|
||||||
|
@"macos-icon-screen-color": ?ColorList = null,
|
||||||
|
|
||||||
/// Put every surface (tab, split, window) into a dedicated Linux cgroup.
|
/// Put every surface (tab, split, window) into a dedicated Linux cgroup.
|
||||||
///
|
///
|
||||||
/// This makes it so that resource management can be done on a per-surface
|
/// This makes it so that resource management can be done on a per-surface
|
||||||
@ -3577,12 +3612,17 @@ pub const Color = struct {
|
|||||||
var buf: [128]u8 = undefined;
|
var buf: [128]u8 = undefined;
|
||||||
try formatter.formatEntry(
|
try formatter.formatEntry(
|
||||||
[]const u8,
|
[]const u8,
|
||||||
std.fmt.bufPrint(
|
try self.formatBuf(&buf),
|
||||||
&buf,
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format the color as a string.
|
||||||
|
pub fn formatBuf(self: Color, buf: []u8) ![]const u8 {
|
||||||
|
return std.fmt.bufPrint(
|
||||||
|
buf,
|
||||||
"#{x:0>2}{x:0>2}{x:0>2}",
|
"#{x:0>2}{x:0>2}{x:0>2}",
|
||||||
.{ self.r, self.g, self.b },
|
.{ self.r, self.g, self.b },
|
||||||
) catch return error.OutOfMemory,
|
) catch error.OutOfMemory;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// fromHex parses a color from a hex value such as #RRGGBB. The "#"
|
/// fromHex parses a color from a hex value such as #RRGGBB. The "#"
|
||||||
@ -3637,6 +3677,130 @@ pub const Color = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const ColorList = struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
colors: std.ArrayListUnmanaged(Color) = .{},
|
||||||
|
colors_c: std.ArrayListUnmanaged(Color.C) = .{},
|
||||||
|
|
||||||
|
/// ghostty_config_color_list_s
|
||||||
|
pub const C = extern struct {
|
||||||
|
colors: [*]Color.C,
|
||||||
|
len: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn cval(self: *const Self) C {
|
||||||
|
return .{
|
||||||
|
.colors = self.colors_c.items.ptr,
|
||||||
|
.len = self.colors_c.items.len,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parseCLI(
|
||||||
|
self: *Self,
|
||||||
|
alloc: Allocator,
|
||||||
|
input_: ?[]const u8,
|
||||||
|
) !void {
|
||||||
|
const input = input_ orelse return error.ValueRequired;
|
||||||
|
if (input.len == 0) return error.ValueRequired;
|
||||||
|
|
||||||
|
// Whenever a color list is set, we reset the list
|
||||||
|
self.colors.clearRetainingCapacity();
|
||||||
|
|
||||||
|
// Split the input by commas and parse each color
|
||||||
|
var it = std.mem.tokenizeScalar(u8, input, ',');
|
||||||
|
var count: usize = 0;
|
||||||
|
while (it.next()) |raw| {
|
||||||
|
count += 1;
|
||||||
|
if (count > 64) return error.InvalidValue;
|
||||||
|
|
||||||
|
const color = try Color.parseCLI(raw);
|
||||||
|
try self.colors.append(alloc, color);
|
||||||
|
try self.colors_c.append(alloc, color.cval());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no colors were parsed, we need to return an error
|
||||||
|
if (self.colors.items.len == 0) return error.InvalidValue;
|
||||||
|
|
||||||
|
assert(self.colors.items.len == self.colors_c.items.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone(
|
||||||
|
self: *const Self,
|
||||||
|
alloc: Allocator,
|
||||||
|
) Allocator.Error!Self {
|
||||||
|
return .{
|
||||||
|
.colors = try self.colors.clone(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare if two of our value are requal. Required by Config.
|
||||||
|
pub fn equal(self: Self, other: Self) bool {
|
||||||
|
const itemsA = self.colors.items;
|
||||||
|
const itemsB = other.colors.items;
|
||||||
|
if (itemsA.len != itemsB.len) return false;
|
||||||
|
for (itemsA, itemsB) |a, b| {
|
||||||
|
if (!a.equal(b)) return false;
|
||||||
|
} else return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used by Formatter
|
||||||
|
pub fn formatEntry(self: Self, formatter: anytype) !void {
|
||||||
|
// If no items, we want to render an empty field.
|
||||||
|
if (self.colors.items.len == 0) {
|
||||||
|
try formatter.formatEntry(void, {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up the value of our config. Our buffer size should be
|
||||||
|
// sized to contain all possible maximum values.
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var fbs = std.io.fixedBufferStream(&buf);
|
||||||
|
var writer = fbs.writer();
|
||||||
|
for (self.colors.items, 0..) |color, i| {
|
||||||
|
var color_buf: [128]u8 = undefined;
|
||||||
|
const color_str = try color.formatBuf(&color_buf);
|
||||||
|
if (i != 0) try writer.writeByte(',');
|
||||||
|
try writer.writeAll(color_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
try formatter.formatEntry(
|
||||||
|
[]const u8,
|
||||||
|
fbs.getWritten(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parseCLI" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
var p: Self = .{};
|
||||||
|
try p.parseCLI(alloc, "black,white");
|
||||||
|
try testing.expectEqual(2, p.colors.items.len);
|
||||||
|
|
||||||
|
// Error cases
|
||||||
|
try testing.expectError(error.ValueRequired, p.parseCLI(alloc, null));
|
||||||
|
try testing.expectError(error.InvalidValue, p.parseCLI(alloc, " "));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "format" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
var p: Self = .{};
|
||||||
|
try p.parseCLI(alloc, "black,white");
|
||||||
|
try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||||
|
try std.testing.expectEqualSlices(u8, "a = #000000,#ffffff\n", buf.items);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// Palette is the 256 color palette for 256-color mode. This is still
|
/// Palette is the 256 color palette for 256-color mode. This is still
|
||||||
/// used by many terminal applications.
|
/// used by many terminal applications.
|
||||||
pub const Palette = struct {
|
pub const Palette = struct {
|
||||||
@ -3753,7 +3917,7 @@ pub const RepeatableString = struct {
|
|||||||
return .{ .list = list };
|
return .{ .list = list };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The number of itemsin the list
|
/// The number of items in the list
|
||||||
pub fn count(self: Self) usize {
|
pub fn count(self: Self) usize {
|
||||||
return self.list.items.len;
|
return self.list.items.len;
|
||||||
}
|
}
|
||||||
@ -4922,6 +5086,16 @@ pub const MacTitlebarProxyIcon = enum {
|
|||||||
hidden,
|
hidden,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// See macos-icon
|
||||||
|
///
|
||||||
|
/// Note: future versions of Ghostty can support a custom icon with
|
||||||
|
/// path by changing this to a tagged union, which doesn't change our
|
||||||
|
/// format at all.
|
||||||
|
pub const MacAppIcon = enum {
|
||||||
|
official,
|
||||||
|
@"custom-color",
|
||||||
|
};
|
||||||
|
|
||||||
/// See gtk-single-instance
|
/// See gtk-single-instance
|
||||||
pub const GtkSingleInstance = enum {
|
pub const GtkSingleInstance = enum {
|
||||||
desktop,
|
desktop,
|
||||||
|
Reference in New Issue
Block a user