週間アラーム(早速のTauriの習作アプリケーション)

今更の話だけど、コロナ禍でリモートワークなど自分も含めて自宅で活動することが多くなり、自宅などでも適切なタイムスケジュールでメリハリを付ける必要が増えてきたと思います。

そのためには会社や学校と同じように指定した時間割でアラーム(チャイム)を鳴らす設備があると便利かと思い、既存のアプリケーションを探して使用しておりました(スマホ/タブレット用アプリ)。

使用したアプリはスケジューリングした指定時刻に好きな音源を鳴らすことが出来て便利だったが、カレンダーとの関連が無く、日祝日など休みの日にもチャイムが鳴ってしまうのでその度にOFFにするのが面倒でした。

ならば作ってみようネタにしようということで、構想から(・・・というのが10月くらいのお話)。

  1. 平日<休日<曜日個別<祝祭日<個別指定日 の優先度でスケジューリングしたアラームタイムテーブルで運用
  2. アプリケーションの見た目は時計表示機能をもつGUIでスケジューリング等の設定を行う
  3. デフォルト音源(フリーの音源素材)のほか任意のMP3音源をアラームとして使用可能
  4. ターゲット機器はPC。ただしアラーム用に占有出来るよう安価なもの

4のターゲット機器は、安価なものとしてRasberryPiにUSBスピーカーを接続した構成とした(今となってはRaspberryPi自体が入手困難な貴重なデバイスですが・・・w)

ターゲットはLinuxだけど、GUIなどデバッグはMacOSでやりたいのでマルチプラットフォームGUIのTauriが最適ということで、アプリケーションフレームワークとしてTauriを採用という、まさに取って付けたような結論ありきの流れ。。

TauriであればフロントエンドはWebと同等なので3の音源再生も造作も無しと。

で、一通りの機能を実装して完成したアプリケーションが左のレポジトリ。MacOSでの実行画面が下記になります。折角Bootstrapを使用したフロントエンドなのにこのデザインセンスよwww。とりあえずは実用的には動けばいいや的なものになっています。

スケジュール管理画面
タイムテーブル編集状態

アラームスケジュールの設定は左記のように、デフォルトで平日<休日といった優先度の低い日程が定められており、ユーザーはスケジュールを特に変更したい日を個別に追加する。

それぞれのスジュール日程に対してアラームセットと呼ばれるタイムテーブルを設定する。アラームセットは個別データとなっているので、別のスケジュール日程に同じアラームセットを適用することが可能(左下画面)。

画面は省略するが、祝祭について、2023年まではデフォルト設定してあるが、それ以降はユーザーで追加する(祝祭日でーたをCSVファイルで一括登録も可能)。

しかし、さすがに工夫無しで使用するとUIがWEBアプリのそのまんまになってしまいますね。もう少しデスクトップアプリっぽくしたかったのですが。。。

あと、マルチプラットフォームGUIで苦労した所として、Linux環境だけ音源再生出来ない問題がありました。

もともとWEBアプリでは、ページロード後にユーザー操作無しには音声再生出来ないような仕組みになっている(広告アプリなどが勝手に大音量で鳴らし始めたら大変なので)ので、同じエンジンを使用しているTauriでも初期には同様の問題があったらしいが、デスクトップアプリでそれでは不便だろうとのことで対策されているはず?なので、本家GithubのDiscussionに質問を投げているが、イマイチ反応は無いですねーΣ(゚д゚lll)

一応自前で解決しているが、何か原因が良く分からない解決方法でした(笑)

これをキッカケに本家のGithubに爪痕でも残せたら、、、と思ってたけど。うーん高い壁なのかなw
(機械翻訳のいい加減な英文投稿でうまく伝わらなかったのかもwww)

Tauriアプリケーション開発メモ(Webpack+Typescript+React)

いままで覚えるのが面倒とかいう理由でフロントエンドのフレームワークを使用せずに素のTypescriptでゴリゴリ書いていたが、そろそろ限界というかネタ不足と言うことで、TauriアプリケーションにおいてReactをフロント側のフレームワークとして使用することにしました。

ということで、Typescript+Reactをフロントエンドに使用したTauriアプリケーション開発準備メモ。

以下のような手順でアプリケーションを作成します。

  1. プロジェクトディレクトリの作成
  2. フロントエンドの準備
  3. Tauri (Rust)プロジェクトの作成
  4. コンパイル

プロジェクトディレクトリの作成

アプリケーション名に準ずるディレクトリを作成して、移動します。

フロントエンドの準備

npmの初期化を行い、Webpack、Typescript、 Sass、Reactをインストールします

