サインイン

追記(20230827 OneDriveアプリからオンライン表示へ切り替え)

無駄にプログラムなんか書く必要はありませんでした!

以下、『いかに苦労してOneDriveにサインインするか』という、上記サインイン 4に辿り着くまでの、長いながいまわり道の記録です。なので、お読みいただく価値がないことを最初に申し添えます。m(__)m

OneDrive接続専用Browserを作る!

OneDriveのサインイン画面にメールアドレスを自動入力
あとはEnterキーを叩くだけ
どうしてもコレを実現したかった!

【今回の記事】

1.動機は同期
2.WebView4Delphiコンポーネントを使う
3.プログラムでクリックを実行
4.特殊なフォルダを表示する
5.まとめ
6.お願いとお断り

1.動機は同期

この春から勤務先が変わり、それに伴って職場のPC環境も大きく変化して、ファイルのやり取りにOneDriveを利用することが多くなった。今までは、Windows11のアプリとして設定されているOneDriveを常に起動しておいて、必要な時、タスクバーから呼び出して使っていたが、バックアップ的な意味合いで利用することが多く、ファイル交換用途での利用はそれほど多くなかった。

複数のPC間でのデータのやりとりにOneDriveを利用するしかない現在の環境では、今まであまり考えたことがなかった同期のタイミングが問題になってきた。特に、それが『今すぐ』別のPCで使いたいファイルの場合、こっちのPCからクラウドにデータをアップロードして、直ちに、あっちのPCでそのデータをダウンロードしたいのだけれど、アプリのOneDriveでそれを実現する方法がわからない。

Google先生に尋ねても、『コレだ!』という答えは見つからず・・・。
(OneDriveの正しい使い方を私が知らないだけなのかもしれませんが)

仕方がないからアプリではなく、WebブラウザからOneDriveにサインインして、別のPCですぐに使いたいファイル(やフォルダ)をアップロード。別PC側でも同様にしてWebブラウザからOneDriveにサインイン、目的のファイル(やフォルダ)をダウンロードしていたんだけど・・・。

いったん接続してしまえば、ほっといても特に問題はないし、最新の状態に表示を更新したければ F5キー を押すだけだから、いいっちゃいいんだけど・・・。

OneDriveへのサインイン時にメールアドレスを入力するのが、かなりめんどくさい。

特に急いでいる時に入力を間違えたりすると、余計、イライラして、精神的に非常によろしくない。メモ帳にID替わりのメールアドレスを入力、デスクトップに保存しておいて、それをコピペすればいいかと思ったのだけれど、それすら面倒に感じてしまう自分がいよいよ情けなくなった・・・。

魂が腐っている・・・

他に作りたいプログラムも特に今はないし、仕事もそんなに忙しくはないから、思い切ってOneDriveへデータを送受信できる専用ブラウザを作ることにした。

仕様は単純明快。起動したらOneDriveのサインイン画面を表示、メールアドレスを自動入力、あとはEnterキーを叩くだけ。これさえ出来れば、仕事はかなり快適に。

Delphiと力を合わせれば、そんなの1日でできるー!(・・・と、いつも思う)
かくしてOneDrive接続専用Browser作りが日曜日の朝、スタートしたのでした。

2.WebView4Delphiコンポーネントを使う

すぐに思い出したのは、(いつか、いじってみたい)と思っていたWebView4Delphiコンポーネント。

何かでその存在を知り、ダウンロードして、ちょっと触れてみたのは・・・確か、去年のことだった・・・と思いながら、2022年の作業を記録したフォルダ内をさがすとやっぱりあったWebView4Delphiコンポーネント。さっそく、これを今年の作業フォルダへコピーする。試しにdemoフォルダ内のプログラムを動かしてみると、問題なく動く。去年、ダウンロードした際に、コンポーネントのインストールまで行っていたようだ。

WebView4Delphiは、GitHub の salvadordf / WebView4Delphi からダウンロードできる。

salvadordf / WebView4Delphi

https://github.com/salvadordf/WebView4Delphi

Aboutには次の記述があり、

WebView4Delphi is an open source project created by Salvador Díaz Fau to embed Chromium-based browsers in applications made with Delphi or Lazarus/FPC for Windows.

https://github.com/salvadordf/WebView4Delphi

