···11----
22-title: Advent Adventures - Prologue
33-date: 2023-12-19
44-summary: A preparation post for when I begin going through Advent of Code year-by-year
55-cowsay: Happy Holidays!
66----
77-88-Over the past few years I've done [Advent of Code](https://adventofcode.com) on and off. At the time of writing, I'm on [day 19 of this year's challenge](https://adventofcode.com/2023/day/19) and will (hopefully) complete this year. After that, I want to go back and do all the previous years. I'll be writing a post for each year that I do, with a few highlights for the days I enjoyed and/or struggled with. I'll be using [Rust](https://www.rust-lang.org/) for all of my solutions, as it's a language I really want to learn in-depth.
99-1010-Of course, I can't call myself a programmer if I don't needlessly over-complicate things. In addition to solving each day I want to keep everything organized, I want to make a repo for all of my Advent solutions. In addition to each year, I want to make a central utils crate that I can use across all years, I want this to be a sort of "swiss-army-knife" of competitive programming tactics/algorithms-- something I can look back on later for reference and also use next year.
1111-1212-I also want this repo to be easy to use. That is, I want it to be able to generate most of the boilerplate for me through macros. I want to be able to generate a year with a single command and have all the days of that year ready to be implemented.
1313-1414-This repo should also be able to make a single binary I can use to run any of my solutions across all of the years I've done.
1515-1616-Lets get started!
1717-1818-## The Project Structure
1919-2020-I'm going to be using [cargo workspaces](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html) to organize everything. The structure will look something like this:
2121-2222-```dir
2323-advent/
2424-├─ src/
2525-├─ core/
2626-│ ├─ Cargo.toml
2727-│ ├─ src/
2828-├─ macros/
2929-│ ├─ Cargo.toml
3030-│ ├─ src/
3131-├─ util/
3232-│ ├─ Cargo.toml
3333-│ ├─ src/
3434-├─ years/
3535-│ ├─ 20XX/
3636-│ │ ├─ Cargo.toml
3737-│ │ ├─ src/
3838-│ │ │ ├─ day_X.rs
3939-├─ Cargo.toml
4040-```
4141-4242-My top-level package is `advent`, this will be the main workspace. The `util` package will be a library that I can use across all years. Each year will be its own package, with its own `Cargo.toml` file. This will allow me to have a separate `main.rs` for each year, and also allow me to use the `util` package as a dependency.
4343-4444-### Core Package
4545-4646-This package will contain everything needed to work with days and parts. This should export a `Year` and `Day` trait that all years and days will implement. It will handle parsing arguments, getting the input from stdin, and timing the solutions. This package will be used by the year packages.
4747-4848-### Macros Package
4949-5050-This package will contain any macros I need. It's a proc macro crate.
5151-5252-### Year Packages
5353-5454-Each year will be a binary that I can run with `cargo run -p y_[YEAR] [DAY]:[PART]`. For example, to run the solution for day 1 part 2 of 2020 I would run `cargo run -p y_2022 1:2`. In addition, the binary can take a wild card to mean "run all days". For example, `cargo run -p y_2022 *` would run all days of 2020.
5555-5656-The binary will take input through stdin.
5757-5858-### Main Package
5959-6060-The main package will contain a binary that acts as a runner for all days. It will take a year, day, and part as arguments and run the corresponding binary. This package includes all the day packages as dependencies and simply acts as a runner for them.
6161-6262-### Utils Package
6363-6464-The utils package is pretty self-explanatory. It will contain a library that I can use over the years. This will only include things used in _solving the problems_ not running the days. Ideally, this will require no dependencies and would be easily transferred to other projects.
6565-6666-## The `Day` trait
6767-6868-The `Day` trait will be the main trait that all days will implement. It will have a few methods that will be used by the `Year` trait to run the days. Here's what it looks like:
6969-7070-```rs
7171-pub trait Day {
7272-7373- type Input;
7474-7575- const EXAMPLE_INPUT_1: &'static str = "";
7676- const EXAMPLE_INPUT_2: &'static str = "";
7777-7878- const EXPECTED_1: &'static str = "";
7979- const EXPECTED_2: &'static str = "";
8080-8181- fn get_example_input(part: usize) -> &'static str {
8282- match part {
8383- 1 => Self::EXAMPLE_INPUT_1,
8484- 2 => Self::EXAMPLE_INPUT_2,
8585- _ => panic!("Invalid part number"),
8686- }
8787- }
8888-8989- fn run_part(part: usize, input: Option<&str>) -> Option<String> {
9090- let input = input.unwrap_or_else(|| Self::get_example_input(part));
9191- let input = Self::parse_input(input);
9292- match part {
9393- 1 => Self::part_1(input),
9494- 2 => Self::part_2(input),
9595- _ => panic!("Invalid part number"),
9696- }
9797- }
9898-9999- // ...
100100-101101- fn parse_input(input: &str) -> Self::Input;
102102-103103- fn part_1(_input: Self::Input) -> Option<String> { None }
104104- fn part_2(_input: Self::Input) -> Option<String> { None }
105105-106106-}
107107-```
108108-109109-The general idea here is we have a few constants that contain the example input for each part. Then we have a few methods that will be used to run the days. The `get_example_input` method will be used to get the example input for a given part. The `run_part` method will be used to run a given part. The `parse_input` method will be used to parse the input into the type used by the day. Finally, the `part_1` and `part_2` methods will be used to run the parts.
110110-111111-Here we expect `part_1` and `part_2` to be implemented. However, they return `None` by default. This is so we can generate all 25 days of a year without having to implement all of them. If a day is not implemented, the runner will simply print a message saying that the day is not implemented.
112112-113113-Sadly `parse_input` will always have to be implemented, as there's no way to implement it by default. `I` here is a `String` by default, and since `parse_input` returns `I` one might think that for a default implementation, we could just return the input as-is. However, rust won't allow this as in the event `I` is changed to another type, the default implementation would no longer work. So we have to implement `parse_input` for each day. Implementing it isn't too bad, and could be part of a derive macro later on.
114114-115115-## The `Year` trait
116116-117117-The `Year` trait will be the main trait that all years will implement. It will have a few methods that will be used by the `core` package to run the days. Here's what it looks like:
118118-119119-```rs
120120-pub trait Year {
121121- const YEAR: usize;
122122-123123- fn solve_day(day: usize, part: usize, input: Option<&str>) -> Option<String>;
124124-125125- fn solve_day_both_parts(day: usize, extra_indent: &str);
126126-127127- fn solve_all_days() {
128128- println!("Year {}:", Self::YEAR);
129129- for day in 1..=MAX_DAY {
130130- Self::solve_day_both_parts(day, " ");
131131- }
132132- }
133133-134134- fn run_dp(input: Option<&str>, dp: DP) {
135135- match dp.day {
136136- Selection::All => {
137137- Self::solve_all_days();
138138- },
139139- Selection::Single(day) => {
140140- match dp.part {
141141- Selection::All => {
142142- Self::solve_day_both_parts(day, "");
143143- },
144144- Selection::Single(part) => {
145145- Self::solve_day(day, part, input);
146146- },
147147- }
148148- },
149149- }
150150- }
151151-}
152152-```
153153-154154-The general idea here is we have a few methods that will be used to run the days. The `solve_day` method will be used to run a given day. The `solve_day_both_parts` method will be used to run both parts of a given day. Finally, the `solve_all_days` method will be used to run all days. The extra indent is used to indent the output of the day so that it lines up with the year.
155155-156156-## Trying to implement a year
157157-158158-Now that we have a basic skeleton of what a year should look like, let's try to implement it! I'm going to be using the most recent year, 2023, as my testing grounds.
159159-160160-The layout of each year's package will look like this:
161161-162162-```dir
163163-20XX/
164164-├─ src/
165165-│ ├─ main.rs
166166-│ ├─ lib.rs
167167-│ ├─ day_X.rs
168168-├─ Cargo.toml
169169-```
170170-171171-The `main.rs` file will be used to run the days. The `lib.rs` file will be used to implement the `Year` trait. The `day_X.rs` files will be used to implement the `Day` trait for each day.
172172-173173-Starting out I'm going to manually implement one day of 2023 and get my traits implemented. Then, I'm going to see where I can use macros, derive macros, and other things to make the process easier for subsequent days/years.
174174-175175-### Day 1
176176-177177-```rs
178178-use core::Day;
179179-180180-pub struct Day1;
181181-182182-// Ideally most of this could be handled by a proc macro of some kind
183183-impl Day for Day1 {
184184-185185- type Input = String;
186186-187187- const EXAMPLE_INPUT_1: &'static str = "...";
188188- // Defining examples and such...
189189-190190- fn parse_input(input: &str) -> Self::Input {
191191- input.to_string()
192192- }
193193-194194- fn part_1(input: Self::Input) -> Option<String> {
195195- //...
196196- Some(answer.to_string())
197197- }
198198-199199- fn part_2(input: Self::Input) -> Option<String> {
200200- //...
201201- Some(answer.to_string())
202202- }
203203-}
204204-```
205205-206206-I still don't know how I entirely feel about making my Days return `String`s. It's fine for now and will come in handy if for some reason a problem needs a string as an answer. However, I think I might change it to return an `i64` instead. I'll have to see how it goes.
207207-208208-Now that I have a day implemented, I want to try and simplify defining it via a macro. I'm going to try and make a macro that will generate the `Day` trait implementation (or most of it) for me.
209209-210210-```rs
211211-#[macro_export]
212212-macro_rules! ex_for_day {
213213- ($day:literal, $part:literal) => {
214214- include_str!(concat!("examples/day_", stringify!($day), "/", stringify!($part), ".txt"))
215215- };
216216-}
217217-218218-#[macro_export]
219219-macro_rules! day_stuff {
220220- ($day:literal, $e_1:literal, $e_2:literal) => {
221221- day_stuff!($day, $e_1, $e_2, String);
222222-223223- fn parse_input(input: &str) -> Self::Input {
224224- input.to_string()
225225- }
226226- };
227227-228228- ($day:literal, $e_1:literal, $e_2:literal, $i: ty) => {
229229- type Input = $i;
230230-231231- const DAY: usize = $day;
232232- const EXAMPLE_INPUT_1: &'static str = ex_for_day!($day, 1);
233233- const EXAMPLE_INPUT_2: &'static str = ex_for_day!($day, 2);
234234- const EXPECTED_1: &'static str = $e_1;
235235- const EXPECTED_2: &'static str = $e_2;
236236- }
237237-}
238238-```
239239-240240-Here we can see I have two macros. The first one, `ex_for_day`, is used to get the example input for a given day and part. The second one, `day_stuff`, is used to generate part of the `Day` trait implementation. It takes the day number, the expected answers, and the type of the input. It then generates the `Input` type, the example inputs, and the expected answers. Finally, it generates the `parse_input` method.
241241-242242-If no input type is given, it defaults to `String`. This is because I want to be able to use this macro for all days, and I don't want to have to specify the input type for each day.
243243-244244-In theory, I could use proc-macros here to generate more of the file. However, proc macros tend to mess with debugging output / hide what scopes things are defined in. I want to be able to debug my code, so I'm going to stick with these macros for now.
245245-246246-Now, I need to be able to generate a `Year` trait implementation. This will be a bit more complicated than the `Day` trait implementation, as I need to generate a `match` statement for each day. I'm going to try and make a macro that will generate the `Year` trait implementation for me.
247247-248248-### Year 2023
249249-250250-After getting the `Year` trait implemented for 2023, here's what it looks like:
251251-252252-```rs
253253-mod day_1;
254254-255255-use core::{Day, Year};
256256-257257-use day_1::Day1;
258258-259259-pub struct Year2023;
260260-261261-impl Year for Year2023 {
262262-263263- const YEAR: usize = 2023;
264264-265265- fn solve_day(day: usize, part: usize, input: Option<&str>) -> Option<String> {
266266- match day {
267267- 1 => Day1::run_part(part, input),
268268- _ => None,
269269- }
270270- }
271271-272272- fn solve_day_both_parts(day: usize, extra_indent: &str) {
273273- match day {
274274- 1 => Day1::run_all_parts(extra_indent),
275275- _ => (),
276276- }
277277- }
278278-279279-}
280280-```
281281-282282-This looks like it can be placed in a proc macro as all we're _really changing_ here is the year number, the rest should be carbon copy for all years. So I created a simple proc macro that basically has the `pub struct` and `impl` blocks as a template, and will replace the year number with the proper year:
283283-284284-```rs
285285-extern crate proc_macro;
286286-287287-use proc_macro::TokenStream;
288288-289289-const YEAR_TEMPLATE: &str = include_str!("template_year.rs");
290290-291291-#[proc_macro]
292292-pub fn year(item: TokenStream) -> TokenStream {
293293- let year = item.to_string();
294294-295295- YEAR_TEMPLATE.replace("__YEAR__", &year).parse().unwrap()
296296-}
297297-```
298298-299299-After expanding this to include mod statements, use statements, and tests, I get the ability to simply do:
300300-301301-```rs
302302-use macros::year;
303303-304304-year!(2023);
305305-```
306306-307307-to automatically make a runner and tester for all the days in 2023.
308308-309309-#### Adding the Year Runner
310310-311311-Up until now, I haven't shown _how_ running problems is going to work. The year binaries will parse the arguments into a struct called `DP` (day, part). This struct will then be used to find the correct day and part to run. Here's what it looks like:
312312-313313-```rs
314314-#[derive(Clone, Debug)]
315315-pub enum Selection {
316316- All,
317317- Single(usize), // TODO: Add range maybe?
318318-}
319319-320320-pub struct DP {
321321- pub day: Selection,
322322- pub part: Selection,
323323-}
324324-```
325325-326326-The parsing for this is pretty simple, split by `:`, then parse each part. If the part is `*` then it's `Selection::All`, otherwise it's `Selection::Single`.
327327-328328-Now I can simply pass this to the `Year` trait's `run_dp` method and it will run the correct day and part.
329329-330330-For the final bit, we need a way to get the `input` we want to run. To do this, I accept the input as a second argument to the binary. If no input is given, we'll use the example input. If the user passes `-`, we will read from stdin. Otherwise, we will simply use the input given.
331331-332332-Combining parsing the DP and the input we get this handy utility method:
333333-334334-```rs
335335-pub fn get_dp_and_input() -> (DP, Option<String>) {
336336- let mut args = args().skip(1);
337337-338338- let dp = args.next().map(|s| DP::parse(&s.trim())).unwrap_or(DP_ALL);
339339-340340- let input = args.next().map(|s| s.trim().to_string()).map(|i| {
341341- if i == "-" {
342342- let mut input = String::new();
343343- stdin().read_to_string(&mut input).expect("Failed to read input");
344344- input.trim().to_string()
345345- } else {
346346- i
347347- }
348348- });
349349-350350- (dp, input)
351351-}
352352-```
353353-354354-All we need in `main.rs` for our years now is some glue code:
355355-356356-```rs
357357-use core::{Year, get_dp_and_input};
358358-359359-use y_2023::Year2023;
360360-361361-fn main() {
362362- let (dp, input) = get_dp_and_input();
363363- Year2023::run_dp(input.as_deref(), dp);
364364-}
365365-```
366366-367367-We can run our year with `cargo run -p y_2023 1:1` and it will run day 1 part 1 of 2023. We can also run `cargo run -p y_2023 1:*` to run both parts of day 1, or `cargo run -p y_2023 *` to run all days of 2023.
368368-369369-It's a bit overkill, but I made a proc macro to generate this glue code for me:
370370-371371-```rs
372372-#[proc_macro]
373373-pub fn year_runner(item: TokenStream) -> TokenStream {
374374- let year = item.to_string();
375375-376376- format!("
377377- use core::{{Year, get_dp_and_input}};
378378-379379- use y_{year}::Year{year};
380380-381381- fn main() {{
382382- let (dp, input) = get_dp_and_input();
383383- Year{year}::run_dp(input.as_deref(), dp);
384384- }}").parse::<TokenStream>().unwrap()
385385-}
386386-```
387387-388388-### Generating Days / Years
389389-390390-Now that I've got a basic skeleton for a day and year, I want to be able to generate them. I'm going to make a function in my `core` package that will let me generate a day to a given file given the day's number. Then I'm gonna write a function that can generate an entire year (and its rust project) given the year's number.
391391-392392-So to generate an entire year crate I need to:
393393-394394-1. Create a directory for the year in `years/`
395395-2. Initialize a `Cargo.toml` file
396396- 1. Name it `y_[YEAR]`
397397- 2. Add the `core` package as a dependency
398398- 3. Add the `util` package as a dependency
399399- 4. Add the `macros` package as a dependency
400400-3. Make a `src/` folder
401401- 1. Make example files for every day in the year in `examples/day_[DAY]/[PART].txt`
402402- 2. Make a `day_[DAY].rs` file for each day, with a definition for the Day and a call to `day_stuff!`
403403- 3. Make a `lib.rs` file that has a single macro call to `year!([YEAR]);`
404404- 4. Make a `main.rs` file that has a single macro call to `year_runner!([YEAR]);`
405405-4. Add the year as a dependency to the `Cargo.toml` file in the root of the project
406406-407407-The logic for this is pretty rudimentary so I'm going to not include it here. Basically, the main binary will take a year number and generate the year crate for that year. It will also add the year crate as a dependency to the root `Cargo.toml` file.
408408-409409-### The Main Binary
410410-411411-The main binary is used for two things:
412412-413413-1. It's the thing that you run to actually generate years
414414-2. It can run any year, day, and part. It does this by compiling all years into itself
415415-416416-The first step is making a simple CLI parser. I want it to be able to take 2 commands `new` and `solve`. `new` is used to generate a new year, and `solve` is used to run a year, day, and part. Here's what the CLI parser looks like:
417417-418418-```rs
419419-let args = std::env::args().skip(1).collect::<Vec<_>>();
420420-421421- let command = args.get(0);
422422-423423- match command {
424424- Some(command) => {
425425- match command.as_str() {
426426- "new" => {
427427- let year = args.get(1).expect("No year provided");
428428- make_year(year);
429429- },
430430- "solve" | "run" => {
431431- let (ydp, input) = get_ydp_and_input(args[1..].to_vec());
432432- run_ydp(ydp, input);
433433- }
434434- _ => {
435435- println!("Unknown command: {}", command);
436436- println!("Available commands: new, solve");
437437- }
438438- }
439439- },
440440- None => {
441441- println!("No command provided");
442442- println!("Available commands: new, solve");
443443- }
444444- }
445445-```
446446-447447-After that, we need to connect `new` to `core::make_year` and then `solve` to a match statement that will run the correct year, day, and part.
448448-449449-To generate this match statement (and use statements) I once again turn to a proc macro. These macros are pretty much the same for days, they have a match that takes a year number and will run the corresponding year.
450450-451451-### Testing
452452-453453-Now that we have a project generation command we're done! This allows us to generate a new year crate, auto-updating the main binary to include it as a dependency. The crate will have all 25 days generated, with the `Day` trait implemented for each day. The `Year` trait will also be implemented for the year. Now we can run the year's binary through `cargo run -p y_[YEAR] [DAY]:[PART]` and it will run the correct day and part. Or we can run `cargo run solve [YEAR]:[DAY]:[PART]` to get the same result.
454454-455455-I'll start by generating all 25 days for 2023. Up until now, I've only been generating one day per year for simplicity.
456456-457457-So now my y_2023 crate has a `day_x.rs` file for every day until the 25th. I can now run `cargo run solve 2023:*` to run every single day of the year. Although they're not implemented yet, it will just print that the day is not implemented.
458458-459459-### Conclusion
460460-461461-So now I have an automated repo for solving Advent of Code. I can generate a new year, and it will generate all 25 days for that year. I then implement the days and run them through the main binary. I'm going to hold off on transferring my 2023 solutions to this repo until it's over. After which I want to take a look at all of the days in 2023 and see what I can extract into the `utils` crate and `macros` crate.
462462-463463-And with that, I'm done with this post! Stay tuned for a "Prologue part 2" / "2023" post where I go over some of the highlights of 2023 for me and describe what logic I'm extracting into the `utils` crate and `macros` crate.
+353
src/content/posts/wip_screen_captures.md
···11+---
22+title: Work In Progress Friday - Screen Captures
33+date: 2024-07-25
44+summary: An adventure in making scripts to capture screen shots and recordings
55+cowsay: A picture is worth a thousand words
66+---
77+88+I've recently been going down the path of madness known as customizing my desktop and
99+I figured I'd share some neat scripts and setups I've done along the way.
1010+1111+Today I'll go into some scripts I've been working on to capture screen shots and recordings.
1212+They allow selecting regions of the screen and specific windows, and I also made it so you
1313+can edit them afterwards.
1414+1515+## Background
1616+1717+My entire system and home folder is managed by [NixOS](https://nixos.org), so I have a
1818+[configuration repository](https://github.com/Bwc9876/nix-conf) where all my scripts and configs
1919+can be found, I'll reference them throughout this post and provide links to the current version of each so you
2020+can see if I've updated them since this post.
2121+2222+Currently I use [Hyprland](https://hyprland.org) as my window manager, and have been duct-taping components together to make my own little Desktop Environment around it.
2323+2424+I also like to use [NuShell](https://nushell.sh) as my shell, and these scripts
2525+will be written in it. If you haven't checked out NuShell yet, I highly recommend it!
2626+2727+## Screenshots
2828+2929+First is the script to take screenshots. This is a relatively simple script as it simply builds
3030+on top of [grimblast](https://github.com/hyprwm/contrib/tree/main/grimblast) with some nice QoL
3131+features.
3232+3333+To install grimblast, all I have to do is add it to my `environment.systemPackages`:
3434+3535+```nix
3636+environment.systemPackages = with pkgs; [
3737+ # ...
3838+ grimblast
3939+ libnotify # For notifications
4040+ xdg-utils # For opening files
4141+ # ...
4242+];
4343+```
4444+4545+Grimblast will automatically save screenshots to `XDG_SCREENSHOTS_DIR`, I
4646+manually set this in my _home manager_ config with:
4747+4848+```nix
4949+xdg.userDirs.extraConfig.XDG_SCREENSHOTS_DIR = "${config.home.homeDirectory}/Pictures/Screenshots";
5050+```
5151+5252+Grimblast will name the screenshots with the current date and time, which works for me.
5353+5454+Now along to actually using grimblast, I'll create a new script and put in in my config somewhere, we'll
5555+call it `screenshot.nu`. I usually like to place any non-nix files in a folder called `res` at the root
5656+of my config, we'll get to actually calling this script once we're done writing it.
5757+5858+To start out we need to call grimblast, I like to use `copysave` as the action as I like having it
5959+immediately in my clipboard, and having it saved for later. I'll also add `--freeze` which simply
6060+freezes the screen while I select the region to capture.
6161+6262+```nushell
6363+let file_path = grimblast --freeze copysave
6464+```
6565+6666+grimblast will then return the path to the saved screenshot, which we save in `file_path`.
6767+If the user were to cancel the selection (press escape), `file_path` would be empty, so we want to make sure
6868+to check for that so we're not trying to open a non-existent file.
6969+7070+```nushell
7171+if $file_path == "" {
7272+ exit 1
7373+}
7474+```
7575+7676+Now the main part, we'll send a notification that the screenshot was saved, and have options for it.
7777+7878+I want four actions for the screenshot:
7979+8080+- Open
8181+- Open Folder
8282+- Edit
8383+- Delete
8484+8585+Also since grimblast saves the screenshot as a png, I can pass it as the icon of the notification.
8686+8787+```nushell
8888+let choice = notify-send --app-name=screengrab -i $file_path -t 7500 --action=open=Open --action=folder="Show In Folder" --action=edit=Edit --action=delete=Delete "Screenshot taken" $"Screenshot saved to ($file_path) and copied to clipboard"
8989+```
9090+9191+A long command here, `notify-send` allows us to send a notification to the currently running notification daemon.
9292+In my case I'm using [swaync](https://github.com/ErikReider/SwayNotificationCenter).
9393+9494+- `--app-name` is the name of the application that sent the notification, I say screengrab here so swaync will show an icon in addition to the image, also so I can play a camera shutter sound when the notification is sent.
9595+- `-i` is the icon to display in the notification, in this case the screenshot we just took.
9696+- `-t` is the time in milliseconds to show the notification
9797+- `--action` is actions to display in the notification, `name=Text`
9898+- First position argument is the notification title, and second is the body.
9999+100100+With that we get a neat notification when we screenshot.
101101+102102+
103103+104104+Now we need to handle each action, the chosen action is returned by notify-send, so we can match on that.
105105+106106+- "Open" and "Open Folder" are pretty simple, just pass `$file_path` and `$file_path | path dirname` to `xdg-open`
107107+- "Edit" I'll simply pass the file path to my editor, I chose [swappy](https://github.com/jtheoof/swappy) because of it's simplicity and ease of use.
108108+- "Delete" I'll just remove the file.
109109+110110+```nushell
111111+match $choice {
112112+ "open" => {
113113+ xdg-open $file_path
114114+ }
115115+ "folder" => {
116116+ xdg-open ($file_path | path dirname)
117117+ }
118118+ "edit" => {
119119+ swappy -f $file_path
120120+ }
121121+ "delete" => {
122122+ rm $file_path
123123+ }
124124+}
125125+```
126126+127127+And that's it! I now have a fairly robust screenshot script.
128128+129129+### Calling the Screenshot script
130130+131131+Now in terms of actually calling it I'll be binding it to `Win` + `Shift` + `S` in Hyprland, as
132132+well as `PrintScreen`.
133133+134134+In home manager i simply have to add these strings to my `wayland.windowManager.hyprland.settings.bind`
135135+array:
136136+137137+```nix
138138+wayland.windowManager.hyprland.settings.bind = [
139139+ # ...
140140+ ",Print,exec,nu ${../res/screenshot.nu}"
141141+ "SUPER SHIFT,S,exec,nu ${../res/screenshot.nu}"
142142+ # ...
143143+];
144144+```
145145+146146+Now by switching to my new config (and making sure to stage `screenshot.nu` of course),
147147+I can take screenshots with a keybind!
148148+149149+## Screen Recordings
150150+151151+This will be a bit more involved mainly because something like grimblast doesn't exist for screen recordings.
152152+Looking at existing solutions I couldn't find any that I really liked, mostly because they involved
153153+some additional UI. To be clear this script will be for _simple_, _short_ recordings, long-term stuff
154154+I'll still prefer to use something like OBS.
155155+156156+For the actual screen recording I'll be using [wf-recorder](https://github.com/ammen99/wf-recorder).
157157+158158+```nix
159159+environment.systemPackages = with pkgs; [
160160+ # ...
161161+ wf-recorder
162162+ libnotify # For notifications
163163+ xdg-utils # For opening files
164164+ slurp # Will explain this later
165165+ # ...
166166+];
167167+```
168168+169169+First and foremost location, I chose to use `~/Videos/Captures` for my recordings. I didn't
170170+set an environment variable for this, it'll be hardcoded in the script.
171171+172172+```nushell
173173+let date_format = "%Y-%m-%d_%H-%M-%S"
174174+175175+let captures_folder = $"($env.HOME)/Videos/Captures"
176176+177177+if not ($captures_folder | path exists) {
178178+ mkdir $captures_folder
179179+}
180180+181181+let out_name = date now | format date $"($captures_folder)/($date_format).mp4"
182182+```
183183+184184+This will handle determining the folder and name for the recordings,
185185+and creating the folder if it doesn't exist.
186186+187187+Next up I want to have a similar selection process to the screenshot script, to do this I'll use
188188+[slurp](https://github.com/emersion/slurp) to select areas of the screen,
189189+which is what grimblast uses under the hood.
190190+191191+In addition, grimblast does some communication with Hyprland to get window information such as
192192+position and size, this lets you select a window to take a screenshot of. I'll be getting that info manually from
193193+Hyprland using NuShell instead:
194194+195195+```nushell
196196+let workspaces = hyprctl monitors -j | from json | get activeWorkspace.id
197197+let windows = hyprctl clients -j | from json | where workspace.id in $workspaces
198198+let geom = $windows | each { |w| $"($w.at.0),($w.at.1) ($w.size.0)x($w.size.1)" } | str join "\n"
199199+```
200200+201201+This gets all the geometry in a format `slurp` will be able to parse and use.
202202+203203+```nushell
204204+let stat = do { echo $geom | slurp -d } | complete
205205+206206+if $stat.exit_code == 1 {
207207+ echo "No selection made"
208208+ exit
209209+}
210210+```
211211+212212+I do `complete` here to get the exit code of the slurp command, if it's 1 then the user cancelled the selection
213213+and similar to the screenshot script I'll exit.
214214+215215+Now it's time to actually record, the stdout of `slurp` contains the geometry that we want to capture,
216216+so we'll pass that to `wf-recorder` with the `-g` flag:
217217+218218+```nushell
219219+wf-recorder -g ($stat.stdout) -F fps=30 -f $out_name
220220+```
221221+222222+Pretty simple command, `-g` is the geometry to record, `-F` is the format options, and `-f` is the output file.
223223+224224+Now we'll run into an issue if we run this script and start recording, there's no way to stop it!
225225+I'll cover how we're going to get around that when it comes to setting up keybinds.
226226+227227+Assuming `wf-recorder` stops, we'll send a notification to the user that the recording is done:
228228+229229+```nushell
230230+let action = notify-send --app-name=simplescreenrecorder --icon=simplescreenrecorder -t 7500 --action=open=Open --action=folder="Show In Folder" --action=delete=Delete "Recording finished" $"File saved to ($out_name)"
231231+```
232232+233233+
234234+235235+Most arguments are the same here as the screenshot script, the only difference is the icon and app name.
236236+The actions are also basically the same, so I'll leave out the explanation and just show the handler:
237237+238238+```nushell
239239+match $action {
240240+ "open" => {
241241+ xdg-open $out_name
242242+ }
243243+ "folder" => {
244244+ xdg-open $captures_folder
245245+ }
246246+ "delete" => {
247247+ rm $out_name
248248+ }
249249+}
250250+```
251251+252252+### Calling the Recording script
253253+254254+Now to actually call the script, I'll bind it to `Win` + `Shift` + `R` in Hyprland.
255255+256256+However, we're going to do something special with the `exec` line here. Instead of simply calling the script
257257+we're going to check if `wf-recorder` is already running, if this is the case we can send
258258+`SIGINT` to it to make it stop recording, meaning our script will continue and show the notification.
259259+260260+```nix
261261+wayland.windowManager.hyprland.settings.bindr = [
262262+ # ...
263263+ "SUPER SHIFT,R,exec,pkill wf-recorder --signal SIGINT || nu ${../res/screenrec.nu}"
264264+ # ...
265265+];
266266+```
267267+268268+`pkill` here will exit with code `1` if it doesn't find any processes to kill, so the `||` will run our script if `pkill` fails.
269269+270270+Note that I did this on `bindr`, this means the keybind will only happen once the R key is _released_ rather than
271271+pressed. This is to prevent a weird issue I ran into where the recording would stop immediately after starting.
272272+273273+And that's it! We can now screen record with ease. It won't record audio
274274+(might do an additional keybind in the future) and it also doesn't copy the recording to the clipboard, but
275275+it works pretty well for short videos.
276276+277277+## Full Scripts
278278+279279+### Screenshot Script
280280+281281+```nushell
282282+#!/usr/bin/env nu
283283+284284+let file_path = grimblast --freeze copysave area
285285+286286+if $file_path == "" {
287287+ exit 1;
288288+}
289289+290290+let choice = notify-send --app-name=screengrab -i $file_path -t 7500 --action=open=Open --action=folder="Show In Folder" --action=edit=Edit --action=delete=Delete "Screenshot taken" $"Screenshot saved to ($file_path) and copied to clipboard"
291291+292292+match $choice {
293293+ "open" => {
294294+ xdg-open $file_path
295295+ }
296296+ "folder" => {
297297+ xdg-open ($file_path | path dirname)
298298+ }
299299+ "edit" => {
300300+ swappy -f $file_path
301301+ }
302302+ "delete" => {
303303+ rm $file_path
304304+ }
305305+}
306306+```
307307+308308+[Most recent version on GitHub](https://github.com/Bwc9876/nix-conf/blob/main/res/screenshot.nu)
309309+310310+### Recording Script
311311+312312+```nushell
313313+#!/usr/bin/env nu
314314+315315+let date_format = "%Y-%m-%d_%H-%M-%S"
316316+317317+let captures_folder = $"($env.HOME)/Videos/Captures"
318318+319319+if not ($captures_folder | path exists) {
320320+ mkdir $captures_folder
321321+}
322322+323323+let out_name = date now | format date $"($captures_folder)/($date_format).mp4"
324324+325325+let workspaces = hyprctl monitors -j | from json | get activeWorkspace.id
326326+let windows = hyprctl clients -j | from json | where workspace.id in $workspaces
327327+let geom = $windows | each { |w| $"($w.at.0),($w.at.1) ($w.size.0)x($w.size.1)" } | str join "\n"
328328+329329+let stat = do { echo $geom | slurp -d } | complete
330330+331331+if $stat.exit_code == 1 {
332332+ echo "No selection made"
333333+ exit
334334+}
335335+336336+wf-recorder -g ($stat.stdout) -F fps=30 -f $out_name
337337+338338+let action = notify-send --app-name=simplescreenrecorder --icon=simplescreenrecorder -t 7500 --action=open=Open --action=folder="Show In Folder" --action=delete=Delete "Recording finished" $"File saved to ($out_name)"
339339+340340+match $action {
341341+ "open" => {
342342+ xdg-open $out_name
343343+ }
344344+ "folder" => {
345345+ xdg-open $captures_folder
346346+ }
347347+ "delete" => {
348348+ rm $out_name
349349+ }
350350+}
351351+```
352352+353353+[Most recent version on GitHub](https://github.com/Bwc9876/nix-conf/blob/main/res/screenrec.nu)