$ npm init -y
$ npm install -D webpack webpack-cli webpack-dev-server
$ npm install -D typescript ts-loader sass style-loader css-loader postcss-loader sass-loader
$ nom install -D babel-loader @babel/core @babel/preset-env
$ npm install -S react react-dom @types/react @types/react-dom
$ npm install -D @babel/preset-react

package.jsonに デバッグ/ビルド script を追加

scripts: {
    "build": "webpack",
    "dev": "webpack serve"
},

typescriptの設定初期化

$ npx tsc --init --target es6 \
                --module es2022 \
                --lib es2022,DOM \
                --sourceMap true \
                --moduleResolution node \
                --allowSyntheticDefaultImports true \
                --resolveJsonModule true \
                --jsx react

webpack.config.js作成

webpack.config.js
const path = require("path");

module.exports = {
  mode: "development",
  entry: './src/index.tsx',
  output: {
    path: path.join(__dirname, "dist"),
    filename: "main.js",
  },
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env', '@babel/react']
            },
          },
          {
            loader: 'ts-loader',
            options: {
              configFile: path.resolve(__dirname, 'tsconfig.json'),
            },
          },
        ],
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
        ],
      },
      {
        test: /\.scss$/,
        use: [{
          loader: 'style-loader'
        }, {
          loader: 'css-loader'
        }, {
          loader: 'postcss-loader',
        }, {
          loader: 'sass-loader'
        }]
      },
      {
        test:/\.SVG$/,
        type: 'asset/source',
      },
      {
        test:/.(gif|svg|png|jpg|jpeg|JPG)$/,
        type: 'asset', /**  default <8kb: inline, >8kb: resource  */
        generator: {
          filename: 'image/[hash].[ext]',
        }
      },
      {
        test:/.(mp3|wav|aac)$/,
        type: 'asset/resource',
        generator: {
          filename: 'audio/[hash].[ext]'
        }
      },
      {
        test:/.(woff|woff2|eot|ttf|otf)$/,
        type: 'asset/resource',
        generator: {
          filename: 'font/[hash].[ext]'
        }
      }
    ]
  },
  devServer: {
    static: {
      directory: path.join(__dirname, 'dist'),
    },
    port: 3000,
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json", ".wasm"]
  },
  experiments: {
    outputModule: true,
    asyncWebAssembly: true,
  },
  target: 'web',
};

ソースディレクトリ(src)と生成物ディレクトリ(dist)の作成とテンプレ作成

dist/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Tauri App Title</title>
  <script async type="module" src="main.js"></script>
</head>
<body>
  <div id="root"></div>
</body>
</html>
src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';

new Promise(resolv => window.onload = resolv).then(() => {
    const root: ReactDOM.Root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
    root.render(
        <div>sample contents</div>
    );
});

Tauriプロジェクトの作成

$ cargo tauri init

ターゲットディレクトリを../distとするほか、フロントエンドが先に作成されているのでデフォルト通りでOK。

コンパイル&デバッグ

$ cargo tauri dev

その他UI構築にbootstrapを使いたい場合は
$ npm install -D bootstrap @types/bootstrap react-bootstrap @types/react-bootstrap
と、当然Tauri APIを使用するなら
$ npm install -D @tauri-apps/api
など・・・・とな。

TauriによるRustデスクトップアプリケーション開発

Webpack(Typescript、Sass)フロントエンド開発環境

Rustと関連クレートをインストールしたら、アプリケーションプロジェクトを作成します。

通常のRustプロジェクトであれば、cargo init [project name]とするのですが、Tauri開発環境ではフロントエンドとの同時進行開発となるので、まずはプロジェクトのトップディレクトリの作成から

$ mkdir [project name]
$ cd [project name]

として作業を始めます。ここではプロジェクト名を単純に「proj」としておきます。

次に下記コマンド cargo tauri init で対話的にプロジェクトを作成します。

  • プロジェクト名
  • タイトル
  • フロントエンドのアセットディレクトリ。生成されるRustプロジェクトからの相対パスとなるので、../dist の様にする。
    この中にあるindex.htmlがフロントエンドデザインの起点となる
  • 開発用サーバーの設定。上記アセットと同じパスにしても良いが、本番構成と同じTauiの内部サーバーを使用するので毎回コンパイルが必要となるため、開発用サーバーを立ち上げてそのURLを記載する方が便利かも。(後述のwebpack-dev-server)を使用するのが良いと思います。
proj$ cargo tauri init
What is your app name? · [project name]
What should the window title be? · [project name]
? Where are your web assets (HTML/CSS/JS) located, relative to the "<current dir>/src-tauri/tauri.conf.json" file that w
Where are your web assets (HTML/CSS/JS) located, relative to the "<current dir>/src-tauri/tauri.conf.json" file that will be created? · ../dist
What is the url of your dev server? · ../dist または http://localhost:8080

