-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathghosttyfetch.zig
More file actions
215 lines (170 loc) · 7.41 KB
/
ghosttyfetch.zig
File metadata and controls
215 lines (170 loc) · 7.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
const std = @import("std");
const builtin = @import("builtin");
const types = @import("src/types.zig");
const config = @import("src/config.zig");
const frames = @import("src/frames.zig");
const sysinfo = @import("src/sysinfo.zig");
const ui = @import("src/ui.zig");
const shell = @import("src/shell.zig");
const resize = @import("src/resize.zig");
const Allocator = types.Allocator;
const clear_screen = types.clear_screen;
const config_file = types.config_file;
const posix = std.posix;
// Global state for signal handler
var global_term_mode: ?*shell.TerminalMode = null;
fn signalHandler(_: c_int) callconv(.c) void {
// Restore terminal mode if active
if (global_term_mode) |tm| {
tm.restore();
}
// Exit immediately
std.process.exit(130); // 128 + SIGINT(2)
}
fn installSignalHandlers() void {
const act = posix.Sigaction{
.handler = .{ .handler = signalHandler },
.mask = posix.sigemptyset(),
.flags = 0,
};
posix.sigaction(posix.SIG.INT, &act, null);
posix.sigaction(posix.SIG.TERM, &act, null);
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const check = gpa.deinit();
if (check == .leak) {
std.debug.print("Warning: Memory leak detected\n", .{});
}
}
const allocator = gpa.allocator();
var exit_status: ?u8 = null;
defer if (exit_status) |code| std.process.exit(code);
const stdout_file = std.fs.File.stdout();
const stdin_file = std.fs.File.stdin();
// Check if running interactively (stdin is a TTY)
const is_interactive = stdin_file.isTty();
const cfg = config.loadConfig(allocator) catch |err| {
if (err == error.MissingConfig) {
std.debug.print("Config file '{s}' not found.\n", .{config_file});
std.debug.print("Searched in: ~/.config/ghosttyfetch/, /usr/share/ghosttyfetch/, and next to executable.\n", .{});
}
return err;
};
defer config.freeConfig(allocator, cfg);
resize.install();
var term_size = types.TerminalSize.detect(stdout_file) catch
types.TerminalSize{ .width = 120, .height = 40 };
var layout = frames.calculateLayout(term_size);
const fps = try config.resolveFps(allocator, cfg);
const prefs = try config.colorPreferences(allocator, cfg, stdout_file.isTty(), fps);
defer config.freeColorPreferences(allocator, prefs);
const sysinfo_lines = try sysinfo.loadSystemInfoLines(allocator, cfg.sysinfo);
defer sysinfo.freeSystemInfoLines(allocator, sysinfo_lines);
const raw_frames = try frames.loadRawFrames(allocator);
defer frames.freeFrames(allocator, raw_frames);
const orig_dims = frames.getFrameDimensions(raw_frames[0]);
var styled_info = try ui.stylizeInfoLines(allocator, sysinfo_lines, layout.info_width, prefs);
defer sysinfo.freeSystemInfoLines(allocator, styled_info);
if (cfg.match_info_height orelse false) {
layout.art_height = @max(10, styled_info.len);
layout.constrainToAspectRatio(orig_dims.width, orig_dims.height);
}
var frame_cache = try frames.LazyFrameCache.init(
allocator,
raw_frames,
layout.art_width,
layout.art_height,
prefs,
);
defer frame_cache.deinit();
// Get first frame to calculate initial width
const first_frame = try frame_cache.getFrame(0);
var frame_width = frames.frameVisibleWidth(first_frame);
var info_start_col = frame_width + 4;
const delay_ns = frames.fpsToDelayNs(fps);
const info_colors = ui.resolveInfoColors(prefs);
const prompt_prefix = try shell.buildPromptPrefix(allocator, prefs);
defer allocator.free(prompt_prefix);
var input_buffer = std.ArrayList(u8).empty;
defer input_buffer.deinit(allocator);
// Only enable raw mode if running interactively
var term_mode: ?shell.TerminalMode = null;
if (is_interactive) {
term_mode = try shell.TerminalMode.enable(stdin_file);
global_term_mode = &term_mode.?;
installSignalHandlers();
}
defer if (term_mode) |*tm| {
tm.restore();
global_term_mode = null;
};
var submitted_command: ?[]u8 = null;
defer if (submitted_command) |cmd| allocator.free(cmd);
var keep_running = true;
var frame_index: usize = 0;
while (keep_running) {
frame_index = 0;
while (frame_index < frame_cache.frameCount()) : (frame_index += 1) {
if (is_interactive and submitted_command == null) {
submitted_command = try shell.captureInput(allocator, stdin_file, &input_buffer);
}
const prompt_line = try shell.renderPromptLine(allocator, prompt_prefix, input_buffer.items, info_colors);
defer allocator.free(prompt_line);
// Get frame lazily - scales on first access, cached thereafter
const frame = try frame_cache.getFrame(frame_index);
const combined = try ui.combineFrameAndInfo(allocator, frame, styled_info, info_start_col);
defer allocator.free(combined);
const with_prompt = try ui.appendPromptLines(allocator, combined, prompt_line);
defer allocator.free(with_prompt);
try stdout_file.writeAll(clear_screen);
try stdout_file.writeAll(with_prompt);
if (submitted_command != null) {
keep_running = false;
break;
}
// Non-interactive mode: just show one full animation cycle and exit
if (!is_interactive and frame_index + 1 >= frame_cache.frameCount()) {
keep_running = false;
break;
}
if (resize.checkAndClear()) {
const new_term_size = types.TerminalSize.detect(stdout_file) catch
types.TerminalSize{ .width = 120, .height = 40 };
var new_layout = frames.calculateLayout(new_term_size);
const new_styled = try ui.stylizeInfoLines(allocator, sysinfo_lines, new_layout.info_width, prefs);
sysinfo.freeSystemInfoLines(allocator, styled_info);
styled_info = new_styled;
if (cfg.match_info_height orelse false) {
new_layout.art_height = @max(10, styled_info.len);
new_layout.constrainToAspectRatio(orig_dims.width, orig_dims.height);
}
frame_cache.resize(new_layout.art_width, new_layout.art_height);
term_size = new_term_size;
layout = new_layout;
// Recalculate frame width from first frame after resize
const resized_frame = try frame_cache.getFrame(0);
frame_width = frames.frameVisibleWidth(resized_frame);
info_start_col = frame_width + 4;
break; // Restart frame loop with new size
}
std.Thread.sleep(delay_ns);
}
}
// Restore terminal before running command
if (term_mode) |*tm| {
tm.restore();
global_term_mode = null;
}
if (submitted_command) |cmd| {
const command = std.mem.trim(u8, cmd, " \t\r\n");
if (command.len == 0) return;
try stdout_file.writeAll(clear_screen);
try stdout_file.writeAll(prompt_prefix);
try stdout_file.writeAll(command);
try stdout_file.writeAll("\n");
const code = try shell.runCommandInShell(allocator, command);
exit_status = @as(u8, @intCast(code));
}
}