ライセンスはMITだから、利用にあたっては「著作権表示および許諾表示をソフトウェアのすべての複製または重要な部分に記載」すればOK! 面倒なことは一切ない。

MITライセンスの正しい著作権表示および許諾表示の入れ方を教えてくれるWebサイト様もある。なんと有難いことか。作成者の方に心から感謝。

40代からプログラミング!
MITライセンスとは?無料ツール・テンプレートの利用方法と注意点

https://biz.addisteria.com/mit-license-1/

さっそく、WebView4Delphi コンポーネントに添付されている

demos\Delphi_VCL\MiniBrowser

これをベースにして、OneDrive接続専用Browser作りを開始する。

まず、次のようにGUIを作成。メールアドレスの自動入力という「目的」に合わせてVCLコンポーネントを追加する。

「ダウンロードフォルダを開く」は、後で説明。実行時、その右側のチェックボックス「設定」をチェックすると「入力ボタン」より右側のコントロールが出現する。

で、FormCreate手続きに自分用のコードを追加。

implementation

{$R *.dfm}

uses
  uTextViewerForm,
  uWVCoreWebView2WebResourceResponseView, uWVCoreWebView2HttpResponseHeaders,
  uWVCoreWebView2HttpHeadersCollectionIterator,
  uWVCoreWebView2ProcessInfoCollection, uWVCoreWebView2ProcessInfo,
  uWVCoreWebView2Delegates,
  //以下を追加
  Vcl.Clipbrd,
  System.IniFiles, System.UITypes,
  Winapi.ShlObj, Winapi.KnownFolders,
  Winapi.ShellAPI;

procedure TMiniBrowserFrm.FormCreate(Sender: TObject);
var
  Ini: TIniFile;
  strID, strX, strY: String;
begin
  FGetHeaders             := True;
  FHeaders                := TStringList.Create;
  FFileStream             := nil;
  FUserAuthFrm            := nil;
  FResourceContents       := nil;
  FBlockImages            := False;
  FDownloadIDGen          := 0;
  FDownloadOperation      := nil;
  WVBrowser1.DefaultURL   := URLCbx.Text;

  //Formを最大化して表示
  MiniBrowserFrm.WindowState:=wsMaximized;

  //iniファイルの存在を確認
  if FileExists(ChangeFileExt(Application.ExeName, '.ini')) then
  begin
    //iniファイルからデータを読込み
    Ini := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
    try
      strID:=Ini.ReadString('Section', 'ID', '');
      strX:=Ini.ReadString('Section', 'IchiX', '580');
      strY:=Ini.ReadString('Section', 'IchiY', '420');
    finally
      Ini.Free;
    end;
    //復号して表示(実際のプログラムではメールアドレスは暗号化処理している)
    Edit1.Text:=strID;
    EditX.Text:=strX;
    EditY.Text:=strY;
  end;

end;

あと、EXE の実行には、EXE のあるフォルダ内 に WebView2Loader.dll が必要。今回作成するのは64 ビット版の EXE だから bin64 フォルダ内の 64 ビット用DLL を使用しなければならない。

実際の動作の様子。実行時画面では・・・

「設定」にチェックすると・・・

ここにiniファイルから読み取ったサインイン用のID(メールアドレス)と、プログラムからクリックするアドレス入力欄の座標を表示(入力)する。

画面解像度が異なるPCでは、当然、フォーカスを当てたいサインイン用のメールアドレスを入力するフレームの表示位置が異なるから、最初にその位置座標を調べる必要がある。座標Checkにチェックを入れるとマウスポインタの現在位置のスクリーン座標が画面右上にリアルタイムで表示される仕組みだ。

マウスポインタが現在ある位置の座標を取得して表示するコードは・・・

procedure TMiniBrowserFrm.Timer2Timer(Sender: TObject);
var
  lh_Handle:  HWND;
  lpt_Pos:    TPoint;
  lrc_Rect:   TRect;
  lrg_Region: HRGN;
  li_Ret:     Integer;
