rustnext.jsdesktop-applicationtauri

Finding challenging to call function from Rust to NextJS ( Tauri + NextJS )


Whenever I run yarn tauri dev command in my terminal, Below are the errors I'm getting in my errors, I've pasted the errors,


[{
    "resource": "/m:/Projects/screen_recorder/src-tauri/src/main.rs",
    "owner": "rustc",
    "code": {
        "value": "Click for full compiler diagnostic",
        "target": {
            "$mid": 1,
            "path": "/diagnostic message [0]",
            "scheme": "rust-analyzer-diagnostics-view",
            "query": "0",
            "fragment": "file:///m%3A/Projects/screen_recorder/src-tauri/src/main.rs"
        }
    },
    "severity": 8,
    "message": "failed to resolve: could not find `__cmd__start_capture` in `screen_capture`\ncould not find `__cmd__start_capture` in `screen_capture`",
    "source": "rustc",
    "startLineNumber": 13,
    "startColumn": 29,
    "endLineNumber": 13,
    "endColumn": 42
},{
    "resource": "/m:/Projects/screen_recorder/src-tauri/src/main.rs",
    "owner": "rustc",
    "code": {
        "value": "Click for full compiler diagnostic",
        "target": {
            "$mid": 1,
            "path": "/diagnostic message [1]",
            "scheme": "rust-analyzer-diagnostics-view",
            "query": "1",
            "fragment": "file:///m%3A/Projects/screen_recorder/src-tauri/src/main.rs"
        }
    },
    "severity": 8,
    "message": "failed to resolve: could not find `__cmd__stop_capture` in `screen_capture`\ncould not find `__cmd__stop_capture` in `screen_capture`",
    "source": "rustc",
    "startLineNumber": 14,
    "startColumn": 29,
    "endLineNumber": 14,
    "endColumn": 41
},{
    "resource": "/m:/Projects/screen_recorder/src-tauri/src/screen_capture.rs",
    "owner": "rustc",
    "code": {
        "value": "Click for full compiler diagnostic",
        "target": {
            "$mid": 1,
            "path": "/diagnostic message [4]",
            "scheme": "rust-analyzer-diagnostics-view",
            "query": "4",
            "fragment": "file:///m%3A/Projects/screen_recorder/src-tauri/src/screen_capture.rs"
        }
    },
    "severity": 8,
    "message": "mismatched types\n  expected enum `Result<screen_capture::Capture, Box<(dyn std::error::Error + Send + Sync + 'static)>>`\nfound unit type `()`",
    "source": "rustc",
    "startLineNumber": 77,
    "startColumn": 37,
    "endLineNumber": 77,
    "endColumn": 62,
    "relatedInformation": [
        {
            "startLineNumber": 77,
            "startColumn": 8,
            "endLineNumber": 77,
            "endColumn": 11,
            "message": "implicitly returns `()` as its body has no tail or `return` expression",
            "resource": "/m:/Projects/screen_recorder/src-tauri/src/screen_capture.rs"
        }
    ]
},{
    "resource": "/m:/Projects/screen_recorder/src-tauri/src/screen_capture.rs",
    "owner": "rustc",
    "code": {
        "value": "Click for full compiler diagnostic",
        "target": {
            "$mid": 1,
            "path": "/diagnostic message [6]",
            "scheme": "rust-analyzer-diagnostics-view",
            "query": "6",
            "fragment": "file:///m%3A/Projects/screen_recorder/src-tauri/src/screen_capture.rs"
        }
    },
    "severity": 8,
    "message": "mismatched types\n  expected enum `Result<(), Box<(dyn std::error::Error + Send + Sync + 'static)>>`\nfound unit type `()`",
    "source": "rustc",
    "startLineNumber": 81,
    "startColumn": 100,
    "endLineNumber": 81,
    "endColumn": 123,
    "relatedInformation": [
        {
            "startLineNumber": 81,
            "startColumn": 8,
            "endLineNumber": 81,
            "endColumn": 24,
            "message": "implicitly returns `()` as its body has no tail or `return` expression",
            "resource": "/m:/Projects/screen_recorder/src-tauri/src/screen_capture.rs"
        }
    ]
},{
    "resource": "/m:/Projects/screen_recorder/src-tauri/src/screen_capture.rs",
    "owner": "rustc",
    "code": {
        "value": "E0308",
        "target": {
            "$mid": 1,
            "path": "/stable/error_codes/E0308.html",
            "scheme": "https",
            "authority": "doc.rust-lang.org"
        }
    },
    "severity": 8,
    "message": "expected Result<(), Box<dyn Error + Send + Sync, Global>>, found ()",
    "source": "rust-analyzer",
    "startLineNumber": 83,
    "startColumn": 5,
    "endLineNumber": 83,
    "endColumn": 6
},{
    "resource": "/m:/Projects/screen_recorder/src-tauri/src/screen_capture.rs",
    "owner": "rustc",
    "code": {
        "value": "Click for full compiler diagnostic",
        "target": {
            "$mid": 1,
            "path": "/diagnostic message [1]",
            "scheme": "rust-analyzer-diagnostics-view",
            "query": "1",
            "fragment": "file:///m%3A/Projects/screen_recorder/src-tauri/src/screen_capture.rs"
        }
    },
    "severity": 4,
    "message": "unused imports: `Write`, `self`\n`#[warn(unused_imports)]` on by default",
    "source": "rustc",
    "startLineNumber": 2,
    "startColumn": 10,
    "endLineNumber": 2,
    "endColumn": 14,
    "relatedInformation": [
        {
            "startLineNumber": 2,
            "startColumn": 5,
            "endLineNumber": 3,
            "endColumn": 5,
            "message": "remove the unused imports",
            "resource": "/m:/Projects/screen_recorder/src-tauri/src/screen_capture.rs"
        }
    ],
    "tags": [
        1
    ]
},{
    "resource": "/m:/Projects/screen_recorder/src-tauri/src/screen_capture.rs",
    "owner": "rustc",
    "code": {
        "value": "Click for full compiler diagnostic",
        "target": {
            "$mid": 1,
            "path": "/diagnostic message [3]",
            "scheme": "rust-analyzer-diagnostics-view",
            "query": "3",
            "fragment": "file:///m%3A/Projects/screen_recorder/src-tauri/src/screen_capture.rs"
        }
    },
    "severity": 4,
    "message": "unused imports: `Write`, `self`\n`#[warn(unused_imports)]` on by default",
    "source": "rustc",
    "startLineNumber": 2,
    "startColumn": 16,
    "endLineNumber": 2,
    "endColumn": 21,
    "relatedInformation": [
        {
            "startLineNumber": 2,
            "startColumn": 5,
            "endLineNumber": 3,
            "endColumn": 5,
            "message": "remove the unused imports",
            "resource": "/m:/Projects/screen_recorder/src-tauri/src/screen_capture.rs"
        }
    ],
    "tags": [
        1
    ]
}]

