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 #[cfg(feature = "mux")]
56 let mux_only = {
57 let mux_only = profile.mux_session_name.is_some()
58 && profile.command.is_none()
59 && profile.tmux_session_name.is_none();
60 if let Some(ref mux_name) = profile.mux_session_name
61 && self.tmux_state.transport.is_none()
62 && !self.is_gateway_active()
63 {
64 self.begin_mux_session_attach(mux_name);
65 }
66 mux_only
67 };
68 #[cfg(not(feature = "mux"))]
69 let mux_only = false;
70
71 if !mux_only {
72 match self.tab_manager.new_tab_from_profile(
73 &self.config.load(),
74 Arc::clone(&self.runtime),
75 &profile,
76 grid_size,
77 ) {
78 Ok(tab_id) => {
79 if self.config.load().tabs.new_tab_position
80 == crate::config::NewTabPosition::AfterActive
81 && let Some(idx) = prior_active_idx
82 {
83 self.tab_manager.move_tab_to_index(tab_id, idx + 1);
84 }
85
86 if let Some(tab) = self.tab_manager.get_tab_mut(tab_id) {
88 tab.profile.profile_icon = profile.icon.clone();
89 }
90
91 if let Some(window) = &self.window
93 && let Some(tab) = self.tab_manager.get_tab_mut(tab_id)
94 {
95 tab.start_refresh_task(
96 Arc::clone(&self.runtime),
97 Arc::clone(window),
98 self.config.load().rendering.max_fps,
99 self.config.load().power.inactive_tab_fps,
100 );
101
102 if let Some(renderer) = &self.renderer
107 && let Ok(mut term) = tab.terminal.try_write()
108 {
109 let (cols, rows) = renderer.grid_size();
110 let size = renderer.size();
111 let width_px = size.width as usize;
112 let height_px = size.height as usize;
113
114 term.set_cell_dimensions(
115 renderer.cell_width() as u32,
116 renderer.cell_height() as u32,
117 );
118 if let Err(e) = term.resize_with_pixels(cols, rows, width_px, height_px)
119 {
120 crate::debug_error!(
121 "TERMINAL",
122 "resize_with_pixels failed (open_profile): {e}"
123 );
124 }
125 log::info!(
126 "Opened profile '{}' in tab {} ({}x{} at {}x{} px)",
127 profile.name,
128 tab_id,
129 cols,
130 rows,
131 width_px,
132 height_px
133 );
134 }
135 }
136
137 self.apply_profile_badge(&profile);
139 self.apply_profile_shader_settings(&profile);
140
141 self.focus_state.needs_redraw = true;
142 self.request_redraw();
143
144 if let Some(ref session_name) = profile.tmux_session_name
146 && self.config.load().tmux.tmux_enabled
147 && !self.is_gateway_active()
148 {
149 match profile.tmux_connection_mode {
150 par_term_config::TmuxConnectionMode::ControlMode => {
151 if let Err(e) = self.initiate_tmux_gateway(Some(session_name)) {
152 crate::debug_error!(
153 "TMUX",
154 "Profile tmux auto-connect failed: {}",
155 e
156 );
157 }
158 }
159 par_term_config::TmuxConnectionMode::Normal => {
160 let cmd = format!(
162 "{} new-session -A -s '{}'\n",
163 self.config.load().tmux.tmux_path,
164 session_name.replace('\'', "'\\''")
165 );
166 if let Some(tab) = self.tab_manager.active_tab_mut()
167 && let Ok(term) = tab.terminal.try_read()
168 && let Err(e) = term.write(cmd.as_bytes())
169 {
170 crate::debug_error!(
171 "TAB_ACTION",
172 "PTY write failed (tmux profile attach): {e}"
173 );
174 }
175 }
176 }
177 }
178 }
179 Err(e) => {
180 log::error!("Failed to open profile '{}': {}", profile.name, e);
181
182 let error_msg = e.to_string();
184 let (title, message) = if error_msg.contains("Unable to spawn")
185 || error_msg.contains("No viable candidates")
186 {
187 let cmd = profile
189 .command
190 .as_deref()
191 .unwrap_or("the configured command");
192 (
193 format!("Profile '{}' Failed", profile.name),
194 format!(
195 "Command '{}' not found. Check that it's installed and in your PATH.",
196 cmd
197 ),
198 )
199 } else if error_msg.contains("No such file or directory") {
200 (
201 format!("Profile '{}' Failed", profile.name),
202 format!(
203 "Working directory not found: {}",
204 profile.working_directory.as_deref().unwrap_or("(unknown)")
205 ),
206 )
207 } else {
208 (
209 format!("Profile '{}' Failed", profile.name),
210 format!("Failed to start: {}", error_msg),
211 )
212 };
213 self.deliver_notification(&title, &message);
214 }
215 }
216 }
217 }
218
219 pub(crate) fn apply_profile_badge(&mut self, profile: &crate::profile::Profile) {
224 {
226 let mut vars = self.badge_state.variables_mut();
227 vars.profile_name = profile.name.clone();
228 }
229
230 self.badge_state.apply_profile_settings(profile);
232
233 if profile.badge_text.is_some() {
234 crate::debug_info!(
235 "PROFILE",
236 "Applied profile badge settings: format='{}', color={:?}, alpha={}",
237 profile.badge_text.as_deref().unwrap_or(""),
238 profile.badge_color,
239 profile.badge_color_alpha.unwrap_or(0.0)
240 );
241 }
242
243 self.badge_state.mark_dirty();
245 }
246
247 pub(crate) fn apply_profile_shader_settings(&mut self, profile: &crate::profile::Profile) {
249 let mut changed = false;
250 if let Some(shader) = &profile.shader {
251 self.config.rcu(|old| {
252 let mut new = (**old).clone();
253 new.shader.custom_shader = Some(shader.clone());
254 std::sync::Arc::new(new)
255 });
256 self.config.rcu(|old| {
257 let mut new = (**old).clone();
258 new.shader.custom_shader_enabled = true;
259 std::sync::Arc::new(new)
260 });
261 changed = true;
262 }
263 if let Some(brightness) = profile.shader_brightness {
264 self.config.rcu(|old| {
265 let mut new = (**old).clone();
266 new.shader.custom_shader_brightness = brightness.clamp(0.05, 1.0);
267 std::sync::Arc::new(new)
268 });
269 changed = true;
270 }
271 if let Some(text_opacity) = profile.shader_text_opacity {
272 self.config.rcu(|old| {
273 let mut new = (**old).clone();
274 new.shader.custom_shader_text_opacity = text_opacity.clamp(0.0, 1.0);
275 std::sync::Arc::new(new)
276 });
277 changed = true;
278 }
279 if let Some(animation_speed) = profile.shader_animation_speed {
280 self.config.rcu(|old| {
281 let mut new = (**old).clone();
282 new.shader.custom_shader_animation_speed = animation_speed.clamp(0.0, 5.0);
283 std::sync::Arc::new(new)
284 });
285 changed = true;
286 }
287 if let Some(texture_set) = &profile.shader_texture_set {
288 self.config.rcu(|old| {
289 let mut new = (**old).clone();
290 new.shader.custom_shader_channel0 = texture_set[0].clone();
291 std::sync::Arc::new(new)
292 });
293 self.config.rcu(|old| {
294 let mut new = (**old).clone();
295 new.shader.custom_shader_channel1 = texture_set[1].clone();
296 std::sync::Arc::new(new)
297 });
298 self.config.rcu(|old| {
299 let mut new = (**old).clone();
300 new.shader.custom_shader_channel2 = texture_set[2].clone();
301 std::sync::Arc::new(new)
302 });
303 self.config.rcu(|old| {
304 let mut new = (**old).clone();
305 new.shader.custom_shader_channel3 = texture_set[3].clone();
306 std::sync::Arc::new(new)
307 });
308 changed = true;
309 }
310
311 if changed {
312 self.refresh_background_shader_renderer();
313 crate::debug_info!(
314 "PROFILE",
315 "Applied shader overrides for profile '{}'",
316 profile.name
317 );
318 }
319 }
320
321 pub fn toggle_profile_drawer(&mut self) {
323 self.overlay_ui.profile_drawer_ui.toggle();
324 self.focus_state.needs_redraw = true;
325 self.request_redraw();
326 }
327
328 pub fn save_profiles(&self) {
330 if let Err(e) = profile_storage::save_profiles(&self.overlay_ui.profile_manager) {
331 log::error!("Failed to save profiles: {}", e);
332 }
333 }
334
335 pub fn apply_profile_changes(&mut self, profiles: Vec<crate::profile::Profile>) {
337 self.overlay_ui.profile_manager = ProfileManager::from_profiles(profiles);
338 self.save_profiles();
339 self.overlay_state.profiles_menu_needs_update = true;
341 }
342}