プロジェクト名その他は後からでも変更可能のようです。(src-tauri/tairu.conf.json)

設定したアセットディレクトリと、その中にindex.htmlを作成します。内容はとりあえずテンプレート的な感じで。(VScodeなら1文字目に’!’を入力して[tab]キーでドーンとテンプレートが入力されます)

proj$ mkdir dist
proj$ touch dist/index.html
proj
├── dist
│   └── index.html
└── src-tauri
    ├── Cargo.lock
    ├── Cargo.toml
    ├── build.rs
    ├── icons
    │   ├── 128x128.png
    │   ├── ........
    │   ├── 32x32.png
    │   ├── icon.icns
    │   ├── icon.ico
    │   └── icon.png
    ├── src
    │   └── main.rs
    └── tauri.conf.json		

ここまでのディレクトリ構成は左のようになります。

src-tauri以下がRustのプロジェクトです。dist以下がフロントエンドを構成するアセットディレクトリで、ここからWEBアプリと同様なノリで進めることが出来ます。

ちなみにこの段階で、tauriアプリとしてコンパイル&実行が出来ます。

proj$ cargo tauri dev

しかし、まだ真っ白なウインドウだけのアプリですが・・・・w

また、プロジェクト初期化の段階で開発サーバーにhttp://localhostとか設定していた場合は、まだサーバーを立ち上げていないので何も起こりません。

ここからフロントエンドの開発環境を整えます。まずはプロジェクトのトップディレクトリでnodejsの初期化と必要なものを色々とインストールします

proj$ npm init -y
proj$ npm install --save-dev webpack webpack-cli webpack-dev-server
proj$ npm install --save-dev typescript sass ts-loader sass-loader css-loader
proj$ npm install --save-dev postcss-loader style-loader
proj$ npm install --save-dev @tauri-apps/api @tauri-apps/cli

片っ端からインストールしている感じがしないでもないですが、これだけあれば足りるでしょうか。

次にTypescriptの初期化。この辺は好みがあるかもなので、テキトウに。。。

proj$ npx tsc --init --target es6 --module ES2020 --lib ES2020,DOM --sourceMap true --moduleResolution node --allowSyntheticDefaultImports true --resolveJsonModule true

webpack.config.jsも通常のWEBアプリと同様の設定としました。

webpack.config.js

const path = require("path");
module.exports = {
  mode: "development",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader"
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
        ],
      },
      {
        test: /\.scss$/,
        use: [
          {
            loader: 'style-loader'
          },
          {
            loader: 'css-loader'
          },
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: function () {
                  return [require('autoprefixer')];
                }
              }
            }
          },
          {
            loader: 'sass-loader'
          }
        ]
      },
      {
        test:/\.xml$/,
        loader: 'xml-loader',
      },
      {
        test:/\.html$/,
        loader: 'html-loader',
      },
      {
        test:/\.SVG$/,
        //loader: 'svg-inline-loader'
        type: 'asset/source',
      },
      {
        // https://ics.media/entry/16329/
        test:/.(gif|svg|png|jpg|jpeg|JPG)$/,
        // type: 'asset/inline',
        type: 'asset', /**  default <8kb: inline, >8kb: resource  */
        generator: {
          filename: 'image/[hash].[ext]',
        }
      },
      {
        test:/.(mp3|wav|aac)$/,
        type: 'asset/resource',
        generator: {
          filename: 'audio/[hash].[ext]'
        }
      },
      {
        test:/.(woff|woff2|eot|ttf|otf)$/,
        type: 'asset/resource',
        generator: {
          filename: 'font/[hash].[ext]'
        }
      }
    ]
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json", ".wasm"]
  },
  experiments: {
    outputModule: true,
    asyncWebAssembly: true,
  },
  devServer: {
    static: {
      directory: path.join(__dirname, 'dist')
    },
    port: 8080,
  }
}

webpackはデフォルトではソースはsrc/index.js、生成物はdist/main.mjs(モジュール版)となっているので、上記のファイル構成に対してトップへsrcディレクトリを追加します

proj$ mkdir src
proj$ touch src/index.js

そしてdist/index.htmlに埋め込むために次の一行をHEADの最後に追加

  <script type="module" src="./main.mjs"></script>

package.jsonに実行環境のスクリプトを追加

  "scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server"
  },

