Electron呼び出し元モジュール-スクリーンショットスキーム最適化

8901 ワード

スクリーンショット機能は,最初はelectronが提供するAPI:desktopCaptureを自然に用いた.しかし、実際には、このAPIはスクリーンショットに特化しているわけではなく、最終的な実現効果もあまりよくありません.ここではdesktopCapturerベースのスクリーンショットスキームと、macとwindows専用に設計された最適化スキームについて説明します.

DesktopCapturer


構想
  • BrowserWindowを新規作成します.
  • ウィンドウのロードが完了すると、desktopCapturerを呼び出して現在のデスクトップスクリーンのフレームを取得するとともに、現在のウィンドウサイズをフルスクリーン
  • に変更する.
  • ウィンドウに2つのcanvasを描画し、1つはマスク、1つは裁断領域
  • を表示する.
    次のdesktopCapturerの使用を添付します.
    onCapture: function() {
        const self = this;
        const desktopCapturer = Electron.desktopCapturer;
        const display = Electron.screen.getPrimaryDisplay();
        const size = display.size;
    
        desktopCapturer.getSources({types: ['screen']}, function(error, sources) {
          if (error) throw error;
          const sourceId = sources[0].id;
          navigator.webkitGetUserMedia({
            audio: false,
            video: {
              mandatory: {
                chromeMediaSource: 'desktop',
                chromeMediaSourceId: sourceId,
                minWidth: size.width,
                maxWidth: size.width,
                minHeight: size.height,
                maxHeight: size.height,
              },
            },
          }, function(stream) {
            const video = ReactDOM.findDOMNode(self.refs.captureVideo);
            const canvas = ReactDOM.findDOMNode(self.refs.captureCanvas);
            const context = canvas.getContext('2d');
            video.addEventListener('play', function() {
              video.pause();
              canvas.setAttribute('width', size.width);
              canvas.setAttribute('height', size.height);
              context.drawImage(video, 0, 0, size.width, size.height);
              self.executeAction(windowAction.windowResize, {
                window: cfg.GLB.CAPTURE_WINDOW,
                width: size.width,
                height: size.height + 85,
                onTop: true,
                fullscreen: true,
              });
            });
    
            video.addEventListener('canplay', function() {
              video.play();
            });
            video.setAttribute('src', URL.createObjectURL(stream));
            /*
            setTimeout(function() {
              video.play()
            }, 500);*/
          }, function(e) {
            console.error('getUserMediaError');
          });
        });
      },
    

    Macスクリーンショット


    macの最適化スキームは簡単で、macが持参したコマンドscreencapture -iを使用します.
    screencaptureはmacが持参したスクリーンショットコマンドで、-i-wの2つのモードがあり、それぞれ自由スクリーンショットとウィンドウスクリーンショットである.screencapture -i filePath保存するパスを指定screencapture -i -x filePath、スクリーンショット完了後のプロンプト音を閉じます

    Windowsスクリーンショット


    Windowsのスクリーンショットのスキームは、Windows自体がmacのようなスクリーンショットコマンドを提供していないため、長い間検討されてきた.調査研究によると、ネット上でwindowsのスクリーンショットに対して主にいくつかの方法があることが分かった.
  • Nircmdコマンドラインツール(http://www.nirsoft.net/utils/nircmd.html)、サードパーティのwindowsコマンドラインツール、nircmd.exe savescreenshot "f:\temp\shot.png"は現在のデスクトップを指定ファイル
  • に保存します.
  • Nodejsライブラリ:screenshot-desktop,node-desktop-screenshot,このようなNodejsライブラリは少なくありませんが、実際の実現原理はそれほど悪くなく、execを通じて自身が含むbatまたはcmdツールを呼び出します.また、指定した領域を切り取るには、フルスクリーンショットまたはパラメータを転送するしかありません.
  • は、Electronプロジェクトでネイティブモジュールを呼び出す.研究したElectron成熟製品の多くは,eagle,bearychatなどのこの方法を採用している.この方法は3つに細分化することもできます.
  • nativeコードコンパイルを呼び出す.Nodeファイル
  • node-ffi、edge-atom-shellなどのモジュールを介してnodejsにC++コード呼び出しdll
  • を直接書く.
  • は、コマンドラインにより実行する.exeファイルdll
  • を呼び出す

    3つのシナリオでは、前の2つは簡単なフルスクリーンショットであり、裁断、編集などの機能は提供できません.次に、第3のスキームの具体的な実装を分析する.

    exeでdllを呼び出す


    これはプロジェクトが現在採用しているスキームで、nodejsではchild_processのexecFileメソッドでexeファイルを実行し、exeは同級ディレクトリの下のdllを呼び出し、スクリーンショットツールを呼び出す.
    const libPath = path.join(__dirname, 'capture.exe').replace('app.asar', 'app.asar.unpacked');
    
        clipboard.clear();
    
        const exec = require('child_process').execFile;
        exec(libPath, (err, stdout, stderr) => {
          if (err) log.error('capture error', err);
          log.info('capture finished', clipboard.readImage().isEmpty());
    
          const image = clipboard.readImage();
          if (!image.isEmpty()) {
              //   UI   
          }
        })
      },
    

    exeとdllファイルをapp.asar.unpackedディレクトリにパッケージし、絶対パスで実行します.exeとdllはネットで探したもので、呼び出しは複雑ではありません.
    問題はdllの一部が適用されない点で、修正が必要で、これはdllファイルの反コンパイルと再コンパイルの問題に関連していますが、私はすでに大学の先生に完全に返して、環境とコンパイルツールさえ一時的に思い出せなくて、長い間振り回されていました.その後、コンパイルされたDLLファイルの変更方法を参照します.
    キーツール:
    逆コンパイルツールILSpy
    WindowsにはILコンパイルツールilasmとildasmが付属しています.https://docs.microsoft.com/en-us/dotnet/framework/tools/ilasm-exe-il-assembler
    手順:
  • dllファイルをILSpyにドラッグし、C++ソースコードを表示し、修正が必要な部分を見つけます.
  • ildasmツールを使用してdllを開き、ILファイルにダンプします.
  • は大体IL文法を熟知して、ソースコードを比較して、修正を行います;
  • ilasmコマンドを実行し、ILをdllに再コンパイルします.

  • パッケージングaddon


    Electronはプラットフォーム間PC開発の枠組みとして、多くのオリジナルAPIを提供していますが、需要が異なり、多くの場合、Cベースの下位業務を実現する必要があります.Electronはnodejsがオリジナルモジュールを呼び出すソリューションを提供します:Nodeオリジナルモジュールを使用node-gypの環境を構成する後、c++コードをnode呼び出し用のインタフェースに露出しbidingを修正する.gyp.現在のelectron環境を生成するaddonモジュールをコンパイルする、すなわち.nodeファイル.
    node-gyp rebuild --runtime=electron --target_arch=ia32 --target=1.7.11 --disturl=https://atom.io/download/atom-shell
    

    DeadlineとC++に対する弱い鶏を考慮すると、この案はほとんど考慮されていない.しかし、既存のプロジェクトのaddonモジュールも発見され、screenshot.nodeを直接呼び出してみました.パッケージが実行された後、エラーを報告しました.
    'xxx/screenshot.node' was compiled against a different Node.js version using
    NODE_MODULE_VERSION 53. This version of Node.js requires
    NODE_MODULE_VERSION 54. Please try re-compiling or re-installing
    ...
    

    エラーログは明確ですscreenshot.Nodeコンパイルで使用されるelectronバージョンは、現在のプロジェクトと一致しません.53はelectron 1.6に対応する.xバージョン、54は1.7に対応する.xバージョン、具体的な対応関係は表示できますhttps://github.com/lgeiger/electron-abi
    electronの更新ログは1.6に言及されているからです.xバージョンにセキュリティ・ホールがあれば、ダウングレードして解決しようとしません.また、大きなバージョンを変更すると、プロジェクト内の他のオリジナルモジュールもrebuildを必要とします.

    Nodejs呼び出しdll


    Windowsのスクリーンショットを実現できるNodeオリジナルモジュールが見つからないため,node-ffiとedge-atom-shellというNodeオリジナルモジュールを研究し,両者ともnodejsとCの通信を実現できる.
    もちろん、installの前に、install後のffiモジュールが現在の環境で使用できるように、後node-gypの環境を構成する必要があります.
    e.g
    画像管理アプリケーションeagleではedge-atom-shellが大量に運用されています.
    edge = require('edge-atom-shell');
    NiuNiuCaptureInit = edge.func(function () {
        /*#r "mscorlib.dll"
                using System.Threading.Tasks;
                using System;
                using System.Text;
                using System.Runtime.InteropServices;
    
                public class Startup
                {
                    public delegate void Callback();
                    public Callback callbackInstance;
    
                    [DllImport("NiuniuCapturex64.dll", EntryPoint = "StartScreenCapture")]
                    static extern IntPtr StartScreenCapture(StringBuilder fileName, Callback callback);
                    
                    [DllImport("NiuniuCapturex64.dll", EntryPoint = "InitScreenCapture")]
                    static extern IntPtr InitScreenCapture(string auth);
                    
                    public async Task Invoke(dynamic input)
                    {
                        return 123;
                    }
                }
            */});
    
            NiuNiuCaptureInit({}, function (error, result) {});
    
            NiuNiuCapture = edge.func(function () {/*
                #r "mscorlib.dll"
                using System.Threading.Tasks;
                using System;
                using System.Text;
                using System.Runtime.InteropServices;
    
                public class Startup
                {
                    public delegate void Callback();
                    public Callback callbackInstance;
    
                    [DllImport("NiuniuCapturex64.dll", EntryPoint = "StartScreenCapture")]
                    static extern IntPtr StartScreenCapture(StringBuilder fileName, Callback callback);
                    
                    [DllImport("NiuniuCapturex64.dll", EntryPoint = "InitScreenCapture")]
                    static extern IntPtr InitScreenCapture(string auth);
                    
                    public async Task Invoke(dynamic input)
                    {
                        callbackInstance = new Callback(delegate () {
                            ((Func>)input.event_handler)(123);
                        });
                        StringBuilder data = new StringBuilder(input.path);
                        InitScreenCapture("niuniu");
                        StartScreenCapture(data, callbackInstance);
                        return 123;
                    }
                }       
            */});
                
    

    その他


    最終的にはexeを使用してdllを実行するスキームが使用されるが、exeの開閉にも一定の遅延が必要であるため、スクリーンショット機能の応答が速くない.
    ソリューションを探している間、他にも良いオープンソースプロジェクトがありましたが、どのように利用するかは考えられませんでした.
    screenshot、微信スクリーンショットdllを利用したC#とpythonツール
    参考:electronはNodeを使用する.js原生モジュール