par_term/app/tab_ops/
profile_ops.rs1use std::sync::Arc;
7
8use crate::profile::{ProfileId, ProfileManager, storage as profile_storage};
9
10use super::super::window_state::WindowState;
11
12impl WindowState {
13 pub fn open_profile(&mut self, profile_id: ProfileId) {
15 log::debug!("open_profile called with id: {:?}", profile_id);
16
17 if self.config.load().tabs.max_tabs > 0
19 && self.tab_manager.tab_count() >= self.config.load().tabs.max_tabs
20 {
21 log::warn!(
22 "Cannot open profile: max_tabs limit ({}) reached",
23 self.config.load().tabs.max_tabs
24 );
25 self.deliver_notification(
26 "Tab Limit Reached",
27 &format!(
28 "Cannot open profile: maximum of {} tabs already open",
29 self.config.load().tabs.max_tabs
30 ),
31 );
32 return;
33 }
34
35 let profile = match self.overlay_ui.profile_manager.get(&profile_id) {
36 Some(p) => p.clone(),
37 None => {
38 log::error!("Profile not found: {:?}", profile_id);
39 return;
40 }
41 };
42 log::debug!("Found profile: {}", profile.name);
43
44 let grid_size = self.renderer.as_ref().map(|r| r.grid_size());
46
47 let prior_active_idx = self.tab_manager.active_tab_index();
48
49 match self.tab_manager.new_tab_from_profile(
50 &self.config.load(),
51 Arc::clone(&self.runtime),
52 &profile,
53 grid_size,
54 ) {
55 Ok(tab_id) => {
56 if self.config.load().tabs.new_tab_position
57 == crate::config::NewTabPosition::AfterActive
58 && let Some(idx) = prior_active_idx
59 {
60 self.tab_manager.move_tab_to_index(tab_id, idx + 1);
61 }
62
63 if let Some(tab) = self.tab_manager.get_tab_mut(tab_id) {
65 tab.profile.profile_icon = profile.icon.clone();
66 }
67
68 if let Some(window) = &self.window
70 && let Some(tab) = self.tab_manager.get_tab_mut(tab_id)
71 {
72 tab.start_refresh_task(
73 Arc::clone(&self.runtime),
74 Arc::clone(window),
75 self.config.load().rendering.max_fps,
76 self.config.load().power.inactive_tab_fps,
77 );
78
79 if let Some(renderer) = &self.renderer
84 && let Ok(mut term) = tab.terminal.try_write()
85 {
86 let (cols, rows) = renderer.grid_size();
87 let size = renderer.size();
88 let width_px = size.width as usize;
89 let height_px = size.height as usize;
90
91 term.set_cell_dimensions(
92 renderer.cell_width() as u32,
93 renderer.cell_height() as u32,
94 );
95 if let Err(e) = term.resize_with_pixels(cols, rows, width_px, height_px) {
96 crate::debug_error!(
97 "TERMINAL",
98 "resize_with_pixels failed (open_profile): {e}"
99 );
100 }
101 log::info!(
102 "Opened profile '{}' in tab {} ({}x{} at {}x{} px)",
103 profile.name,
104 tab_id,
105 cols,
106 rows,
107 width_px,
108 height_px
109 );
110 }
111 }
112
113 self.apply_profile_badge(&profile);
115 self.apply_profile_shader_settings(&profile);
116
117 self.focus_state.needs_redraw = true;
118 self.request_redraw();
119
120 if let Some(ref session_name) = profile.tmux_session_name
122 && self.config.load().tmux.tmux_enabled
123 && !self.is_gateway_active()
124 {
125 match profile.tmux_connection_mode {
126 par_term_config::TmuxConnectionMode::ControlMode => {
127 if let Err(e) = self.initiate_tmux_gateway(Some(session_name)) {
128 crate::debug_error!(
129 "TMUX",
130 "Profile tmux auto-connect failed: {}",
131 e
132 );
133 }
134 }
135 par_term_config::TmuxConnectionMode::Normal => {
136 let cmd = format!(
138 "{} new-session -A -s '{}'\n",
139 self.config.load().tmux.tmux_path,
140 session_name.replace('\'', "'\\''")
141 );
142 if let Some(tab) = self.tab_manager.active_tab_mut()
143 && let Ok(term) = tab.terminal.try_read()
144 && let Err(e) = term.write(cmd.as_bytes())
145 {
146 crate::debug_error!(
147 "TAB_ACTION",
148 "PTY write failed (tmux profile attach): {e}"
149 );
150 }
151 }
152 }
153 }
154 }
155 Err(e) => {
156 log::error!("Failed to open profile '{}': {}", profile.name, e);
157
158 let error_msg = e.to_string();
160 let (title, message) = if error_msg.contains("Unable to spawn")
161 || error_msg.contains("No viable candidates")
162 {
163 let cmd = profile
165 .command
166 .as_deref()
167 .unwrap_or("the configured command");
168 (
169 format!("Profile '{}' Failed", profile.name),
170 format!(
171 "Command '{}' not found. Check that it's installed and in your PATH.",
172 cmd
173 ),
174 )
175 } else if error_msg.contains("No such file or directory") {
176 (
177 format!("Profile '{}' Failed", profile.name),
178 format!(
179 "Working directory not found: {}",
180 profile.working_directory.as_deref().unwrap_or("(unknown)")
181 ),
182 )
183 } else {
184 (
185 format!("Profile '{}' Failed", profile.name),
186 format!("Failed to start: {}", error_msg),
187 )
188 };
189 self.deliver_notification(&title, &message);
190 }
191 }
192 }
193
194 pub(crate) fn apply_profile_badge(&mut self, profile: &crate::profile::Profile) {
199 {
201 let mut vars = self.badge_state.variables_mut();
202 vars.profile_name = profile.name.clone();
203 }
204
205 self.badge_state.apply_profile_settings(profile);
207
208 if profile.badge_text.is_some() {
209 crate::debug_info!(
210 "PROFILE",
211 "Applied profile badge settings: format='{}', color={:?}, alpha={}",
212 profile.badge_text.as_deref().unwrap_or(""),
213 profile.badge_color,
214 profile.badge_color_alpha.unwrap_or(0.0)
215 );
216 }
217
218 self.badge_state.mark_dirty();
220 }
221
222 pub(crate) fn apply_profile_shader_settings(&mut self, profile: &crate::profile::Profile) {
224 let mut changed = false;
225 if let Some(shader) = &profile.shader {
226 self.config.rcu(|old| {
227 let mut new = (**old).clone();
228 new.shader.custom_shader = Some(shader.clone());
229 std::sync::Arc::new(new)
230 });
231 self.config.rcu(|old| {
232 let mut new = (**old).clone();
233 new.shader.custom_shader_enabled = true;
234 std::sync::Arc::new(new)
235 });
236 changed = true;
237 }
238 if let Some(brightness) = profile.shader_brightness {
239 self.config.rcu(|old| {
240 let mut new = (**old).clone();
241 new.shader.custom_shader_brightness = brightness.clamp(0.05, 1.0);
242 std::sync::Arc::new(new)
243 });
244 changed = true;
245 }
246 if let Some(text_opacity) = profile.shader_text_opacity {
247 self.config.rcu(|old| {
248 let mut new = (**old).clone();
249 new.shader.custom_shader_text_opacity = text_opacity.clamp(0.0, 1.0);
250 std::sync::Arc::new(new)
251 });
252 changed = true;
253 }
254 if let Some(animation_speed) = profile.shader_animation_speed {
255 self.config.rcu(|old| {
256 let mut new = (**old).clone();
257 new.shader.custom_shader_animation_speed = animation_speed.clamp(0.0, 5.0);
258 std::sync::Arc::new(new)
259 });
260 changed = true;
261 }
262 if let Some(texture_set) = &profile.shader_texture_set {
263 self.config.rcu(|old| {
264 let mut new = (**old).clone();
265 new.shader.custom_shader_channel0 = texture_set[0].clone();
266 std::sync::Arc::new(new)
267 });
268 self.config.rcu(|old| {
269 let mut new = (**old).clone();
270 new.shader.custom_shader_channel1 = texture_set[1].clone();
271 std::sync::Arc::new(new)
272 });
273 self.config.rcu(|old| {
274 let mut new = (**old).clone();
275 new.shader.custom_shader_channel2 = texture_set[2].clone();
276 std::sync::Arc::new(new)
277 });
278 self.config.rcu(|old| {
279 let mut new = (**old).clone();
280 new.shader.custom_shader_channel3 = texture_set[3].clone();
281 std::sync::Arc::new(new)
282 });
283 changed = true;
284 }
285
286 if changed {
287 self.refresh_background_shader_renderer();
288 crate::debug_info!(
289 "PROFILE",
290 "Applied shader overrides for profile '{}'",
291 profile.name
292 );
293 }
294 }
295
296 pub fn toggle_profile_drawer(&mut self) {
298 self.overlay_ui.profile_drawer_ui.toggle();
299 self.focus_state.needs_redraw = true;
300 self.request_redraw();
301 }
302
303 pub fn save_profiles(&self) {
305 if let Err(e) = profile_storage::save_profiles(&self.overlay_ui.profile_manager) {
306 log::error!("Failed to save profiles: {}", e);
307 }
308 }
309
310 pub fn apply_profile_changes(&mut self, profiles: Vec<crate::profile::Profile>) {
312 self.overlay_ui.profile_manager = ProfileManager::from_profiles(profiles);
313 self.save_profiles();
314 self.overlay_state.profiles_menu_needs_update = true;
316 }
317}