begin
  if chkZahyo.Checked then
  begin
    //マウスカーソル位置をスクリーン座標で取得
    GetCursorPos(lpt_Pos);
    //自身のウィンドウリージョンを調べる
    lh_Handle := Self.Handle;

    //ウィンドウリージョン取得のため空のリージョンを作っておく
    lrg_Region := CreateRectRgn(0,0,0,0);
    try
      //ウィンドウリージョン取得
      li_Ret := GetWindowRgn(lh_Handle, lrg_Region);
      if (li_Ret <> ERROR) then begin
        //ウィンドウのRectを取得
        GetWindowRect(lh_Handle, lrc_Rect);
        //スクリーン座標からウィンドウの左上を原点とした座標に変換
        lpt_Pos.X := lpt_Pos.X - lrc_Rect.Left;
        lpt_Pos.Y := lpt_Pos.Y - lrc_Rect.Top;
        //ウィンドウリージョン内にマウスカーソルがあるかテスト
        if (PtInRegion(lrg_Region, lpt_Pos.X, lpt_Pos.Y)) then begin
          LabelXY.Caption:=Format('OK %d (%d-%d)', 
            [li_Ret, lpt_Pos.X, lpt_Pos.Y]);
        end else begin
          LabelXY.Caption:=Format('NG %d (%d-%d)', 
            [li_Ret, lpt_Pos.X, lpt_Pos.Y]);
        end;
      end else begin
        LabelXY.Caption:=Format('X:%d, Y:%d', [lpt_Pos.X, lpt_Pos.Y]);
      end;
    finally
      DeleteObject(lrg_Region);
    end;
  end;
end;

で、最適なクリックポイントのX座標とY座標を読み取り、Editに入力、保存ボタンをクリックでiniファイルに保存する。

クライアント座標でなく、スクリーン座標としたのは、話をカンタンにするため。だからFormCreate手続きでFormを最大化して表示するように設定している。こうすればどんな解像度のPCでも、画面左上からのスクリーン座標でメールアドレスを入力するフレームの位置が決定できると考えたのだ。

実は、最初は「他のアプリへ文字列を送信」する方法で、サインイン画面を狙い撃ちしようと思っていたのだが、ブラウザの入力欄は「ウィンドウではない」ようで、目印にするハンドルがなく、簡単には文字列を送信できないことがわかった。

朝5時くらいから作業を始めて、半日くらいイロイロ悩んだのだけれど、お昼過ぎにようやくサインインのフレームが表示されている位置をプログラムからクリックして、フォーカスを当て、さらにプログラムからCtrl+V(貼り付け)の操作を行い、iniファイルからクリップボードに読み込んでおいたメールアドレスを流し込めばイイと気づく。

そこで大きく方針を転換。文字列を送信ではなく、サインインのフレームをクリックしてメールアドレスを貼り付ける方向でGUIも、プログラムも準備した。

さらに「保存」ボタンは、メールアドレスとPCごとに異なるサインインのフレームの座標をiniファイルに書き込んで記録するために設置。

これで OneDrive接続専用Browser のGUIは完成。あとはプログラムを書くだけに。

※ 実際のプログラムでは、iniファイルにメールアドレスを保存する際に、さらにひと手間かけて暗号化、iniファイルから読みだす際に復号している。

3.プログラムでクリックを実行

普段、僕がいちばんよく利用するWebブラウザはFirefoxだ。インターネット黎明期、そう誰もが Netscape Navigator を使っていた頃からのファンなのだ。

プログラムを何度も動かして動作確認しているうちに、あることに気づく。それは何かと言うと、OneDriveのサインイン画面を表示した時の挙動が、Firefoxと作成中のOneDrive接続専用Browserではちょっと違うのだ。

FirefoxでOneDriveのサインイン画面を表示した場合は、メールアドレスを入力するフレームにセットフォーカスされた状態でサインイン画面が表示されるのに対し、作成中のOneDrive接続専用Browserではそうならない。しかも、アドレス入力欄を1回クリックしただけではダメ(セットフォーカスされない)で、2回クリックしないと入力待機状態にならない(1回めのクリックで、一瞬セットフォーカスされたように見えるが、その後、キャレットが消失してしまい、点滅状態にならない)。

DelphiのIDEから、Ctrl+F で「SetFocus」を検索キーワードにコード全体を確認しても、それは「見つからなかった」。なぜ、2回クリックしないと入力待機状態にならないのか、その原因はさっぱりわからない。

原因はわからなくても、とにかくそれが現実だから、Webにあった情報を頼りにプログラムからアドレス入力欄をクリックするコードを書いてみた。