I've installed below two libraries, in my Cargo.toml file

[dependencies]
windows-capture = "1.0.65"
once_cell = "1.9.0"

From the code provided by windows-capture Link library, I copied the code and created a new file inside src-tauri/src called screen_capture.rs

Code inside my main.rs file (src-tauri/src/main.rs)

#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]

mod screen_capture;

use tauri::Builder;

fn main() {
    Builder::default()
        .invoke_handler(tauri::generate_handler![
            screen_capture::start_capture, 
            screen_capture::stop_capture
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Code inside screen_capture.rs file ` (src-tauri/src/screen_capture.rs )

use std::{
    io::{self, Write},
    sync::{Arc, Mutex},
    time::Instant,
    error::Error,
};
use once_cell::sync::Lazy;
use windows_capture::{
    capture::GraphicsCaptureApiHandler, 
    encoder::{VideoEncoder, VideoEncoderQuality, VideoEncoderType},
    frame::Frame,
    graphics_capture_api::InternalCaptureControl,
};

static CAPTURE_SESSION: Lazy<Arc<Mutex<Option<Capture>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));

pub struct Capture {
    encoder: Option<VideoEncoder>,
    start: Instant,
}

impl Capture {
    // Assuming a new method for initialization
    pub fn new(message: String) -> Result<Self, Box<dyn Error>> {
        println!("Got flag: {}", message);
        let encoder = VideoEncoder::new(
            VideoEncoderType::Mp4,
            VideoEncoderQuality::HD1080p,
            1920,
            1080,
            "video.mp4",
        )?;
        Ok(Self {
            encoder: Some(encoder),
            start: Instant::now(),
        })
    }

    pub fn stop(&mut self) -> Result<(), Box<dyn Error>> {
        // Placeholder for actual stop logic
        if let Some(encoder) = self.encoder.take() {
            encoder.finish()?;
            println!("Capture stopped.");
        }
        Ok(())
    }
}

pub fn start_capture() -> Result<(), Box<dyn Error>> {
    let capture_session = CAPTURE_SESSION.clone();
    let mut session = capture_session.lock().unwrap();
    if session.is_none() {
        let capture = Capture::new("Start Message".into())?;
        *session = Some(capture);
        println!("Capture started.");
    } else {
        println!("Capture is already running.");
    }
    Ok(())
}

pub fn stop_capture() -> Result<(), Box<dyn Error>> {
    let capture_session = CAPTURE_SESSION.clone();
    let mut session = capture_session.lock().unwrap();
    if let Some(mut capture) = session.take() {
        capture.stop()?;
    } else {
        println!("No capture is running.");
    }
    Ok(())
}

impl GraphicsCaptureApiHandler for Capture {
    type Flags = String;
    type Error = Box<dyn std::error::Error + Send + Sync>;

    fn new(message: Self::Flags) -> Result<Self, Self::Error> {
        // Your existing implementation...
    }

    fn on_frame_arrived(&mut self, frame: &mut Frame, capture_control: InternalCaptureControl,) -> Result<(), Self::Error> {
        // Your existing implementation...
    }

    fn on_closed(&mut self) -> Result<(), Self::Error> {
        println!("Capture session closed");
        Ok(())
    }
}

tauri.conf.json file

{
  "$schema": "../node_modules/@tauri-apps/cli/schema.json",
  "build": {
    "beforeBuildCommand": "npm run build",
    "beforeDevCommand": "npm run dev",
    "devPath": "http://localhost:3000",
    "distDir": "../out",
    "withGlobalTauri": true
  },
  "package": {
    "productName": "screen_recorder",
    "version": "0.1.0"
  },
  "tauri": {
    "allowlist": {
      "all": false
    },
    "bundle": {
      "active": true,
      "category": "DeveloperTool",
      "copyright": "",
      "deb": {
        "depends": []
      },
      "externalBin": [],
      "icon": [
        "icons/32x32.png",
        "icons/128x128.png",
        "icons/128x128@2x.png",
        "icons/icon.icns",
        "icons/icon.ico"
      ],
      "identifier": "com.tauri.dev",
      "longDescription": "",
      "macOS": {
        "entitlements": null,
        "exceptionDomain": "",
        "frameworks": [],
        "providerShortName": null,
        "signingIdentity": null
      },
      "resources": [],
      "shortDescription": "",
      "targets": "all",
      "windows": {
        "certificateThumbprint": null,
        "digestAlgorithm": "sha256",
        "timestampUrl": ""
      }
    },
    "security": {
      "csp": null
    },
    "updater": {
      "active": false
    },
    "windows": [
      {
        "fullscreen": false,
        "height": 600,
        "resizable": true,
        "title": "Screen Recorder",
        "width": 800
      }
    ]
    
  }
}

Below is my frontend code:

import {invoke} from "@tauri-apps/api/tauri";

export default function Home() {
   const startRecording = () => {
     invoke("start_capture").catch(console.error);
   };

   const stopRecording = () => {
     invoke("stop_capture").catch(console.error);
   };


  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <div>
        <button onClick={startRecording}>Start Recording</button>
        <button onClick={stopRecording}>Stop Recording</button>
      </div>
    </div>
  );
};

Solution

  • Looks like you need to add #[tauri::command] macro for each function you want to invoke:

    #[tauri::command]
    pub fn start_capture() -> Result<(), Error>  {
        let capture_session = CAPTURE_SESSION.clone();
        let mut session = capture_session.lock().unwrap();
        if session.is_none() {
            let capture = Capture::new("Start Message".into())?;
            *session = Some(capture);
            println!("Capture started.");
        } else {
            println!("Capture is already running.");
        }
    }
    
    #[tauri::command]
    pub fn stop_capture() -> Result<(), Error> {
        let capture_session = CAPTURE_SESSION.clone();
        let mut session = capture_session.lock().unwrap();
        if let Some(mut capture) = session.take() {
            capture.stop()?;
        } else {
            println!("No capture is running.");
        }
    }
    

    And don't forget to make sure you serialize your Errors from Capture:

    #[derive(Debug, thiserror::Error)]
    enum Error {
      #[error(transparent)]
      Io(#[from] std::io::Error)
    }
    
    // we must manually implement serde::Serialize
    impl serde::Serialize for Error {
      fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
      where
        S: serde::ser::Serializer,
      {
        serializer.serialize_str(self.to_string().as_ref())
      }
    }
    
    

    Last thing you want to do is to align your Capture and Encoder error types from dyn Error to a serializable Error from the above.