Skip to content

Commit 0fd7798

Browse files
feat: add option to preserve logs in the output window (#20)
Closes #18
1 parent 2398634 commit 0fd7798

10 files changed

Lines changed: 135 additions & 96 deletions

File tree

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
[package]
22
name = "scriptboard"
3-
version = "1.1.1"
3+
version = "1.2.0"
44
edition = "2024"
55
license = "GPL-3.0-only"
66

77
[package.metadata.winresource]
88
FileDescription = "Scriptboard"
9-
FileVersion = "1.1.1"
9+
FileVersion = "1.2.0"
1010
ProductName = "Scriptboard"
1111
Comments = "A simple desktop software to execute saved script files."
1212
OriginalFilename = "scriptboard.exe"
@@ -46,7 +46,7 @@ panic = "abort"
4646
strip = true
4747

4848
[package.metadata.packager]
49-
version = "1.1.1"
49+
version = "1.2.0"
5050
authors = ["uAtomicBoolean"]
5151
copyright = "Copyright (C) 2025 uAtomicBoolean"
5252
category = "DeveloperTool"

changelogs/1.2.0-changelog.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
**Features:**
2+
- It is now possible to preserve a script's output after restarting it (#18).

src/app/scripts/commands.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use crate::app::store::StoredScript;
2+
use std::process::{Command, Stdio};
3+
4+
#[cfg(target_os = "windows")]
5+
use std::os::windows::process::CommandExt;
6+
7+
#[cfg(target_os = "linux")]
8+
pub fn get_linux_command(script: &StoredScript, parsed_args: Vec<String>) -> Command {
9+
if !script.need_admin {
10+
let mut cmd = Command::new(&script.interpreter);
11+
cmd.arg(&script.path)
12+
.args(parsed_args)
13+
.stdout(Stdio::piped());
14+
return cmd;
15+
}
16+
17+
let mut cmd = Command::new("pkexec");
18+
cmd.args([&script.interpreter, &script.path])
19+
.args(parsed_args)
20+
.stdout(Stdio::piped());
21+
22+
cmd
23+
}
24+
25+
#[cfg(target_os = "macos")]
26+
pub fn get_macos_command(script: &StoredScript, parsed_args: Vec<String>) -> Command {
27+
if !script.need_admin {
28+
let mut cmd = Command::new(&script.interpreter);
29+
cmd.arg(&script.path)
30+
.args(parsed_args)
31+
.stdout(Stdio::piped());
32+
return cmd;
33+
}
34+
35+
let osascript_arg = format!(
36+
"do shell script \"{} {} {}\" with prompt \"{}\" with administrator privileges",
37+
&script.interpreter,
38+
&script.path,
39+
parsed_args.join(" "),
40+
&script.name,
41+
);
42+
43+
let mut cmd = Command::new("osascript");
44+
cmd.arg("-e").arg(osascript_arg).stdout(Stdio::piped());
45+
46+
cmd
47+
}
48+
49+
#[cfg(target_os = "windows")]
50+
pub fn get_windows_command(script: &StoredScript, parsed_args: Vec<String>) -> Command {
51+
// Ask for admin password
52+
// Start-Process cmd -ArgumentList "/C your_command_here" -Verb RunAs
53+
// Example with custom interpreter :
54+
// Start-Process cmd -ArgumentList "/C python -c \"print('Hello, World!')\"" -Verb RunAs
55+
56+
if !script.need_admin {
57+
let mut cmd = Command::new(&script.interpreter);
58+
cmd.arg("-File")
59+
.arg(&script.path)
60+
.args(parsed_args)
61+
.creation_flags(0x08000000)
62+
.stdout(Stdio::piped());
63+
return cmd;
64+
}
65+
66+
let script_args = format!(
67+
"\"/C {} {} {}\"",
68+
&script.interpreter,
69+
&script.path,
70+
parsed_args.join(" ")
71+
);
72+
73+
let mut cmd: Command = Command::new("Start-Process");
74+
cmd.arg("cmd")
75+
.arg("-ArgumentList")
76+
.arg(script_args)
77+
.creation_flags(0x08000000)
78+
.stdout(Stdio::piped());
79+
80+
cmd
81+
}

src/app/scripts/execute.rs

Lines changed: 19 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
1+
use super::commands::*;
12
use crate::app::notifications::{send_notification, send_notification_with_custom_title};
23
use crate::app::store::structs::StoredScript;
34
use crate::{MainPageLogic, NotifStringEnum, NotifTypeEnum, Script, Scriptboard};
45
use slint::{ComponentHandle, Model, VecModel, Weak};
56
use std::io::{BufReader, Read};
6-
use std::process::{Command, Output, Stdio};
7+
use std::process::Output;
78
use std::rc::Rc;
89

9-
#[cfg(target_os = "windows")]
10-
use std::os::windows::process::CommandExt;
10+
/// Prepare the scripts before its execution then start the execution.
11+
pub fn execute_script(
12+
ui_weak: Weak<Scriptboard>,
13+
scripts: Rc<VecModel<Script>>,
14+
script_index: usize,
15+
) {
16+
let mut script = scripts.row_data(script_index as usize).unwrap();
17+
script.running = true;
18+
if !script.preserve_output {
19+
script.output = "".into();
20+
} else {
21+
script.output.push_str("\n");
22+
}
23+
scripts.set_row_data(script_index as usize, script.clone());
24+
run_execution_script(ui_weak.clone(), script, script_index as usize);
25+
}
1126

1227
/// Execute the script and store its output.
1328
/// This function updates the scripts list to update the UI.
14-
pub fn execute_script(ui_weak: Weak<Scriptboard>, script: Script, script_index: usize) {
29+
fn run_execution_script(ui_weak: Weak<Scriptboard>, script: Script, script_index: usize) {
1530
std::thread::spawn({
1631
let light_script: StoredScript = script.into();
1732
move || {
@@ -152,81 +167,3 @@ fn finish_script_execution(ui: Scriptboard, output: Option<Output>, script_index
152167
send_notification_with_custom_title(ui.as_weak(), theme, script.name.into(), message);
153168
}
154169
}
155-
156-
#[cfg(target_os = "linux")]
157-
fn get_linux_command(script: &StoredScript, parsed_args: Vec<String>) -> Command {
158-
if !script.need_admin {
159-
use std::process::Stdio;
160-
161-
let mut cmd = Command::new(&script.interpreter);
162-
cmd.arg(&script.path)
163-
.args(parsed_args)
164-
.stdout(Stdio::piped());
165-
return cmd;
166-
}
167-
168-
let mut cmd = Command::new("pkexec");
169-
cmd.args([&script.interpreter, &script.path])
170-
.args(parsed_args)
171-
.stdout(Stdio::piped());
172-
173-
cmd
174-
}
175-
176-
#[cfg(target_os = "macos")]
177-
fn get_macos_command(script: &StoredScript, parsed_args: Vec<String>) -> Command {
178-
if !script.need_admin {
179-
let mut cmd = Command::new(&script.interpreter);
180-
cmd.arg(&script.path)
181-
.args(parsed_args)
182-
.stdout(Stdio::piped());
183-
return cmd;
184-
}
185-
186-
let osascript_arg = format!(
187-
"do shell script \"{} {} {}\" with prompt \"{}\" with administrator privileges",
188-
&script.interpreter,
189-
&script.path,
190-
parsed_args.join(" "),
191-
&script.name,
192-
);
193-
194-
let mut cmd = Command::new("osascript");
195-
cmd.arg("-e").arg(osascript_arg).stdout(Stdio::piped());
196-
197-
cmd
198-
}
199-
200-
#[cfg(target_os = "windows")]
201-
fn get_windows_command(script: &StoredScript, parsed_args: Vec<String>) -> Command {
202-
// Ask for admin password
203-
// Start-Process cmd -ArgumentList "/C your_command_here" -Verb RunAs
204-
// Example with custom interpreter :
205-
// Start-Process cmd -ArgumentList "/C python -c \"print('Hello, World!')\"" -Verb RunAs
206-
207-
if !script.need_admin {
208-
let mut cmd = Command::new(&script.interpreter);
209-
cmd.arg("-File")
210-
.arg(&script.path)
211-
.args(parsed_args)
212-
.creation_flags(0x08000000)
213-
.stdout(Stdio::piped());
214-
return cmd;
215-
}
216-
217-
let script_args = format!(
218-
"\"/C {} {} {}\"",
219-
&script.interpreter,
220-
&script.path,
221-
parsed_args.join(" ")
222-
);
223-
224-
let mut cmd: Command = Command::new("Start-Process");
225-
cmd.arg("cmd")
226-
.arg("-ArgumentList")
227-
.arg(script_args)
228-
.creation_flags(0x08000000)
229-
.stdout(Stdio::piped());
230-
231-
cmd
232-
}

src/app/scripts/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod commands;
12
pub mod execute;
23

34
use rfd::FileDialog;

src/app/store/structs.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ impl Into<Script> for StoredScript {
5252
args: self.args.into(),
5353
running: false,
5454
output: SharedString::new(),
55+
preserve_output: false,
5556
status_code: 0,
5657
}
5758
}
@@ -70,6 +71,7 @@ impl Into<Script> for &StoredScript {
7071
args: self.args.clone().into(),
7172
running: false,
7273
output: SharedString::new(),
74+
preserve_output: false,
7375
status_code: 0,
7476
}
7577
}

src/ui/pages/main_page.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@ pub fn init_ui(ui: Weak<Scriptboard>, scripts: Rc<VecModel<Script>>) {
1919
let ui_weak = ui.as_weak();
2020
let scripts_clone = scripts.clone();
2121
move |script_index| {
22-
let script = scripts_clone.row_data(script_index as usize).unwrap();
2322
crate::app::scripts::execute::execute_script(
2423
ui_weak.clone(),
25-
script,
24+
scripts_clone.clone(),
2625
script_index as usize,
2726
);
2827
}
@@ -65,18 +64,23 @@ pub fn init_ui(ui: Weak<Scriptboard>, scripts: Rc<VecModel<Script>>) {
6564
let ui_weak = ui_weak.clone();
6665
let scripts_clone = scripts.clone();
6766
move || {
68-
let mut script = scripts_clone.row_data(index as usize).unwrap();
69-
script.running = true;
70-
script.output = "".into();
71-
scripts_clone.set_row_data(index as usize, script.clone());
7267
crate::app::scripts::execute::execute_script(
7368
ui_weak.clone(),
74-
script,
69+
scripts_clone.clone(),
7570
index as usize,
7671
);
7772
}
7873
});
7974

75+
output_window.on_update_preserve_output({
76+
let scripts_clone = scripts.clone();
77+
move |preserve_output| {
78+
let mut script = scripts_clone.row_data(index as usize).unwrap();
79+
script.preserve_output = preserve_output;
80+
scripts_clone.set_row_data(index as usize, script);
81+
}
82+
});
83+
8084
if let Err(err) = slint::select_bundled_translation(&store::get_language()) {
8185
error!("Couldn't select the bundled translation.");
8286
error!("{}", err.to_string());

ui/pages/main.slint

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ export component MainPage {
3131
x: (mod(idx, columns) * card-width);
3232
y: (floor(idx / columns) * card-height);
3333
clicked => {
34-
script.running = true;
35-
script.output = "";
3634
MainPageLogic.execute-script(idx);
3735
}
3836
open-output => {

ui/structs.slint

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ export struct Script {
99
args: string,
1010
running: bool,
1111
output: string,
12+
preserve-output: bool,
1213
status-code: int,
1314
}

ui/windows/script_output_window.slint

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import "../assets/fonts/Inter-Italic-VariableFont_opsz,wght.ttf";
22
import "../assets/fonts/Inter-VariableFont_opsz,wght.ttf";
33

44
import { UAppTheme } from "@sleek-ui/app-theme.slint";
5-
import { UButton, UDivider, UText, UCenter } from "@sleek-ui/widgets.slint";
5+
import { UButton, UCheckbox, UDivider, UText, UCenter } from "@sleek-ui/widgets.slint";
66

77
import { ScrollView } from "std-widgets.slint";
88

@@ -14,6 +14,7 @@ export component ScriptOutputWindow inherits Window {
1414
in-out property <int> script_index;
1515
property <Script> script: scripts[script_index];
1616
callback execute-script();
17+
callback update-preserve-output(pre-out: bool);
1718
title: @tr("Output - ") + script.name;
1819
icon: @image-url("../../res/linux/32x32.png");
1920
default-font-family: "Inter";
@@ -25,8 +26,20 @@ export component ScriptOutputWindow inherits Window {
2526
HorizontalLayout {
2627
vertical-stretch: 0;
2728
padding: UAppTheme.padding-base;
28-
alignment: center;
29+
alignment: space-between;
2930
height: 40px * UAppTheme.scale-factor;
31+
VerticalLayout {
32+
alignment: center;
33+
UCheckbox {
34+
checked: script.preserve-output;
35+
text: @tr("Preserve output");
36+
text-placement: end;
37+
changed checked => {
38+
root.update-preserve-output(self.checked);
39+
}
40+
}
41+
}
42+
3043
VerticalLayout {
3144
alignment: center;
3245
UButton {

0 commit comments

Comments
 (0)