動的にマウスをクリックするには?

https://www.petitmonte.com/bbs/answers?question_id=2014

予め、先に示したマウスカーソルの位置座標を調べるコードで、アドレス入力欄の座標を調査・記録しておいて、Formが完全に描画されたところでプログラムコードからアドレス入力欄をクリックする。

  private
    { Private 宣言 }
    //アドレス貼り付け実行の成否
    boolInput:boolean;
    //Formの表示終了イベントを取得
    procedure CMShowingChanged(var Msg:TMessage); message CM_SHOWINGCHANGED;

procedure TMiniBrowserFrm.CMShowingChanged(var Msg: TMessage);
var
  dwFlags : DWORD;
  X,Y : Integer;
  //LKeyByte : Byte;
begin
  inherited; {通常の CMShowingChagenedをまず実行}
  if Visible then
  begin
    Update; {完全に描画}
    fgWaitBreak:=False;
    WaitTime(1000);
    //クリップボードを初期化
    Clipboard.Clear;
    //文字列をクリップボードに格納
    Clipboard.AsText:=Edit1.Text;

    dwFlags:=MOUSEEVENTF_MOVE or MOUSEEVENTF_ABSOLUTE;
    X:=Trunc(StrToInt(EditX.Text)/Screen.Width*65537);
    Y:=Trunc(StrToInt(EditY.Text)/Screen.Height*65535);

    //移動
    Mouse_Event(dwFlags,X,Y,0,0);
    Application.ProcessMessages;

    //クリック
    Mouse_Event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
    Application.ProcessMessages;

    WaitTime(300);

    Mouse_Event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
    Application.ProcessMessages;

    Mouse_Event(MOUSEEVENTF_LEFTUP,0,0,0,0);

    // [Ctrl] + [V] のキー操作 -> btnCopyClick手続きで実行
    {
    LKeyByte := Ord('V');
    keybd_event(VK_CONTROL, MapVirtualKey(VK_CONTROL, 0), 0, 0);
    keybd_event(LKeyByte,   MapVirtualKey(LKeyByte, 0),   0, 0);
    keybd_event(LKeyByte,   MapVirtualKey(LKeyByte, 0),   KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CONTROL, MapVirtualKey(VK_CONTROL, 0), KEYEVENTF_KEYUP, 0);
    }
    WaitTime(1500);
    btnCopyClick(nil);
    if boolInput then btnCopy.Enabled:=False;
  end;
end;

WaitTimeなる関数は、次のWebサイト様で紹介されていたものをそのまま使用。

待ち関数の必要性

https://gumina.sakura.ne.jp/CREATION/OLD/COLUMN/CD1MATI.htm
function TMiniBrowserFrm.WaitTime(const t: integer): Boolean;
var
  Timeout: TDateTime;
begin
  //待ち関数  指定カウントが経過すれば True, 中断されたならば False
  fgWaitBreak := False;
  Timeout := Now + t/24/3600/1000;
  while (Now < Timeout)and not fgWaitBreak do begin
    Application.ProcessMessages;
    Sleep(1);
  end;
  Result := not fgWaitBreak;
end;

WaitTime関数の引数の値を様々に変えて実行してみると、MyPCでは、上記の数値で確実にアドレス入力欄をプログラムコードからクリックすることに成功!

メールアドレスをCtrl+Vする部分は、GUIの「入力」ボタンと共用だからボタンのClick手続き側に記述して呼び出す(実行する)ことにする。これだと都合4回、アドレス入力欄をクリックすることになるが、単にクリックするだけだから2回でも、4回でも大差ないだろう。むしろ、確実に動かすための保険だと考え、このままにする。

procedure TMiniBrowserFrm.btnCopyClick(Sender: TObject);
var
  dwFlags : DWORD;
  X,Y : Integer;
  LKeyByte : Byte;