src-tauri/tauri.config.json を変更

  • beforeBuildCommand : Build前に実行されるコマンド、この場合Webpackコンパイルのみ
  • beforeDevCommand:デバッグ時に事前に実行されるコマンド。webpack-dev-server起動、webpackコンパイルもリアルタイムに行われる。
  • devPath:webpack-dev-serverに置き換える。これによりデバッグ状態でもフロントエンドの編集がリアルタイムに反映されるようになる
  "build": {
    "beforeBuildCommand": "npm run build",
    "beforeDevCommand": "npm run dev",
    "devPath": "http://localhost:8080/",
    "distDir": "../dist"
  },

しかし、実際のデバッグはVScode上で行われるので、.vscodeディレクトリと各種設定を行う(VScodeのデバッグでは上記tauri.config.jsonは使われないようです)

proj
├── .vscode
│   ├── launch.json
│   └── tasks.json
└── src-tauri
・・・・・
.vscode/*

launch.json

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
      {
        "type": "lldb",
        "request": "launch",
        "name": "Tauri Development Debug",
        "cargo": {
          "args": [
            "build",
            "--manifest-path=./src-tauri/Cargo.toml",
            "--no-default-features"
          ]
        },
        // task for the `beforeDevCommand` if used, must be configured in `.vscode/tasks.json`
        "preLaunchTask": "dist:dev"
      },
      {
        "type": "lldb",
        "request": "launch",
        "name": "Tauri Production Debug",
        "cargo": {
          "args": ["build", "--release", "--manifest-path=./src-tauri/Cargo.toml"]
        },
        // task for the `beforeBuildCommand` if used, must be configured in `.vscode/tasks.json`
        "preLaunchTask": "dist:build"
      }
    ]
  }

tasks.json

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
      {
        "label": "dist:dev",
        "type": "shell",
        // `dev` keeps running in the background
        // ideally you should also configure a `problemMatcher`
        // see https://code.visualstudio.com/docs/editor/tasks#_can-a-background-task-be-used-as-a-prelaunchtask-in-launchjson
        "isBackground": true,
        // change this to your `beforeDevCommand`:
        "command": "yarn",
        "args": ["dev"],
      },
      {
        "label": "dist:build",
        "type": "shell",
        // change this to your `beforeBuildCommand`:
        "command": "yarn",
        "args": ["build"],
      }
    ]
  }

コレで、VScodeで開発・デバッグが出来るようになりました。

フロントエンドの開発が殆どWEBアプリのそれと同じなのが良いですね。ていうか、特にバックエンドの要らないアプリだとjavascriptだけでデスクトップアプリが出来てしまうという。逆に殆どをRsutによるバックエンドだけにしてもアプリケーションを構成出来たり、アプリの用途や開発者の好みでどちら寄りにも出来てしまいそうなのが面白い。

TauriによるRustデスクトップアプリ開発環境構築メモ

Rustのインストール

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 全てデフォルトで作成するので下記のように、フルオートでインストール出来る
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
# 下記コマンドでパスを設定
$ source $HOME/.cargo/env

Cargoで必要なモジュールをインストール

$ cargo install cargo-generate
$ cargo install cargo-update
$ cargo install cargo-edit

VScodeなどによるデバッグ用に下記ツールをインストール

$ rustup component add rls rust-analysis rust-src

ツールのアップデート

$ rustup update
$ cargo install-update --all

Tauri関連のインストール

$ cargo install kauri-cli
$ npm install --save-dev @tauri-apps/cli

とりあえずこんな感じで、MacOS、Windows、Linux(Ubuntu)でやってみました。次は簡単なアプリケーションを作成してみる

Rustアプリケーション開発(Tauri)

以前の記事でチョットだけ触れたRsut言語ですが、チェックしていたTauri(デスクトップアプリケーション開発フレームワーク)が6月16日にバージョン1.0で正式公開されたのを機に、当方でもアプリ開発のツールとして導入することにしました。

Rustのデスクトップアプリと言うか、その構成はWEBアプリに近く、Rust作成するバックエンドコアを軸にUIなどのフロントエンドは各OSが持つブラウザエンジンが担当する。従ってフロントエンドはブラウザアプリと同様に主にHTML5で書くことになり、さらに同じソースファイルでマルチプラットフォームを実現するというコンセプトだそうです。

現在対応中のプラットフォームは

  • Windows(7以降)
  • MacOS
  • Linux

で、その他AndroidやiOSへの対応もすすんでいるという(こちらが導入の目的だったりする)。

自分の触手が動いたのは上記のマルチプラットフォームが第1に上げられるが、その他

  • フロントエンドの開発がWEBアプリのそのままで、Webpack、Sass、Typescriptなどの環境がそのまま使え、あまり新しいことを覚える必要が無い
  • 最新の言語Rustで開発出来る事。これが目的の人には待望の環境かも知れないですね。

ということで久々なのに短めですが、次回よりまた開発環境の構築などからメモしていこうと思います。