begin

  boolInput:=False;

  try

    //クリップボードを初期化
    Clipboard.Clear;
    //文字列をクリップボードに格納
    Clipboard.AsText:=Edit1.Text;

    dwFlags:=MOUSEEVENTF_MOVE or MOUSEEVENTF_ABSOLUTE;
    X:=Trunc(StrToInt(EditX.Text)/Screen.Width*65537);
    Y:=Trunc(StrToInt(EditY.Text)/Screen.Height*65535);

    //移動
    Mouse_Event(dwFlags,X,Y,0,0);
    //クリック
    Mouse_Event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
    //Mouse_Event(MOUSEEVENTF_LEFTUP,0,0,0,0);
    Application.ProcessMessages;
    WaitTime(500);
    Mouse_Event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
    Mouse_Event(MOUSEEVENTF_LEFTUP,0,0,0,0);
    Application.ProcessMessages;

    // [Ctrl] + [V] のキー操作
    LKeyByte := Ord('V');
    keybd_event(VK_CONTROL, MapVirtualKey(VK_CONTROL, 0), 0, 0);
    keybd_event(LKeyByte,   MapVirtualKey(LKeyByte, 0),   0, 0);
    keybd_event(LKeyByte,   MapVirtualKey(LKeyByte, 0),   KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CONTROL, MapVirtualKey(VK_CONTROL, 0), KEYEVENTF_KEYUP, 0);

    boolInput:=True;

  except

    boolInput:=False;

  end;

end;

動作確認すると、ごく、たまーにメールアドレスが自動入力されないこともあるが、9割方予期した通りに動作してくれる。メールアドレスが自動入力されない場合でも、クリップボードにデータは間違いなく読み込まれているから、手動でCtrl+Vすればいいだけだ。

いちいち、メールアドレスを入力することに比べれば、Ctrl+VしてEnterキーを押す方がずっとカンタンだ。イイ感じになってきた。

4.特殊なフォルダを表示する

実際にファイルをOneDriveへ、アップロードしたり、ダウンロードしたり、動作確認を行ってみると、任意のタイミングでダウンロードフォルダを開く機能が欲しくなった。なので、これをボタンクリックで実行できるようにする。

ダウンロードフォルダができたのはWindows Vista以降のようで、この特殊なフォルダへのPathを取得するには、SHGetKnownFolderPath 関数を使うらしい。コードはいつもお世話になるMr.XRAYさんのWebサイトの記事を参考にして書く。

460_特殊フォルダのフルパスを取得

http://mrxray.on.coocan.jp/Delphi/plSamples/460_SpecialFolderPath.htm#08

GUIDの一覧は、次のWebサイトにあった。こちらの情報でダウンロードフォルダのGUIDがわかった。

ファイル ダイアログ ボックスのカスタム プレイス用既知のフォルダー GUID

https://learn.microsoft.com/ja-jp/dotnet/desktop/winforms/controls/known-folder-guids-for-file-dialog-custom-places?view=netframeworkdesktop-4.8

ボタンクリックで、ダウンロードフォルダを開く手続きは次の通り。

procedure TMiniBrowserFrm.btnOpenDLFolderClick(Sender: TObject);
var
  FolderID:TGUID;
  FolderPath:PChar;
  D_FolderPath, ExeFileName:string;
  LhInst:Cardinal;
begin
  //ダウンロードフォルダのGUIDを指定
  FolderID:=StringToGUID('{374DE290-123F-4565-9164-39C4925E467B}');
  if SHGetKnownFolderPath(FolderID,0,0,FolderPath)= S_OK then
  begin
    D_FolderPath := FolderPath;
    //ダウンロードフォルダを開く
    ExeFileName:= 'explorer.exe';
    LhInst:=ShellExecute(Handle, 'open', PChar(ExeFileName), 
      PChar(D_FolderPath), nil, SW_SHOW);
    if LhInst <= 32 then
    begin
      MessageBox(Handle, '起動に失敗しました.', '情報', MB_ICONINFORMATION);
    end;
  end;
end;

5.まとめ

WebView4Delphiコンポーネントのdemoにあるサンプル(MiniBrowser)に、以下の内容を追加したプログラムを使えばOneDrive自動サインインは可能。その手順は次の通り。

(1)プログラムコードでアドレス入力欄をクリックしてセットフォーカス。
(2)クリップボードに送ったメールアドレスを、プログラムコードでCtrl+V。
(3)Enterキー押し下げでOneDriveへ接続。データは即送受信可能になる。

さらに、SHGetKnownFolderPath 関数でダウンロードフォルダへのPathを取得して、エクスプローラで表示すればより一層便利に使えそう。

6.お願いとお断り

このサイトの内容を利用される場合は、自己責任でお願いします。ここに記載した内容を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。