diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 44bf5eef..bf3d8de3 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,3 +1,11 @@ + + + # Description Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index f208e446..163076e8 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,7 +2,7 @@ name: Build + Test v2 on: push: - branches: [release/*, master] + branches: [release/*, master, bugfix/*] workflow_dispatch: jobs: @@ -12,31 +12,32 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - go-version: [1.18, 1.19] + os: [ubuntu-latest, windows-latest, macos-latest, macos-11] + go-version: ['1.20'] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install linux dependencies if: matrix.os == 'ubuntu-latest' run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev build-essential pkg-config - name: Setup Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} + cache-dependency-path: ./v2/go.sum - name: Run tests (mac) - if: matrix.os == 'macos-latest' + if: matrix.os == 'macos-latest' || matrix.os == 'macos-11' env: CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13 working-directory: ./v2 run: go test -v ./... - name: Run tests (!mac) - if: matrix.os != 'macos-latest' + if: matrix.os != 'macos-latest' && matrix.os != 'macos-11' working-directory: ./v2 run: go test -v ./... @@ -50,7 +51,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 @@ -89,15 +90,16 @@ jobs: vanilla-ts, plain, ] - go-version: [1.18, 1.19] + go-version: ['1.20'] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} + cache-dependency-path: ./v2/go.sum - name: Build Wails CLI run: | diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6db750b7..9bd98c17 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -26,6 +26,29 @@ jobs: run: | echo "::warning::Feature branch does not contain any changes to the website." +# lint_go: +# name: Run Go Linters +# runs-on: ubuntu-latest +# steps: +# - name: Checkout code +# uses: actions/checkout@v4 +# +# - name: Setup Go +# uses: actions/setup-go@v4 +# with: +# go-version: "1.21" +# +# - name: Update go modules +# working-directory: ./v2 +# run: go mod tidy +# +# - name: Run Linter +# uses: golangci/golangci-lint-action@v3 +# with: +# version: v1.54 +# working-directory: ./v2 +# args: --timeout=10m0s --config ./.golangci.yml + test_go: name: Run Go Tests runs-on: ${{ matrix.os }} @@ -33,7 +56,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - go-version: [1.18, 1.19] + go-version: ['1.20'] steps: - name: Checkout code diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 00000000..453e4cb8 --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,24 @@ +on: + workflow_dispatch: {} + pull_request: {} + push: + branches: + - main + - master + paths: + - .github/workflows/semgrep.yml + schedule: + # random HH:MM to avoid a load spike on GitHub Actions at 00:00 + - cron: 14 16 * * * +name: Semgrep +jobs: + semgrep: + name: semgrep/ci + runs-on: ubuntu-20.04 + env: + SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} + container: + image: returntocorp/semgrep + steps: + - uses: actions/checkout@v3 + - run: semgrep ci diff --git a/README.es.md b/README.es.md index e34267c1..f7c117f6 100644 --- a/README.es.md +++ b/README.es.md @@ -42,7 +42,7 @@ [English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · [한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · -[Русский](README.ru.md) +[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) diff --git a/README.fr.md b/README.fr.md new file mode 100644 index 00000000..45580841 --- /dev/null +++ b/README.fr.md @@ -0,0 +1,143 @@ +

+
+

+ +

+ Créer des applications de bureau avec Go et les technologies Web. +
+
+ + GitHub + + + + + + Go Reference + + + CodeFactor + + + + + + Awesome + + + Discord + +
+ + Build + + + GitHub tag (latest SemVer pre-release) + +

+ +
+ + + +[English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · +[한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · +[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) + + + +
+ +## Sommaire + +- [Sommaire](#sommaire) +- [Introduction](#introduction) +- [Fonctionnalités](#fonctionnalités) + - [Feuille de route](#feuille-de-route) +- [Démarrage](#démarrage) +- [Les sponsors](#les-sponsors) +- [Foire aux questions](#foire-aux-questions) +- [Les étoiles au fil du temps](#les-étoiles-au-fil-du-temps) +- [Les contributeurs](#les-contributeurs) +- [License](#license) +- [Inspiration](#inspiration) + +## Introduction + +La méthode traditionnelle pour fournir des interfaces web aux programmes Go consiste à utiliser un serveur web intégré. Wails propose une approche différente : il offre la possibilité d'intégrer à la fois le code Go et une interface web dans un seul binaire. Des outils sont fournis pour vous faciliter la tâche en gérant la création, la compilation et le regroupement des projets. Il ne vous reste plus qu'à faire preuve de créativité! + +## Fonctionnalités + +- Utiliser Go pour le backend +- Utilisez n'importe quelle technologie frontend avec laquelle vous êtes déjà familier pour construire votre interface utilisateur. +- Créez rapidement des interfaces riches pour vos programmes Go à l'aide de modèles prédéfinis. +- Appeler facilement des méthodes Go à partir de Javascript +- Définitions Typescript auto-générées pour vos structures et méthodes Go +- Dialogues et menus natifs +- Prise en charge native des modes sombre et clair +- Prise en charge des effets modernes de translucidité et de "frosted window". +- Système d'événements unifié entre Go et Javascript +- Outil puissant pour générer et construire rapidement vos projets +- Multiplateforme +- Utilise des moteurs de rendu natifs - _pas de navigateur intégré_ ! + +### Feuille de route + +La feuille de route du projet peut être consultée [ici](https://github.com/wailsapp/wails/discussions/1484). Veuillez consulter avant d'ouvrir une demande d'amélioration. + +## Démarrage + +Les instructions d'installation se trouvent sur le site [site officiel](https://wails.io/docs/gettingstarted/installation). + +## Les sponsors + +Ce projet est soutenu par ces personnes aimables et entreprises: + + +

+ +

+ +## Foire aux questions + +- S'agit-il d'une alternative à Electron ? + + Cela dépend de vos besoins. Il est conçu pour permettre aux programmeurs Go de créer facilement des applications de bureau légères ou d'ajouter une interface à leurs applications existantes. Wails offre des éléments natifs tels que des menus et des boîtes de dialogue, il peut donc être considéré comme une alternative légère à electron. + +- À qui s'adresse ce projet ? + + Les programmeurs Go qui souhaitent intégrer une interface HTML/JS/CSS à leurs applications, sans avoir à créer un serveur et à ouvrir un navigateur pour l'afficher. + +- Pourquoi ce nom ?? + + Lorsque j'ai vu WebView, je me suis dit : "Ce que je veux vraiment, c'est un outil pour construire une application WebView, un peu comme Rails l'est pour Ruby". Au départ, il s'agissait donc d'un jeu de mots (Webview on Rails). Il se trouve que c'est aussi un homophone du nom anglais du [Pays](https://en.wikipedia.org/wiki/Wales) d'où je viens. Il s'est donc imposé. + +## Les étoiles au fil du temps + +[![Graphique de l'histoire des étoiles](https://api.star-history.com/svg?repos=wailsapp/wails&type=Date)](https://star-history.com/#wailsapp/wails&Date) + +## Les contributeurs + +La liste des contributeurs devient trop importante pour le readme ! Toutes les personnes extraordinaires qui ont contribué à ce projet ont leur propre page [ici](https://wails.io/credits#contributors). + +## License + +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large) + +## Inspiration + +Ce projet a été principalement codé sur les albums suivants : + +- [Manic Street Preachers - Resistance Is Futile](https://open.spotify.com/album/1R2rsEUqXjIvAbzM0yHrxA) +- [Manic Street Preachers - This Is My Truth, Tell Me Yours](https://open.spotify.com/album/4VzCL9kjhgGQeKCiojK1YN) +- [The Midnight - Endless Summer](https://open.spotify.com/album/4Krg8zvprquh7TVn9OxZn8) +- [Gary Newman - Savage (Songs from a Broken World)](https://open.spotify.com/album/3kMfsD07Q32HRWKRrpcexr) +- [Steve Vai - Passion & Warfare](https://open.spotify.com/album/0oL0OhrE2rYVns4IGj8h2m) +- [Ben Howard - Every Kingdom](https://open.spotify.com/album/1nJsbWm3Yy2DW1KIc1OKle) +- [Ben Howard - Noonday Dream](https://open.spotify.com/album/6astw05cTiXEc2OvyByaPs) +- [Adwaith - Melyn](https://open.spotify.com/album/2vBE40Rp60tl7rNqIZjaXM) +- [Gwidaith Hen Fran - Cedors Hen Wrach](https://open.spotify.com/album/3v2hrfNGINPLuDP0YDTOjm) +- [Metallica - Metallica](https://open.spotify.com/album/2Kh43m04B1UkVcpcRa1Zug) +- [Bloc Party - Silent Alarm](https://open.spotify.com/album/6SsIdN05HQg2GwYLfXuzLB) +- [Maxthor - Another World](https://open.spotify.com/album/3tklE2Fgw1hCIUstIwPBJF) +- [Alun Tan Lan - Y Distawrwydd](https://open.spotify.com/album/0c32OywcLpdJCWWMC6vB8v) diff --git a/README.ja.md b/README.ja.md index d134d31e..a96eccfc 100644 --- a/README.ja.md +++ b/README.ja.md @@ -44,7 +44,7 @@ [English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · [한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · -[Русский](README.ru.md) +[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) @@ -54,17 +54,16 @@ - [目次](#目次) - [はじめに](#はじめに) - - [公式サイト](#公式サイト) - - [ロードマップ](#ロードマップ) - [特徴](#特徴) -- [スポンサー](#スポンサー) + - [ロードマップ](#ロードマップ) - [始め方](#始め方) +- [スポンサー](#スポンサー) - [FAQ](#faq) - [スター数の推移](#スター数の推移) - [コントリビューター](#コントリビューター) -- [特記事項](#特記事項) -- [スペシャルサンクス](#スペシャルサンクス) - [ライセンス](#ライセンス) +- [インスピレーション](#インスピレーション) + ## はじめに @@ -72,44 +71,35 @@ Go プログラムにウェブインタフェースを提供する従来の方 Wails では Go のコードとウェブフロントエンドを単一のバイナリにまとめる機能を提供します。 また、プロジェクトの作成、コンパイル、ビルドを行うためのツールが提供されています。あなたがすべきことは創造性を発揮することです! -### 公式サイト - -Version 2: - -Wails v2 が 3 つのプラットフォームでベータ版としてリリースされました。興味のある方は[新しいウェブサイト](https://wails.io)をご覧ください。 - -レガシー版 v1: - -レガシー版 v1 のドキュメントは[https://wails.app](https://wails.app)で見ることができます。 - -### ロードマップ - -プロジェクトのロードマップは[こちら](https://github.com/wailsapp/wails/discussions/1484)になります。 -機能拡張のリクエストを出す前にご覧ください。 - ## 特徴 - バックエンドには Go を利用しています - 使い慣れたフロントエンド技術を利用して UI を構築できます -- あらかじめ用意されたテンプレートを利用することで、リッチなフロントエンドを備えた Go プログラムを作成できます +- あらかじめ用意されたテンプレートを利用することで、リッチなフロントエンドを備えた Go プログラムを素早く作成できます - JavaScript から Go のメソッドを簡単に呼び出すことができます - あなたの書いた Go の構造体やメソットに応じた TypeScript の定義が自動生成されます - ネイティブのダイアログとメニューが利用できます +- ネイティブなダーク/ライトモードをサポートします - モダンな半透明や「frosted window」エフェクトをサポートしています - Go と JavaScript 間で統一されたイベント・システムを備えています - プロジェクトを素早く生成して構築する強力な cli ツールを用意しています - マルチプラットフォームに対応しています - ネイティブなレンダリングエンジンを使用しています - _つまりブラウザを埋め込んでいるわけではありません!_ -## スポンサー +### ロードマップ -このプロジェクトは、以下の方々・企業によって支えられています。 - +プロジェクトのロードマップは[こちら](https://github.com/wailsapp/wails/discussions/1484)になります。 +機能拡張のリクエストを出す前にご覧ください。 ## 始め方 インストール方法は[公式サイト](https://wails.io/docs/gettingstarted/installation)に掲載されています。 +## スポンサー + +このプロジェクトは、以下の方々・企業によって支えられています。 + + ## FAQ - Electron の代替品になりますか? @@ -130,20 +120,18 @@ Wails v2 が 3 つのプラットフォームでベータ版としてリリー ## スター数の推移 -[![スター数の推移](https://starchart.cc/wailsapp/wails.svg)](https://starchart.cc/wailsapp/wails) +[![Star History Chart](https://api.star-history.com/svg?repos=wailsapp/wails&type=Date)](https://star-history.com/#wailsapp/wails&Date) ## コントリビューター 貢献してくれた方のリストが大きくなりすぎて、readme に入りきらなくなりました! このプロジェクトに貢献してくれた素晴らしい方々のページは[こちら](https://wails.io/credits#contributors)です。 -## 特記事項 +## ライセンス -このプロジェクトは以下の方々の協力がなければ、実現しなかったと思います。 +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large) -- [Dustin Krysak](https://wiki.ubuntu.com/bashfulrobot) - 彼のサポートとフィードバックはとても大きいものでした。 -- [Serge Zaitsev](https://github.com/zserge) - Wails のウィンドウで使用している[Webview](https://github.com/zserge/webview)の作者です。 -- [Byron](https://github.com/bh90210) - 時には Byron が一人でこのプロジェクトを存続させてくれたこともありました。彼の素晴らしいインプットがなければ v1 に到達することはなかったでしょう。 +## インスピレーション プロジェクトを進める際に、以下のアルバムたちも支えてくれています。 @@ -161,20 +149,3 @@ Wails v2 が 3 つのプラットフォームでベータ版としてリリー - [Maxthor - Another World](https://open.spotify.com/album/3tklE2Fgw1hCIUstIwPBJF) - [Alun Tan Lan - Y Distawrwydd](https://open.spotify.com/album/0c32OywcLpdJCWWMC6vB8v) -## スペシャルサンクス - -

-
- このプロジェクトを後援し、WailsをApple Siliconに移植する取り組みを支援してくれた Paceとても感謝しています!

- パワフルで素早く簡単に使えるプロジェクト管理ツールをお探しなら、ぜひチェックしてみてください!

-

- -

- ライセンスを提供していただいたJetBrains社に感謝します!

- ロゴをクリックして、感謝の気持ちを伝えてください!

- -

- -## ライセンス - -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large) diff --git a/README.ko.md b/README.ko.md index 6285f932..a8cd3d1d 100644 --- a/README.ko.md +++ b/README.ko.md @@ -44,7 +44,7 @@ [English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · [한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · -[Русский](README.ru.md) +[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) diff --git a/README.md b/README.md index 6277db3a..bae60686 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ [English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · [한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · -[Русский](README.ru.md) +[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) @@ -122,7 +122,13 @@ This project is supported by these kind people / companies: ## Stargazers over time -[![Star History Chart](https://api.star-history.com/svg?repos=wailsapp/wails&type=Date)](https://star-history.com/#wailsapp/wails&Date) + + + + + Star History Chart + + ## Contributors diff --git a/README.pt-br.md b/README.pt-br.md index a61e6211..da1f6f02 100644 --- a/README.pt-br.md +++ b/README.pt-br.md @@ -41,7 +41,7 @@ [English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · -[한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) +[한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) diff --git a/README.ru.md b/README.ru.md index d6fcb1ee..36492414 100644 --- a/README.ru.md +++ b/README.ru.md @@ -41,7 +41,7 @@ [English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · -[한국어](README.ko.md) · [Español](README.es.md) · [Русский](README.ru.md) +[한국어](README.ko.md) · [Español](README.es.md) · [Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) @@ -52,7 +52,7 @@ - [Содержание](#содержание) - [Вступление](#вступление) - [Особенности](#особенности) - - [Roadmap](#roadmap) + - [Roadmap](#roadmap) - [Быстрый старт](#быстрый-старт) - [Спонсоры](#спонсоры) - [FAQ](#faq) diff --git a/README.uz.md b/README.uz.md new file mode 100644 index 00000000..3b86b199 --- /dev/null +++ b/README.uz.md @@ -0,0 +1,158 @@ +

+
+

+ +

+ Go va Web texnologiyalaridan foydalangan holda ish stoli ilovalarini yarating +
+
+ + GitHub + + + + + + Go Reference + + + CodeFactor + + + + + + Awesome + + + Discord + +
+ + Build + + + GitHub tag (latest SemVer pre-release) + +

+ +
+ + + +[English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · +[한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · +[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz) + + + +
+ +## Tarkib + +- [Tarkib](#tarkib) +- [Kirish](#kirish) +- [Xususiyatlari](#xususiyatlari) + - [Yo'l xaritasi](#yol-xaritasi) +- [Ishni boshlash](#ishni-boshlash) +- [Homiylar](#homiylar) +- [FAQ](#faq) +- [Vaqt o'tishi bilan yulduzlar](#vaqt-otishi-bilan-yulduzlar) +- [Ishtirokchilar](#homiylar) +- [Litsenziya](#litsenziya) +- [Ilhomlanish](#ilhomlanish) + +## Kirish + +Odatda, Go dasturlari uchun veb-interfeyslar o'rnatilgan veb-server va veb-brauzerdir. +Walls boshqacha yondashuvni qo'llaydi: u Go kodini ham, veb-interfeysni ham bitta ikkilik (e.g: EXE)fayliga o'raydi. +Loyihalarni yaratish, kompilyatsiya qilish va birlashtirishni boshqarish orqali ilovangizni yaratishni osonlashtiradi. +Hamma narsa faqat sizning tasavvuringiz bilan cheklangan! + +## Xususiyatlari + +- Backend uchun standart Go dan foydalaning +- UI yaratish uchun siz allaqachon tanish bo'lgan har qanday frontend texnologiyasidan foydalaning +- Oldindan tayyorlangan shablonlardan foydalanib, Go dasturlaringiz uchun tezda boy frontendlarni yarating +- Javascriptdan Go methodlarini osongina chaqiring +- Go struktura va methodlari uchun avtomatik yaratilgan Typescript ta'riflari +- Mahalliy Dialoglar va Menyular +- Mahalliy Dark / Light rejimini qo'llab-quvvatlash +- Zamonaviy shaffoflik va "muzli oyna" effektlarini qo'llab-quvvatlaydi +- Go va Javascript o'rtasidagi yagona hodisa tizimi +- Loyihalaringizni tezda yaratish va qurish uchun kuchli cli vositasi +- Ko'p platformali +- Mahalliy renderlash mexanizmlaridan foydalanadi - _o'rnatilgan brauzer yo'q_! + +### Yo'l xaritasi + +Loyihaning yoʻl xaritasini [bu yerdan](https://github.com/wailsapp/wails/discussions/1484) topish mumkin. Iltimos, maslahatlashing +Buni yaxshilash so'rovini ochishdan oldin. + +## Ishni boshlash + +O'rnatish bo'yicha ko'rsatmalar [Rasmiy veb saytda](https://wails.io/docs/gettingstarted/installation) mavjud. + +## Homiylar + +Ushbu loyiha quyidagi mehribon odamlar / kompaniyalar tomonidan qo'llab-quvvatlanadi: + + +

+ +

+ +## FAQ + +- Bu Elektronga muqobilmi? + + Sizning talablaringizga bog'liq. Bu Go dasturchilariga yengil ish stoli yaratishni osonlashtirish uchun yaratilgan + ilovalar yoki ularning mavjud ilovalariga frontend qo'shing. Wails menyular kabi mahalliy elementlarni taklif qiladi + va dialoglar, shuning uchun uni yengil elektron muqobili deb hisoblash mumkin. + +- Ushbu loyiha kimlar uchun? + + Server yaratmasdan va uni ko'rish uchun brauzerni ochmasdan, o'z ilovalari bilan HTML/JS/CSS orqali frontendini birlashtirmoqchi bo'lgan dasturchilar uchun. + +- Bu qanday nom? + + Men WebViewni ko'rganimda, men shunday deb o'yladim: "Menga WebView ilovasini yaratish uchun vositalar kerak. + biroz Rails for Rubyga o'xshaydi." Demak, dastlab bu so'zlar ustida o'yin edi (Railsda Webview). Shunday bo'ldi. + u men kelgan [Mamlakat](https://en.wikipedia.org/wiki/Wales)ning inglizcha nomining omofonidir. + +## Vaqt o'tishi bilan yulduzlar + + + + + + Yulduzlar tarixi jadvali + + + +## Ishtirokchilar + +Ishtirokchilar roʻyxati oʻqish uchun juda kattalashib bormoqda! Bunga hissa qo'shgan barcha ajoyib odamlarning +loyihada o'z sahifasi bor [bu yerga](https://wails.io/credits#contributors). + +## Litsenziya + +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large) + +## Ilhomlanish + +Ushbu loyiha asosan quyidagi albomlar uchun kodlangan: + +- [Manic Street Preachers - Resistance Is Futile](https://open.spotify.com/album/1R2rsEUqXjIvAbzM0yHrxA) +- [Manic Street Preachers - This Is My Truth, Tell Me Yours](https://open.spotify.com/album/4VzCL9kjhgGQeKCiojK1YN) +- [The Midnight - Endless Summer](https://open.spotify.com/album/4Krg8zvprquh7TVn9OxZn8) +- [Gary Newman - Savage (Songs from a Broken World)](https://open.spotify.com/album/3kMfsD07Q32HRWKRrpcexr) +- [Steve Vai - Passion & Warfare](https://open.spotify.com/album/0oL0OhrE2rYVns4IGj8h2m) +- [Ben Howard - Every Kingdom](https://open.spotify.com/album/1nJsbWm3Yy2DW1KIc1OKle) +- [Ben Howard - Noonday Dream](https://open.spotify.com/album/6astw05cTiXEc2OvyByaPs) +- [Adwaith - Melyn](https://open.spotify.com/album/2vBE40Rp60tl7rNqIZjaXM) +- [Gwidaith Hen Fran - Cedors Hen Wrach](https://open.spotify.com/album/3v2hrfNGINPLuDP0YDTOjm) +- [Metallica - Metallica](https://open.spotify.com/album/2Kh43m04B1UkVcpcRa1Zug) +- [Bloc Party - Silent Alarm](https://open.spotify.com/album/6SsIdN05HQg2GwYLfXuzLB) +- [Maxthor - Another World](https://open.spotify.com/album/3tklE2Fgw1hCIUstIwPBJF) +- [Alun Tan Lan - Y Distawrwydd](https://open.spotify.com/album/0c32OywcLpdJCWWMC6vB8v) diff --git a/README.zh-Hans.md b/README.zh-Hans.md index bd1cb1da..ce3986e3 100644 --- a/README.zh-Hans.md +++ b/README.zh-Hans.md @@ -44,7 +44,7 @@ [English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · [한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · -[Русский](README.ru.md) +[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) diff --git a/scripts/sponsors/package-lock.json b/scripts/sponsors/package-lock.json index 5153e9cf..6fa8f99f 100644 --- a/scripts/sponsors/package-lock.json +++ b/scripts/sponsors/package-lock.json @@ -13,13 +13,24 @@ } }, "node_modules/@antfu/utils": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.4.tgz", - "integrity": "sha512-qe8Nmh9rYI/HIspLSTwtbMFPj6dISG6+dJnOguTlPNXtCvS2uezdxscVBb7/3DrmNbQK49TDqpkSQ1chbRGdpQ==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.6.tgz", + "integrity": "sha512-pvFiLP2BeOKA/ZOS6jxx4XhKzdVLHDhGlFEaZ2flWWYf2xOqVniqpk38I04DFRyz+L0ASggl7SkItTc+ZLju4w==", "funding": { "url": "https://github.com/sponsors/antfu" } }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", @@ -117,6 +128,11 @@ "resolved": "https://registry.npmmirror.com/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "node_modules/b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", @@ -196,17 +212,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, "node_modules/camelcase": { "version": "4.1.0", "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-4.1.0.tgz", @@ -345,9 +350,12 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/consola": { - "version": "2.15.3", - "resolved": "https://registry.npmmirror.com/consola/-/consola-2.15.3.tgz", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } }, "node_modules/core-util-is": { "version": "1.0.2", @@ -422,9 +430,9 @@ } }, "node_modules/defu": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.2.tgz", - "integrity": "sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==" + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.3.tgz", + "integrity": "sha512-Vy2wmG3NTkmHNg/kzpuvHhkqeIx3ODWqasgCRbKtbXEN0G+HpEEv9BtJLp7ZG1CZloFaC41Ah3ZFbq7aqCqMeQ==" }, "node_modules/delayed-stream": { "version": "1.0.0", @@ -435,14 +443,14 @@ } }, "node_modules/destr": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/destr/-/destr-1.2.1.tgz", - "integrity": "sha512-ud8w0qMLlci6iFG7CNgeRr8OcbUWMsbfjtWft1eJ5Luqrz/M8Ebqk/KCzne8rKUlIQWWfLv0wD6QHrqOf4GshA==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.2.tgz", + "integrity": "sha512-65AlobnZMiCET00KaFFjUefxDX0khFA/E4myqZ7a6Sq1yZtR8+FVIvilVX66vF2uobSumxooYZChiRPCKNqhmg==" }, "node_modules/detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", "engines": { "node": ">=8" } @@ -486,24 +494,27 @@ } }, "node_modules/domutils": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", - "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", - "domhandler": "^5.0.1" + "domhandler": "^5.0.3" }, "funding": { "url": "https://github.com/fb55/domutils?sponsor=1" } }, "node_modules/dotenv": { - "version": "16.0.3", - "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.0.3.tgz", - "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, "node_modules/ecc-jsbn": { @@ -529,9 +540,9 @@ } }, "node_modules/entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "engines": { "node": ">=0.12" }, @@ -573,6 +584,11 @@ "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -632,9 +648,9 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "node_modules/fs-extra": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", - "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -954,9 +970,9 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, "node_modules/jiti": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", - "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "bin": { "jiti": "bin/jiti.js" } @@ -981,6 +997,11 @@ "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -1107,15 +1128,26 @@ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, + "node_modules/mlly": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "dependencies": { + "acorn": "^8.10.0", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "ufo": "^1.3.0" + } + }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" }, "node_modules/node-abi": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.33.0.tgz", - "integrity": "sha512-7GGVawqyHF4pfd0YFybhv/eM9JwTtPqx0mAanQ146O3FlSh3pA24zf9IRQTOsfTSqXTNzPSP5iagAJ94jjuVog==", + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.51.0.tgz", + "integrity": "sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==", "dependencies": { "semver": "^7.3.5" }, @@ -1124,19 +1156,19 @@ } }, "node_modules/node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" }, "node_modules/node-fetch-native": { - "version": "0.1.8", - "resolved": "https://registry.npmmirror.com/node-fetch-native/-/node-fetch-native-0.1.8.tgz", - "integrity": "sha512-ZNaury9r0NxaT2oL65GvdGDy+5PlSaHTovT6JV5tOW07k1TQmgC0olZETa4C9KZg0+6zBr99ctTYa3Utqj9P/Q==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.4.1.tgz", + "integrity": "sha512-NsXBU0UgBxo2rQLOeWNZqS3fvflWePMECr8CoSWoSTqCqGbVVsvl9vZu1HfQicYN0g5piV9Gh8RTEvo/uP752w==" }, "node_modules/node-html-parser": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.5.tgz", - "integrity": "sha512-fAaM511feX++/Chnhe475a0NHD8M7AxDInsqQpz6x63GRF7xYNdS8Vo5dKsIVPgsOvG7eioRRTZQnWBrhDHBSg==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.11.tgz", + "integrity": "sha512-FAgwwZ6h0DSDWxfD0Iq1tsDcBCxdJB1nXpLPPxX8YyVWzbfCjKWEzaynF4gZZ/8hziUmp7ZSaKylcn0iKhufUQ==", "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" @@ -1215,15 +1247,14 @@ "pipe-functions": "^1.2.0" } }, - "node_modules/ohmyfetch": { - "version": "0.4.21", - "resolved": "https://registry.npmmirror.com/ohmyfetch/-/ohmyfetch-0.4.21.tgz", - "integrity": "sha512-VG7f/JRvqvBOYvL0tHyEIEG7XHWm7OqIfAs6/HqwWwDfjiJ1g0huIpe5sFEmyb+7hpFa1EGNH2aERWR72tlClw==", + "node_modules/ofetch": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.3.3.tgz", + "integrity": "sha512-s1ZCMmQWXy4b5K/TW9i/DtiN8Ku+xCiHcjQ6/J/nDdssirrQNOoB165Zu8EqLMA2lln1JUth9a0aW9Ap2ctrUg==", "dependencies": { - "destr": "^1.2.0", - "node-fetch-native": "^0.1.8", - "ufo": "^0.8.6", - "undici": "^5.12.0" + "destr": "^2.0.1", + "node-fetch-native": "^1.4.0", + "ufo": "^1.3.0" } }, "node_modules/once": { @@ -1280,6 +1311,11 @@ "node": ">=0.10.0" } }, + "node_modules/pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==" + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz", @@ -1295,6 +1331,16 @@ "resolved": "https://registry.npmmirror.com/pipe-functions/-/pipe-functions-1.3.0.tgz", "integrity": "sha512-6Rtbp7criZRwedlvWbUYxqlqJoAlMvYHo2UcRWq79xZ54vZcaNHpVBOcWkX3ErT2aUA69tv+uiv4zKJbhD/Wgg==" }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -1320,6 +1366,32 @@ "node": ">=10" } }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmmirror.com/psl/-/psl-1.9.0.tgz", @@ -1350,6 +1422,11 @@ "node": ">=0.6" } }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -1365,9 +1442,9 @@ } }, "node_modules/readable-stream": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", - "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -1446,9 +1523,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -1460,18 +1537,18 @@ } }, "node_modules/sharp": { - "version": "0.31.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.3.tgz", - "integrity": "sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg==", + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", "hasInstallScript": true, "dependencies": { "color": "^4.2.3", - "detect-libc": "^2.0.1", - "node-addon-api": "^5.0.0", + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", "prebuild-install": "^7.1.1", - "semver": "^7.3.8", + "semver": "^7.5.4", "simple-get": "^4.0.1", - "tar-fs": "^2.1.1", + "tar-fs": "^3.0.4", "tunnel-agent": "^0.6.0" }, "engines": { @@ -1538,20 +1615,20 @@ "integrity": "sha512-weHOi8BolsDnGIwhhWHbA+wKSuSpvWwjRrdj8SdbIIis2vSwOE37CQP8x3EleuzxanUr3AK8BdUy4MkiOULPZg==" }, "node_modules/sponsorkit": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/sponsorkit/-/sponsorkit-0.8.2.tgz", - "integrity": "sha512-Gxh7hkTUuUVj823+BnwC77Rl5ztFEY00qA2QVULOU0N5qgj1YEP3/0BB/EayfPrHeV7HbAOwSeAnqC8/yoRABA==", + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/sponsorkit/-/sponsorkit-0.8.9.tgz", + "integrity": "sha512-utofJeNDb/Kk0Q2Qmmla5gGuwHi054hGGefxDcLoiUlhcnyiN51zqdN24y9n6073S1+et7nCBeCokrhqGGBMCQ==", "dependencies": { - "consola": "^2.15.3", - "dotenv": "^16.0.3", - "fs-extra": "^11.1.0", + "consola": "^3.2.3", + "dotenv": "^16.3.1", + "fs-extra": "^11.1.1", "image-data-uri": "^2.0.1", - "node-html-parser": "^6.1.5", - "ohmyfetch": "^0.4.21", + "node-html-parser": "^6.1.10", + "ofetch": "^1.3.3", "picocolors": "^1.0.0", - "sharp": "^0.31.3", - "unconfig": "^0.3.7", - "yargs": "^17.7.1" + "sharp": "^0.32.5", + "unconfig": "^0.3.10", + "yargs": "^17.7.2" }, "bin": { "sponsorkit": "bin/sponsorkit.js" @@ -1584,12 +1661,13 @@ "node": ">=0.10.0" } }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" + "node_modules/streamx": { + "version": "2.15.5", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.5.tgz", + "integrity": "sha512-9thPGMkKC2GctCzyCUjME3yR03x2xNo0GPKGkRw2UMYN+gqWa9uqpyNWhmsNCutU5zHmkUum0LsCRQTXUgUCAg==", + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" } }, "node_modules/string_decoder": { @@ -1704,29 +1782,23 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", "dependencies": { - "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "tar-stream": "^3.1.5" } }, "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", + "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, "node_modules/tough-cookie": { @@ -1763,38 +1835,28 @@ "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==" }, "node_modules/ufo": { - "version": "0.8.6", - "resolved": "https://registry.npmmirror.com/ufo/-/ufo-0.8.6.tgz", - "integrity": "sha512-fk6CmUgwKCfX79EzcDQQpSCMxrHstvbLswFChHS0Vump+kFkw7nJBfTZoC1j0bOGoY9I7R3n2DGek5ajbcYnOw==" + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", + "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==" }, "node_modules/unconfig": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-0.3.9.tgz", - "integrity": "sha512-8yhetFd48M641mxrkWA+C/lZU4N0rCOdlo3dFsyFPnBHBjMJfjT/3eAZBRT2RxCRqeBMAKBVgikejdS6yeBjMw==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-0.3.11.tgz", + "integrity": "sha512-bV/nqePAKv71v3HdVUn6UefbsDKQWRX+bJIkiSm0+twIds6WiD2bJLWWT3i214+J/B4edufZpG2w7Y63Vbwxow==", "dependencies": { - "@antfu/utils": "^0.7.2", + "@antfu/utils": "^0.7.6", "defu": "^6.1.2", - "jiti": "^1.18.2" + "jiti": "^1.20.0", + "mlly": "^1.4.2" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, - "node_modules/undici": { - "version": "5.21.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.21.0.tgz", - "integrity": "sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA==", - "dependencies": { - "busboy": "^1.6.0" - }, - "engines": { - "node": ">=12.18" - } - }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "engines": { "node": ">= 10.0.0" } @@ -1905,9 +1967,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", diff --git a/v2/.golangci.yml b/v2/.golangci.yml new file mode 100644 index 00000000..66b77ba7 --- /dev/null +++ b/v2/.golangci.yml @@ -0,0 +1,162 @@ +# Options for analysis runner. +run: + # Custom concurrency value + concurrency: 4 + + # Execution timeout + timeout: 10m + + # Exit code when an issue is found. + issues-exit-code: 1 + + # Inclusion of test files + tests: false + + modules-download-mode: readonly + + allow-parallel-runners: false + + go: '1.21' + + +output: + # Runner output format + format: tab + + # Print line of issue code + print-issued-lines: false + + # Append linter to the output + print-linter-name: true + + # Separate issues by line + uniq-by-line: true + + # Output path prefixing + path-prefix: "" + + # Sort results + sort-results: true + + +# Specific linter configs +linters-settings: + errcheck: + check-type-assertions: false + check-blank: false + ignore: fmt:.* + disable-default-exclusions: false + + gofmt: + simplify: true + + gofumpt: + extra-rules: false + +linters: + fast: false + # Enable all available linters. + enable-all: true + # Disable specific linters + disable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - containedctx + - contextcheck + - cyclop + - deadcode + - decorder + - depguard + - dogsled + - dupl + - dupword + - durationcheck + - errchkjson + - errorlint + - execinquery + - exhaustive + - exhaustivestruct + - exhaustruct + - exportloopref + - forbidigo + - forcetypeassert + - funlen + - gci + - ginkgolinter + - gocheckcompilerdirectives + - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - gocyclo + - godot + - godox + - goerr113 + - goheader + - goimports + - golint + - gomnd + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - gosmopolitan + - govet + - grouper + - ifshort + - importas + - ineffassign + - interfacebloat + - interfacer + - ireturn + - lll + - loggercheck + - maintidx + - makezero + - maligned + - mirror + - musttag + - nakedret + - nestif + - nilerr + - nilnil + - nlreturn + - noctx + - nolintlint + - nonamedreturns + - nosnakecase + - nosprintfhostport + - paralleltest + - prealloc + - predeclared + - promlinter + - reassign + - revive + - rowserrcheck + - scopelint + - sqlclosecheck + - staticcheck + - structcheck + - stylecheck + - tagalign + - tagliatelle + - tenv + - testableexamples + - testpackage + - thelper + - tparallel + - typecheck + - unconvert + - unparam + - unused + - usestdlibvars + - varcheck + - varnamelen + - wastedassign + - whitespace + - wrapcheck + - wsl + - zerologlint \ No newline at end of file diff --git a/v2/Taskfile.yaml b/v2/Taskfile.yaml index 526dd097..d1893732 100644 --- a/v2/Taskfile.yaml +++ b/v2/Taskfile.yaml @@ -3,6 +3,16 @@ version: "3" tasks: + download: + summary: Run go mod tidy + cmds: + - go mod tidy + + lint: + summary: Run golangci-lint + cmds: + - golangci-lint run ./... --timeout=3m -v + release: summary: Release a new version of Task. Call with `task v2:release -- ` dir: tools/release diff --git a/v2/cmd/wails/build.go b/v2/cmd/wails/build.go index 1c6b791e..a71c5d95 100644 --- a/v2/cmd/wails/build.go +++ b/v2/cmd/wails/build.go @@ -18,7 +18,6 @@ import ( ) func buildApplication(f *flags.Build) error { - if f.NoColour { pterm.DisableColor() colour.ColourEnabled = false @@ -50,6 +49,16 @@ func buildApplication(f *flags.Build) error { return err } + // Set obfuscation from project file + if projectOptions.Obfuscated { + f.Obfuscated = projectOptions.Obfuscated + } + + // Set garble args from project file + if projectOptions.GarbleArgs != "" { + f.GarbleArgs = projectOptions.GarbleArgs + } + // Create BuildOptions buildOptions := &build.Options{ Logger: logger, @@ -99,7 +108,7 @@ func buildApplication(f *flags.Build) error { {"Tags", "[" + strings.Join(f.GetTags(), ",") + "]"}, {"Race Detector", bool2Str(f.RaceDetector)}, }...) - if len(buildOptions.OutputFile) > 0 && f.GetTargets().Length() == 1 { + if len(buildOptions.OutputFile) > 0 && len(f.GetTargets()) == 1 { tableData = append(tableData, []string{"Output File", f.OutputFilename}) } pterm.DefaultSection.Println("Build Options") @@ -136,16 +145,11 @@ func buildApplication(f *flags.Build) error { // Allows cancelling the build after the first error. It would be nice if targets.Each would support funcs // returning an error. - var targetErr error targets := f.GetTargets() - targets.Each(func(platform string) { - if targetErr != nil { - return - } - - if !validPlatformArch.Contains(platform) { - buildOptions.Logger.Println("platform '%s' is not supported - skipping. Supported platforms: %s", platform, validPlatformArch.Join(",")) - return + for _, target := range targets { + if !validPlatformArch.Contains(target.Platform) { + buildOptions.Logger.Println("platform '%s' is not supported - skipping. Supported platforms: %s", target.Platform, validPlatformArch.Join(",")) + continue } desiredFilename := projectOptions.OutputFilename @@ -154,17 +158,13 @@ func buildApplication(f *flags.Build) error { } desiredFilename = strings.TrimSuffix(desiredFilename, ".exe") - // Calculate platform and arch - platformSplit := strings.Split(platform, "/") - buildOptions.Platform = platformSplit[0] - buildOptions.Arch = f.GetDefaultArch() - if len(platformSplit) > 1 { - buildOptions.Arch = platformSplit[1] - } + buildOptions.Platform = target.Platform + buildOptions.Arch = target.Arch + banner := "Building target: " + buildOptions.Platform + "/" + buildOptions.Arch pterm.DefaultSection.Println(banner) - if f.Upx && platform == "darwin/universal" { + if f.Upx && target.String() == "darwin/universal" { pterm.Warning.Println("Warning: compress flag unsupported for universal binaries. Ignoring.") f.Upx = false } @@ -173,22 +173,19 @@ func buildApplication(f *flags.Build) error { case "linux": if runtime.GOOS != "linux" { pterm.Warning.Println("Crosscompiling to Linux not currently supported.") - return + continue } case "darwin": if runtime.GOOS != "darwin" { pterm.Warning.Println("Crosscompiling to Mac not currently supported.") - return + continue } - macTargets := targets.Filter(func(platform string) bool { - return strings.HasPrefix(platform, "darwin") - }) - if macTargets.Length() == 2 { + if targets.MacTargetsCount() == 2 { buildOptions.BundleName = fmt.Sprintf("%s-%s.app", desiredFilename, buildOptions.Arch) } } - if targets.Length() > 1 { + if len(targets) > 1 { // target filename switch buildOptions.Platform { case "windows": @@ -210,32 +207,27 @@ func buildApplication(f *flags.Build) error { pterm.Warning.Println("obfuscated flag overrides skipbindings flag.") buildOptions.SkipBindings = false } + } - if !f.DryRun { - // Start Time - start := time.Now() + if !f.DryRun { + // Start Time + start := time.Now() - compiledBinary, err := build.Build(buildOptions) - if err != nil { - pterm.Error.Println(err.Error()) - targetErr = err - return - } - - buildOptions.IgnoreFrontend = true - buildOptions.CleanBinDirectory = false - - // Output stats - buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.\n", compiledBinary, time.Since(start).Round(time.Millisecond).String())) - - outputBinaries[buildOptions.Platform+"/"+buildOptions.Arch] = compiledBinary - } else { - pterm.Info.Println("Dry run: skipped build.") + compiledBinary, err := build.Build(buildOptions) + if err != nil { + pterm.Error.Println(err.Error()) + return err } - }) - if targetErr != nil { - return targetErr + buildOptions.IgnoreFrontend = true + buildOptions.CleanBinDirectory = false + + // Output stats + buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.\n", compiledBinary, time.Since(start).Round(time.Millisecond).String())) + + outputBinaries[buildOptions.Platform+"/"+buildOptions.Arch] = compiledBinary + } else { + pterm.Info.Println("Dry run: skipped build.") } if f.DryRun { @@ -255,5 +247,4 @@ func buildApplication(f *flags.Build) error { } return nil - } diff --git a/v2/cmd/wails/dev.go b/v2/cmd/wails/dev.go index dbb2cf5d..30213a68 100644 --- a/v2/cmd/wails/dev.go +++ b/v2/cmd/wails/dev.go @@ -1,16 +1,16 @@ package main import ( + "os" + "github.com/pterm/pterm" "github.com/wailsapp/wails/v2/cmd/wails/flags" "github.com/wailsapp/wails/v2/cmd/wails/internal/dev" "github.com/wailsapp/wails/v2/internal/colour" "github.com/wailsapp/wails/v2/pkg/clilogger" - "os" ) func devApplication(f *flags.Dev) error { - if f.NoColour { pterm.DisableColor() colour.ColourEnabled = false @@ -34,5 +34,4 @@ func devApplication(f *flags.Dev) error { } return dev.Application(f, logger) - } diff --git a/v2/cmd/wails/doctor.go b/v2/cmd/wails/doctor.go index 75d62b24..5306cab1 100644 --- a/v2/cmd/wails/doctor.go +++ b/v2/cmd/wails/doctor.go @@ -1,12 +1,16 @@ package main import ( + "fmt" + "github.com/wailsapp/wails/v2/internal/shell" "runtime" "runtime/debug" + "strconv" "strings" "github.com/pterm/pterm" + "github.com/jaypipes/ghw" "github.com/wailsapp/wails/v2/cmd/wails/flags" "github.com/wailsapp/wails/v2/internal/colour" "github.com/wailsapp/wails/v2/internal/system" @@ -79,6 +83,86 @@ func diagnoseEnvironment(f *flags.Doctor) error { {pterm.Bold.Sprint("Architecture"), runtime.GOARCH}, } + // Probe CPU + cpus, _ := ghw.CPU() + if cpus != nil { + prefix := "CPU" + for idx, cpu := range cpus.Processors { + if len(cpus.Processors) > 1 { + prefix = "CPU " + strconv.Itoa(idx+1) + } + systemTabledata = append(systemTabledata, []string{prefix, cpu.Model}) + } + } else { + cpuInfo := "Unknown" + if runtime.GOOS == "darwin" { + // Try to get CPU info from sysctl + if stdout, _, err := shell.RunCommand("", "sysctl", "-n", "machdep.cpu.brand_string"); err == nil { + cpuInfo = strings.TrimSpace(stdout) + } + } + systemTabledata = append(systemTabledata, []string{"CPU", cpuInfo}) + } + + // Probe GPU + gpu, _ := ghw.GPU(ghw.WithDisableWarnings()) + if gpu != nil { + prefix := "GPU" + for idx, card := range gpu.GraphicsCards { + if len(gpu.GraphicsCards) > 1 { + prefix = "GPU " + strconv.Itoa(idx+1) + " " + } + if card.DeviceInfo == nil { + systemTabledata = append(systemTabledata, []string{prefix, "Unknown"}) + continue + } + details := fmt.Sprintf("%s (%s) - Driver: %s", card.DeviceInfo.Product.Name, card.DeviceInfo.Vendor.Name, card.DeviceInfo.Driver) + systemTabledata = append(systemTabledata, []string{prefix, details}) + } + } else { + gpuInfo := "Unknown" + if runtime.GOOS == "darwin" { + // Try to get GPU info from system_profiler + if stdout, _, err := shell.RunCommand("", "system_profiler", "SPDisplaysDataType"); err == nil { + var ( + startCapturing bool + gpuInfoDetails []string + ) + for _, line := range strings.Split(stdout, "\n") { + if strings.Contains(line, "Chipset Model") { + startCapturing = true + } + if startCapturing { + gpuInfoDetails = append(gpuInfoDetails, strings.TrimSpace(line)) + } + if strings.Contains(line, "Metal Support") { + break + } + } + if len(gpuInfoDetails) > 0 { + gpuInfo = strings.Join(gpuInfoDetails, " ") + } + } + } + systemTabledata = append(systemTabledata, []string{"GPU", gpuInfo}) + } + + memory, _ := ghw.Memory() + if memory != nil { + systemTabledata = append(systemTabledata, []string{"Memory", strconv.Itoa(int(memory.TotalPhysicalBytes/1024/1024/1024)) + "GB"}) + } else { + memInfo := "Unknown" + if runtime.GOOS == "darwin" { + // Try to get Memory info from sysctl + if stdout, _, err := shell.RunCommand("", "sysctl", "-n", "hw.memsize"); err == nil { + if memSize, err := strconv.Atoi(strings.TrimSpace(stdout)); err == nil { + memInfo = strconv.Itoa(memSize/1024/1024/1024) + "GB" + } + } + } + systemTabledata = append(systemTabledata, []string{"Memory", memInfo}) + } + err = pterm.DefaultTable.WithBoxed().WithData(systemTabledata).Render() if err != nil { return err @@ -89,8 +173,8 @@ func diagnoseEnvironment(f *flags.Doctor) error { // Output Dependencies Status var dependenciesMissing []string var externalPackages []*packagemanager.Dependency - var dependenciesAvailableRequired = 0 - var dependenciesAvailableOptional = 0 + dependenciesAvailableRequired := 0 + dependenciesAvailableOptional := 0 dependenciesTableData := pterm.TableData{ {"Dependency", "Package Name", "Status", "Version"}, diff --git a/v2/cmd/wails/flags/build.go b/v2/cmd/wails/flags/build.go index 974d9c3a..28ef6b97 100644 --- a/v2/cmd/wails/flags/build.go +++ b/v2/cmd/wails/flags/build.go @@ -2,13 +2,10 @@ package flags import ( "fmt" - "os" "os/exec" - "runtime" "strings" "github.com/leaanthony/slicer" - "github.com/wailsapp/wails/v2/internal/system" "github.com/wailsapp/wails/v2/pkg/commands/build" "github.com/wailsapp/wails/v2/pkg/commands/buildtags" ) @@ -49,29 +46,15 @@ type Build struct { compilerPath string userTags []string wv2rtstrategy string // WebView2 runtime strategy - defaultArch string // Default architecture } func (b *Build) Default() *Build { - defaultPlatform := os.Getenv("GOOS") - if defaultPlatform == "" { - defaultPlatform = runtime.GOOS - } - defaultArch := os.Getenv("GOARCH") - if defaultArch == "" { - if system.IsAppleSilicon { - defaultArch = "arm64" - } else { - defaultArch = runtime.GOARCH - } - } + target := defaultTarget() result := &Build{ - Platform: defaultPlatform + "/" + defaultArch, + Platform: target.Platform, WebView2: "download", GarbleArgs: "-literals -tiny -seed=random", - - defaultArch: defaultArch, } result.BuildCommon = result.BuildCommon.Default() return result @@ -88,11 +71,8 @@ func (b *Build) GetWebView2Strategy() string { return b.wv2rtstrategy } -func (b *Build) GetTargets() *slicer.StringSlicer { - var targets slicer.StringSlicer - targets.AddSlice(strings.Split(b.Platform, ",")) - targets.Deduplicate() - return &targets +func (b *Build) GetTargets() TargetsCollection { + return parseTargets(b.Platform) } func (b *Build) GetCompilerPath() string { @@ -144,10 +124,6 @@ func (b *Build) GetBuildModeAsString() string { return "production" } -func (b *Build) GetDefaultArch() string { - return b.defaultArch -} - /* _, _ = fmt.Fprintf(w, "Frontend Directory: \t%s\n", projectOptions.GetFrontendDir()) _, _ = fmt.Fprintf(w, "Obfuscated: \t%t\n", buildOptions.Obfuscated) diff --git a/v2/cmd/wails/flags/common.go b/v2/cmd/wails/flags/common.go index e58eff41..5a81f729 100644 --- a/v2/cmd/wails/flags/common.go +++ b/v2/cmd/wails/flags/common.go @@ -1,5 +1,103 @@ package flags +import ( + "fmt" + "os" + "runtime" + "strings" + + "github.com/leaanthony/slicer" + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v2/internal/system" +) + type Common struct { NoColour bool `description:"Disable colour in output"` } + +type Target struct { + Platform string + Arch string +} + +func defaultTarget() Target { + defaultPlatform := os.Getenv("GOOS") + if defaultPlatform == "" { + defaultPlatform = runtime.GOOS + } + defaultArch := os.Getenv("GOARCH") + if defaultArch == "" { + if system.IsAppleSilicon { + defaultArch = "arm64" + } else { + defaultArch = runtime.GOARCH + } + } + + return Target{ + Platform: defaultPlatform, + Arch: defaultArch, + } +} + +type TargetsCollection []Target + +func (c TargetsCollection) MacTargetsCount() int { + count := 0 + + for _, t := range c { + if strings.HasPrefix(t.Platform, "darwin") { + count++ + } + } + + return count +} + +func (t Target) String() string { + if t.Arch != "" { + return fmt.Sprintf("%s/%s", t.Platform, t.Arch) + } + + return t.Platform +} + +func parseTargets(platform string) TargetsCollection { + allowedPlatforms := map[string]bool{ + "windows": true, + "linux": true, + "darwin": true, + } + + if !allowedPlatforms[platform] { + pterm.Error.Println("platform argument must be one of 'windows', 'linux', or 'darwin'") + os.Exit(1) + } + + var result []Target + var targets slicer.StringSlicer + + targets.AddSlice(strings.Split(platform, ",")) + targets.Deduplicate() + + targets.Each(func(platform string) { + target := Target{ + Platform: "", + Arch: "", + } + + platformSplit := strings.Split(platform, "/") + + target.Platform = platformSplit[0] + + if len(platformSplit) > 1 { + target.Arch = platformSplit[1] + } else { + target.Arch = defaultTarget().Arch + } + + result = append(result, target) + }) + + return result +} diff --git a/v2/cmd/wails/flags/dev.go b/v2/cmd/wails/flags/dev.go index 7e5e6239..b581aadb 100644 --- a/v2/cmd/wails/flags/dev.go +++ b/v2/cmd/wails/flags/dev.go @@ -6,7 +6,6 @@ import ( "net/url" "os" "path/filepath" - "runtime" "github.com/samber/lo" "github.com/wailsapp/wails/v2/internal/project" @@ -17,6 +16,7 @@ type Dev struct { BuildCommon AssetDir string `flag:"assetdir" description:"Serve assets from the given directory instead of using the provided asset FS"` + Platform string `flag:"platform" description:"Platform to target"` Extensions string `flag:"e" description:"Extensions to trigger rebuilds (comma separated) eg go"` ReloadDirs string `flag:"reloaddirs" description:"Additional directories to trigger reloads (comma separated)"` Browser bool `flag:"browser" description:"Open the application in a browser"` @@ -38,16 +38,18 @@ type Dev struct { } func (*Dev) Default() *Dev { + target := defaultTarget() result := &Dev{ Extensions: "go", Debounce: 100, + Platform: target.Platform, } + result.BuildCommon = result.BuildCommon.Default() return result } func (d *Dev) Process() error { - var err error err = d.loadAndMergeProjectConfig() if err != nil { @@ -113,18 +115,19 @@ func (d *Dev) loadAndMergeProjectConfig() error { } return nil - } // GenerateBuildOptions creates a build.Options using the flags func (d *Dev) GenerateBuildOptions() *build.Options { + targets := parseTargets(d.Platform) + result := &build.Options{ OutputType: "dev", Mode: build.Dev, Devtools: true, - Arch: runtime.GOARCH, + Arch: targets[0].Arch, Pack: true, - Platform: runtime.GOOS, + Platform: targets[0].Platform, LDFlags: d.LdFlags, Compiler: d.Compiler, ForceBuild: d.ForceBuild, diff --git a/v2/cmd/wails/flags/generate.go b/v2/cmd/wails/flags/generate.go index 9adf78aa..b14d6701 100644 --- a/v2/cmd/wails/flags/generate.go +++ b/v2/cmd/wails/flags/generate.go @@ -2,6 +2,7 @@ package flags type GenerateModule struct { Common + Compiler string `description:"Use a different go compiler to build, eg go1.15beta1"` Tags string `description:"Build tags to pass to Go compiler. Must be quoted. Space or comma (but not both) separated"` Verbosity int `name:"v" description:"Verbosity level (0 = quiet, 1 = normal, 2 = verbose)"` } @@ -12,3 +13,9 @@ type GenerateTemplate struct { Frontend string `description:"Frontend to use for the template"` Quiet bool `description:"Suppress output"` } + +func (c *GenerateModule) Default() *GenerateModule { + return &GenerateModule{ + Compiler: "go", + } +} diff --git a/v2/cmd/wails/flags/init.go b/v2/cmd/wails/flags/init.go index 6e642ec9..16d56a20 100644 --- a/v2/cmd/wails/flags/init.go +++ b/v2/cmd/wails/flags/init.go @@ -14,7 +14,6 @@ type Init struct { } func (i *Init) Default() *Init { - result := &Init{ TemplateName: "vanilla", } diff --git a/v2/cmd/wails/generate.go b/v2/cmd/wails/generate.go index a7b059ec..15a6b33d 100644 --- a/v2/cmd/wails/generate.go +++ b/v2/cmd/wails/generate.go @@ -2,6 +2,9 @@ package main import ( "fmt" + "os" + "path/filepath" + "github.com/leaanthony/debme" "github.com/leaanthony/gosod" "github.com/pterm/pterm" @@ -14,12 +17,9 @@ import ( "github.com/wailsapp/wails/v2/pkg/clilogger" "github.com/wailsapp/wails/v2/pkg/commands/bindings" "github.com/wailsapp/wails/v2/pkg/commands/buildtags" - "os" - "path/filepath" ) func generateModule(f *flags.GenerateModule) error { - if f.NoColour { pterm.DisableColor() colour.ColourEnabled = false @@ -43,10 +43,16 @@ func generateModule(f *flags.GenerateModule) error { return err } + if projectConfig.Bindings.TsGeneration.OutputType == "" { + projectConfig.Bindings.TsGeneration.OutputType = "classes" + } + _, err = bindings.GenerateBindings(bindings.Options{ - Tags: buildTags, - TsPrefix: projectConfig.Bindings.TsGeneration.Prefix, - TsSuffix: projectConfig.Bindings.TsGeneration.Suffix, + Compiler: f.Compiler, + Tags: buildTags, + TsPrefix: projectConfig.Bindings.TsGeneration.Prefix, + TsSuffix: projectConfig.Bindings.TsGeneration.Suffix, + TsOutputType: projectConfig.Bindings.TsGeneration.OutputType, }) if err != nil { return err @@ -55,7 +61,6 @@ func generateModule(f *flags.GenerateModule) error { } func generateTemplate(f *flags.GenerateTemplate) error { - if f.NoColour { pterm.DisableColor() colour.ColourEnabled = false @@ -77,7 +82,7 @@ func generateTemplate(f *flags.GenerateTemplate) error { } templateDir := filepath.Join(cwd, f.Name) if !fs.DirExists(templateDir) { - err := os.MkdirAll(templateDir, 0755) + err := os.MkdirAll(templateDir, 0o755) if err != nil { return err } @@ -200,7 +205,7 @@ func processPackageJSON(frontendDir string) error { json, _ = sjson.SetBytes(json, "name", "{{.ProjectName}}") json, _ = sjson.SetBytes(json, "author", "{{.AuthorName}}") - err = os.WriteFile(packageJSON, json, 0644) + err = os.WriteFile(packageJSON, json, 0o644) if err != nil { return err } @@ -231,7 +236,7 @@ func processPackageLockJSON(frontendDir string) error { printBulletPoint("Updating package-lock.json data...") json, _ = sjson.Set(json, "name", "{{.ProjectName}}") - err = os.WriteFile(filename, []byte(json), 0644) + err = os.WriteFile(filename, []byte(json), 0o644) if err != nil { return err } diff --git a/v2/cmd/wails/init.go b/v2/cmd/wails/init.go index f9a9c6b3..709905c0 100644 --- a/v2/cmd/wails/init.go +++ b/v2/cmd/wails/init.go @@ -3,6 +3,12 @@ package main import ( "bufio" "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + "github.com/flytam/filenamify" "github.com/leaanthony/slicer" "github.com/pkg/errors" @@ -13,15 +19,9 @@ import ( "github.com/wailsapp/wails/v2/pkg/clilogger" "github.com/wailsapp/wails/v2/pkg/git" "github.com/wailsapp/wails/v2/pkg/templates" - "os" - "os/exec" - "path/filepath" - "strings" - "time" ) func initProject(f *flags.Init) error { - if f.NoColour { pterm.DisableColor() colour.ColourEnabled = false @@ -215,7 +215,7 @@ func initGit(options *templates.Options) error { "frontend/dist", "frontend/node_modules", } - err = os.WriteFile(filepath.Join(options.TargetDir, ".gitignore"), []byte(strings.Join(ignore, "\n")), 0644) + err = os.WriteFile(filepath.Join(options.TargetDir, ".gitignore"), []byte(strings.Join(ignore, "\n")), 0o644) if err != nil { return errors.Wrap(err, "Unable to create gitignore") } @@ -271,7 +271,7 @@ func updateReplaceLine(targetPath string) { } } - err = os.WriteFile("go.mod", []byte(strings.Join(lines, "\n")), 0644) + err = os.WriteFile("go.mod", []byte(strings.Join(lines, "\n")), 0o644) if err != nil { fatal(err.Error()) } diff --git a/v2/cmd/wails/internal/dev/dev.go b/v2/cmd/wails/internal/dev/dev.go index 80d7a6d8..58988bc8 100644 --- a/v2/cmd/wails/internal/dev/dev.go +++ b/v2/cmd/wails/internal/dev/dev.go @@ -52,7 +52,6 @@ func sliceToMap(input []string) map[string]struct{} { // Application runs the application in dev mode func Application(f *flags.Dev, logger *clilogger.CLILogger) error { - cwd := lo.Must(os.Getwd()) // Update go.mod to use current wails version @@ -62,7 +61,7 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error { } // Run go mod tidy to ensure we're up-to-date - err = runCommand(cwd, false, "go", "mod", "tidy") + err = runCommand(cwd, false, f.Compiler, "mod", "tidy") if err != nil { return err } @@ -269,9 +268,18 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d }, viteServerURL, viteVersion, nil } +func isWsl() bool { + version, err := os.ReadFile("/proc/version") + + if err != nil { + return false + } + + return strings.Contains(strings.ToLower(string(version)), "wsl") +} + // restartApp does the actual rebuilding of the application when files change func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, exitCodeChannel chan int, legacyUseDevServerInsteadofCustomScheme bool) (*process.Process, string, error) { - appBinary, err := build.Build(buildOptions) println() if err != nil { @@ -298,7 +306,6 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process // parse appargs if any args, err := shlex.Split(f.AppArgs) - if err != nil { buildOptions.Logger.Fatal("Unable to parse appargs: %s", err.Error()) } @@ -309,6 +316,12 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process os.Setenv("devserver", f.DevServer) os.Setenv("frontenddevserverurl", f.FrontendDevServerURL) + if buildOptions.IsWindowsTargetPlatform() && isWsl() { + // In the case of building a Windows executable under WSL, we need to specify this variable with a list of + // variables that will be passed through + os.Setenv("WSLENV", "loglevel/w:frontenddevserverurl/w:devserver/w:assetdir/w") + } + // Start up new binary with correct args newProcess := process.NewProcess(appBinary, args...) err = newProcess.Start(exitCodeChannel) @@ -345,7 +358,7 @@ func doWatcherLoop(cwd string, buildOptions *build.Options, debugBinaryProcess * logutils.LogGreen("Watching (sub)/directory: %s", cwd) // Main Loop - var extensionsThatTriggerARebuild = sliceToMap(strings.Split(f.Extensions, ",")) + extensionsThatTriggerARebuild := sliceToMap(strings.Split(f.Extensions, ",")) var dirsThatTriggerAReload []string for _, dir := range strings.Split(f.ReloadDirs, ",") { if dir == "" { diff --git a/v2/cmd/wails/internal/dev/watcher.go b/v2/cmd/wails/internal/dev/watcher.go index 499b76df..4e7457ef 100644 --- a/v2/cmd/wails/internal/dev/watcher.go +++ b/v2/cmd/wails/internal/dev/watcher.go @@ -18,7 +18,6 @@ type Watcher interface { // initialiseWatcher creates the project directory watcher that will trigger recompile func initialiseWatcher(cwd string) (*fsnotify.Watcher, error) { - // Ignore dot files, node_modules and build directories by default ignoreDirs := getIgnoreDirs(cwd) diff --git a/v2/cmd/wails/internal/gomod/gomod.go b/v2/cmd/wails/internal/gomod/gomod.go index 52e56344..5da14a5f 100644 --- a/v2/cmd/wails/internal/gomod/gomod.go +++ b/v2/cmd/wails/internal/gomod/gomod.go @@ -56,7 +56,7 @@ func SyncGoMod(logger *clilogger.CLILogger, updateWailsVersion bool) error { } if updated { - return os.WriteFile(gomodFilename, gomodData, 0755) + return os.WriteFile(gomodFilename, gomodData, 0o755) } return nil diff --git a/v2/cmd/wails/internal/version.txt b/v2/cmd/wails/internal/version.txt index 7433fb30..afe8e01d 100644 --- a/v2/cmd/wails/internal/version.txt +++ b/v2/cmd/wails/internal/version.txt @@ -1 +1 @@ -v2.6.0 \ No newline at end of file +v2.7.1 \ No newline at end of file diff --git a/v2/cmd/wails/main.go b/v2/cmd/wails/main.go index a0a7d4a3..ccf1576e 100644 --- a/v2/cmd/wails/main.go +++ b/v2/cmd/wails/main.go @@ -66,7 +66,6 @@ func bool2Str(b bool) string { var app *clir.Cli func main() { - var err error app = clir.NewCli("Wails", "Go/HTML Appkit", internal.Version) diff --git a/v2/cmd/wails/update.go b/v2/cmd/wails/update.go index ac0e7375..9f8b6e60 100644 --- a/v2/cmd/wails/update.go +++ b/v2/cmd/wails/update.go @@ -15,7 +15,6 @@ import ( // AddSubcommand adds the `init` command for the Wails application func update(f *flags.Update) error { - if f.NoColour { colour.ColourEnabled = false pterm.DisableColor() @@ -73,8 +72,7 @@ func update(f *flags.Update) error { } func updateToVersion(targetVersion *github.SemanticVersion, force bool, currentVersion string) error { - - var targetVersionString = "v" + targetVersion.String() + targetVersionString := "v" + targetVersion.String() if targetVersionString == currentVersion { pterm.Println("\nLooks like you're up to date!") diff --git a/v2/examples/customlayout/go.mod b/v2/examples/customlayout/go.mod index 6757baca..8947007a 100644 --- a/v2/examples/customlayout/go.mod +++ b/v2/examples/customlayout/go.mod @@ -16,20 +16,23 @@ require ( github.com/leaanthony/go-ansi-parser v1.6.0 // indirect github.com/leaanthony/gosod v1.0.3 // indirect github.com/leaanthony/slicer v1.6.0 // indirect + github.com/leaanthony/u v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/samber/lo v1.38.1 // indirect github.com/tkrajina/go-reflector v0.5.6 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/wailsapp/go-webview2 v1.0.10 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - golang.org/x/crypto v0.9.0 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect ) replace github.com/wailsapp/wails/v2 v2.1.0 => ../.. diff --git a/v2/examples/customlayout/go.sum b/v2/examples/customlayout/go.sum index b00ead2f..4cdbdac6 100644 --- a/v2/examples/customlayout/go.sum +++ b/v2/examples/customlayout/go.sum @@ -5,71 +5,67 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= -github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY= -github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= +github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= -github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= -github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= -github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4= -github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM= +github.com/leaanthony/go-ansi-parser v1.6.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg= github.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ= github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4= -github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY= github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= +github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= +github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI= +github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc= -github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/samber/lo v1.27.1 h1:sTXwkRiIFIQG+G0HeAvOEnGjqWeWtI9cg5/n51KrxPg= -github.com/samber/lo v1.27.1/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= -github.com/tkrajina/go-reflector v0.5.5 h1:gwoQFNye30Kk7NrExj8zm3zFtrGPqOkzFMLuQZg1DtQ= -github.com/tkrajina/go-reflector v0.5.5/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE= github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/wailsapp/go-webview2 v1.0.7 h1:s95+7irJsAsTy1RsjJ6N0cYX7tZ4gP7Uzawds0L2urs= +github.com/wailsapp/go-webview2 v1.0.7/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/go-webview2 v1.0.10/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= -golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -79,15 +75,13 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v2/go.mod b/v2/go.mod index 60bf75ab..a2d0ab41 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -1,6 +1,6 @@ module github.com/wailsapp/wails/v2 -go 1.18 +go 1.20 require ( github.com/Masterminds/semver v1.5.0 @@ -12,9 +12,11 @@ require ( github.com/fsnotify/fsnotify v1.4.9 github.com/go-git/go-git/v5 v5.3.0 github.com/go-ole/go-ole v1.2.6 + github.com/godbus/dbus/v5 v5.1.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.3.0 github.com/jackmordaunt/icns v1.0.0 + github.com/jaypipes/ghw v0.12.0 github.com/labstack/echo/v4 v4.10.2 github.com/labstack/gommon v0.4.0 github.com/leaanthony/clir v1.3.0 @@ -31,15 +33,15 @@ require ( github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/samber/lo v1.38.1 github.com/stretchr/testify v1.8.1 - github.com/tc-hib/winres v0.1.5 + github.com/tc-hib/winres v0.2.1 github.com/tidwall/sjson v1.1.7 github.com/tkrajina/go-reflector v0.5.6 - github.com/wailsapp/go-webview2 v1.0.7 + github.com/wailsapp/go-webview2 v1.0.10 github.com/wailsapp/mimetype v1.4.1 github.com/wzshiming/ctc v1.2.3 golang.org/x/mod v0.12.0 - golang.org/x/net v0.10.0 - golang.org/x/sys v0.8.0 + golang.org/x/net v0.17.0 + golang.org/x/sys v0.13.0 golang.org/x/tools v0.6.0 ) @@ -48,17 +50,20 @@ require ( atomicgo.dev/keyboard v0.2.8 // indirect bitbucket.org/creachadair/shell v0.0.7 // indirect github.com/Microsoft/go-winio v0.4.16 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect github.com/alecthomas/chroma v0.10.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/containerd/console v1.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect github.com/emirpasic/gods v1.12.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.2.0 // indirect github.com/gookit/color v1.5.2 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/imdario/mergo v0.3.12 // indirect + github.com/jaypipes/pcidb v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect @@ -87,11 +92,13 @@ require ( github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect github.com/yuin/goldmark v1.4.13 // indirect github.com/yuin/goldmark-emoji v1.0.1 // indirect - golang.org/x/crypto v0.9.0 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect - golang.org/x/image v0.5.0 // indirect - golang.org/x/term v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/image v0.12.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + howett.net/plist v1.0.0 // indirect ) diff --git a/v2/go.sum b/v2/go.sum index 7769626f..fdc18d02 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -17,6 +17,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= @@ -51,6 +53,8 @@ github.com/flytam/filenamify v1.0.0 h1:ewx6BY2dj7U6h2zGPJmt33q/BjkSf/YsY/woQvnUN github.com/flytam/filenamify v1.0.0/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= @@ -63,8 +67,11 @@ github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbK github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= @@ -83,10 +90,15 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ= github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo= +github.com/jaypipes/ghw v0.12.0 h1:xU2/MDJfWmBhJnujHY9qwXQLs3DBsf0/Xa9vECY0Tho= +github.com/jaypipes/ghw v0.12.0/go.mod h1:jeJGbkRB2lL3/gxYzNYzEDETV1ZJ56OKr+CSeSEym+g= +github.com/jaypipes/pcidb v1.0.0 h1:vtZIfkiCUE42oYbJS0TAq9XSfSmcsgo9IdxSm9qzYU8= +github.com/jaypipes/pcidb v1.0.0/go.mod h1:TnYUvqhPBzCKnH34KrIX22kAeEbDCSRJ9cqLRCuNDfk= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= @@ -119,8 +131,6 @@ github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRC github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= -github.com/leaanthony/u v1.0.1 h1:tJLpf9EsuCgSB02ojRxg8KRqMgRN6mCTvFwI55kxRFE= -github.com/leaanthony/u v1.0.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI= github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= github.com/leaanthony/winicon v1.0.0 h1:ZNt5U5dY71oEoKZ97UVwJRT4e+5xo5o/ieKuHuk8NqQ= @@ -198,8 +208,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/tc-hib/winres v0.1.5 h1:2dA5yfjdoEA3UyRaOC92HNMt3jap66pLzoW4MjpC/0M= -github.com/tc-hib/winres v0.1.5/go.mod h1:pe6dOR40VOrGz8PkzreVKNvEKnlE8t4yR8A8naL+t7A= +github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= +github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= github.com/tidwall/gjson v1.9.3 h1:hqzS9wAHMO+KVBBkLxYdkEeeFHuqr95GfClRLKlgK0E= github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -218,8 +228,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/wailsapp/go-webview2 v1.0.7 h1:s95+7irJsAsTy1RsjJ6N0cYX7tZ4gP7Uzawds0L2urs= -github.com/wailsapp/go-webview2 v1.0.7/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= +github.com/wailsapp/go-webview2 v1.0.10 h1:PP5Hug6pnQEAhfRzLCoOh2jJaPdrqeRgJKZhyYyDV/w= +github.com/wailsapp/go-webview2 v1.0.10/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= github.com/wzshiming/ctc v1.2.3 h1:q+hW3IQNsjIlOFBTGZZZeIXTElFM4grF4spW/errh/c= @@ -240,15 +250,15 @@ golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= -golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= +golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ= +golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -257,11 +267,13 @@ golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5o golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -287,22 +299,24 @@ golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -320,11 +334,15 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= diff --git a/v2/internal/app/app_bindings.go b/v2/internal/app/app_bindings.go index d079790a..be031819 100644 --- a/v2/internal/app/app_bindings.go +++ b/v2/internal/app/app_bindings.go @@ -31,6 +31,7 @@ func (a *App) Run() error { var tsPrefixFlag *string var tsPostfixFlag *string + var tsOutputTypeFlag *string tsPrefix := os.Getenv("tsprefix") if tsPrefix == "" { @@ -42,6 +43,11 @@ func (a *App) Run() error { tsPostfixFlag = bindingFlags.String("tssuffix", "", "Suffix for generated typescript entities") } + tsOutputType := os.Getenv("tsoutputtype") + if tsOutputType == "" { + tsOutputTypeFlag = bindingFlags.String("tsoutputtype", "", "Output type for generated typescript entities (classes|interfaces)") + } + _ = bindingFlags.Parse(os.Args[1:]) if tsPrefixFlag != nil { tsPrefix = *tsPrefixFlag @@ -49,11 +55,15 @@ func (a *App) Run() error { if tsPostfixFlag != nil { tsSuffix = *tsPostfixFlag } + if tsOutputTypeFlag != nil { + tsOutputType = *tsOutputTypeFlag + } - appBindings := binding.NewBindings(a.logger, a.options.Bind, bindingExemptions, IsObfuscated()) + appBindings := binding.NewBindings(a.logger, a.options.Bind, bindingExemptions, IsObfuscated(), a.options.EnumBind) appBindings.SetTsPrefix(tsPrefix) appBindings.SetTsSuffix(tsSuffix) + appBindings.SetOutputType(tsOutputType) err := generateBindings(appBindings) if err != nil { diff --git a/v2/internal/app/app_dev.go b/v2/internal/app/app_dev.go index 51e3e63b..58cd94ef 100644 --- a/v2/internal/app/app_dev.go +++ b/v2/internal/app/app_dev.go @@ -209,7 +209,7 @@ func CreateApp(appoptions *options.App) (*App, error) { appoptions.OnDomReady, appoptions.OnBeforeClose, } - appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, false) + appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, false, appoptions.EnumBind) eventHandler := runtime.NewEvents(myLogger) ctx = context.WithValue(ctx, "events", eventHandler) diff --git a/v2/internal/app/app_devtools_not.go b/v2/internal/app/app_devtools_not.go index 4e7274ad..91267204 100644 --- a/v2/internal/app/app_devtools_not.go +++ b/v2/internal/app/app_devtools_not.go @@ -2,6 +2,7 @@ package app +// IsDevtoolsEnabled returns true if devtools should be enabled // Note: devtools flag is also added in debug builds func IsDevtoolsEnabled() bool { return false diff --git a/v2/internal/app/app_production.go b/v2/internal/app/app_production.go index 96ed84e3..4c217b17 100644 --- a/v2/internal/app/app_production.go +++ b/v2/internal/app/app_production.go @@ -72,7 +72,7 @@ func CreateApp(appoptions *options.App) (*App, error) { appoptions.OnDomReady, appoptions.OnBeforeClose, } - appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, IsObfuscated()) + appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, IsObfuscated(), appoptions.EnumBind) eventHandler := runtime.NewEvents(myLogger) ctx = context.WithValue(ctx, "events", eventHandler) // Attach logger to context diff --git a/v2/internal/binding/binding.go b/v2/internal/binding/binding.go old mode 100755 new mode 100644 index 75b821f2..568e11b0 --- a/v2/internal/binding/binding.go +++ b/v2/internal/binding/binding.go @@ -23,17 +23,20 @@ type Bindings struct { exemptions slicer.StringSlicer structsToGenerateTS map[string]map[string]interface{} + enumsToGenerateTS map[string]map[string]interface{} tsPrefix string tsSuffix string + tsInterface bool obfuscate bool } // NewBindings returns a new Bindings object -func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}, obfuscate bool) *Bindings { +func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}, obfuscate bool, enumsToBind []interface{}) *Bindings { result := &Bindings{ db: newDB(), logger: logger.CustomLogger("Bindings"), structsToGenerateTS: make(map[string]map[string]interface{}), + enumsToGenerateTS: make(map[string]map[string]interface{}), obfuscate: obfuscate, } @@ -47,6 +50,10 @@ func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exem result.exemptions.Add(name) } + for _, enum := range enumsToBind { + result.AddEnumToGenerateTS(enum) + } + // Add the structs to bind for _, ptr := range structPointersToBind { err := result.Add(ptr) @@ -60,7 +67,6 @@ func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exem // Add the given struct methods to the Bindings func (b *Bindings) Add(structPtr interface{}) error { - methods, err := b.getMethods(structPtr) if err != nil { return fmt.Errorf("cannot bind value to app: %s", err.Error()) @@ -89,16 +95,21 @@ func (b *Bindings) ToJSON() (string, error) { func (b *Bindings) GenerateModels() ([]byte, error) { models := map[string]string{} var seen slicer.StringSlicer + var seenEnumsPackages slicer.StringSlicer allStructNames := b.getAllStructNames() allStructNames.Sort() + allEnumNames := b.getAllEnumNames() + allEnumNames.Sort() for packageName, structsToGenerate := range b.structsToGenerateTS { thisPackageCode := "" w := typescriptify.New() w.WithPrefix(b.tsPrefix) w.WithSuffix(b.tsSuffix) + w.WithInterface(b.tsInterface) w.Namespace = packageName w.WithBackupDir("") w.KnownStructs = allStructNames + w.KnownEnums = allEnumNames // sort the structs var structNames []string for structName := range structsToGenerate { @@ -113,6 +124,20 @@ func (b *Bindings) GenerateModels() ([]byte, error) { structInterface := structsToGenerate[structName] w.Add(structInterface) } + + // if we have enums for this package, add them as well + var enums, enumsExist = b.enumsToGenerateTS[packageName] + if enumsExist { + for enumName, enum := range enums { + fqemumname := packageName + "." + enumName + if seen.Contains(fqemumname) { + continue + } + w.AddEnum(enum) + } + seenEnumsPackages.Add(packageName) + } + str, err := w.Convert(nil) if err != nil { return nil, err @@ -122,6 +147,35 @@ func (b *Bindings) GenerateModels() ([]byte, error) { models[packageName] = thisPackageCode } + // Add outstanding enums to the models that were not in packages with structs + for packageName, enumsToGenerate := range b.enumsToGenerateTS { + if seenEnumsPackages.Contains(packageName) { + continue + } + + thisPackageCode := "" + w := typescriptify.New() + w.WithPrefix(b.tsPrefix) + w.WithSuffix(b.tsSuffix) + w.WithInterface(b.tsInterface) + w.Namespace = packageName + w.WithBackupDir("") + + for enumName, enum := range enumsToGenerate { + fqemumname := packageName + "." + enumName + if seen.Contains(fqemumname) { + continue + } + w.AddEnum(enum) + } + str, err := w.Convert(nil) + if err != nil { + return nil, err + } + thisPackageCode += str + models[packageName] = thisPackageCode + } + // Sort the package names first to make the output deterministic sortedPackageNames := make([]string, 0) for packageName := range models { @@ -146,7 +200,6 @@ func (b *Bindings) GenerateModels() ([]byte, error) { } func (b *Bindings) WriteModels(modelsDir string) error { - modelsData, err := b.GenerateModels() if err != nil { return err @@ -157,7 +210,7 @@ func (b *Bindings) WriteModels(modelsDir string) error { } filename := filepath.Join(modelsDir, "models.ts") - err = os.WriteFile(filename, modelsData, 0755) + err = os.WriteFile(filename, modelsData, 0o755) if err != nil { return err } @@ -165,6 +218,39 @@ func (b *Bindings) WriteModels(modelsDir string) error { return nil } +func (b *Bindings) AddEnumToGenerateTS(e interface{}) { + enumType := reflect.TypeOf(e) + + var packageName string + var enumName string + // enums should be represented as array of all possible values + if hasElements(enumType) { + enum := enumType.Elem() + // simple enum represented by struct with Value/TSName fields + if enum.Kind() == reflect.Struct { + _, tsNamePresented := enum.FieldByName("TSName") + enumT, valuePresented := enum.FieldByName("Value") + if tsNamePresented && valuePresented { + packageName = getPackageName(enumT.Type.String()) + enumName = enumT.Type.Name() + } else { + return + } + // otherwise expecting implementation with TSName() https://github.com/tkrajina/typescriptify-golang-structs#enums-with-tsname + } else { + packageName = getPackageName(enumType.Elem().String()) + enumName = enumType.Elem().Name() + } + if b.enumsToGenerateTS[packageName] == nil { + b.enumsToGenerateTS[packageName] = make(map[string]interface{}) + } + if b.enumsToGenerateTS[packageName][enumName] != nil { + return + } + b.enumsToGenerateTS[packageName][enumName] = e + } +} + func (b *Bindings) AddStructToGenerateTS(packageName string, structName string, s interface{}) { if b.structsToGenerateTS[packageName] == nil { b.structsToGenerateTS[packageName] = make(map[string]interface{}) @@ -233,6 +319,13 @@ func (b *Bindings) SetTsSuffix(postfix string) *Bindings { return b } +func (b *Bindings) SetOutputType(outputType string) *Bindings { + if outputType == "interfaces" { + b.tsInterface = true + } + return b +} + func (b *Bindings) getAllStructNames() *slicer.StringSlicer { var result slicer.StringSlicer for packageName, structsToGenerate := range b.structsToGenerateTS { @@ -243,6 +336,16 @@ func (b *Bindings) getAllStructNames() *slicer.StringSlicer { return &result } +func (b *Bindings) getAllEnumNames() *slicer.StringSlicer { + var result slicer.StringSlicer + for packageName, enumsToGenerate := range b.enumsToGenerateTS { + for enumName := range enumsToGenerate { + result.Add(packageName + "." + enumName) + } + } + return &result +} + func (b *Bindings) hasExportedJSONFields(typeOf reflect.Type) bool { for i := 0; i < typeOf.NumField(); i++ { jsonFieldName := "" diff --git a/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go b/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go index 2309d6da..b37334ec 100644 --- a/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go +++ b/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go @@ -42,7 +42,7 @@ func TestConflictingPackageName(t *testing.T) { // setup testLogger := &logger.Logger{} - b := binding.NewBindings(testLogger, []interface{}{&HandlerTest{}}, []interface{}{}, false) + b := binding.NewBindings(testLogger, []interface{}{&HandlerTest{}}, []interface{}{}, false, []interface{}{}) // then err := b.GenerateGoBindings(generationDir) diff --git a/v2/internal/binding/binding_test/binding_importedenum_test.go b/v2/internal/binding/binding_test/binding_importedenum_test.go new file mode 100644 index 00000000..5b5b4419 --- /dev/null +++ b/v2/internal/binding/binding_test/binding_importedenum_test.go @@ -0,0 +1,50 @@ +package binding_test + +import "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import" + +type ImportedEnumStruct struct { + EnumValue binding_test_import.ImportedEnum `json:"EnumValue"` +} + +func (s ImportedEnumStruct) Get() ImportedEnumStruct { + return s +} + +var ImportedEnumTest = BindingTest{ + name: "ImportedEnum", + structs: []interface{}{ + &ImportedEnumStruct{}, + }, + enums: []interface{}{ + binding_test_import.AllImportedEnumValues, + }, + exemptions: nil, + shouldError: false, + want: `export namespace binding_test { + + export class ImportedEnumStruct { + EnumValue: binding_test_import.ImportedEnum; + + static createFrom(source: any = {}) { + return new ImportedEnumStruct(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.EnumValue = source["EnumValue"]; + } + } + + } + + export namespace binding_test_import { + + export enum ImportedEnum { + Value1 = "value1", + Value2 = "value2", + Value3 = "value3", + } + + } +`, +} diff --git a/v2/internal/binding/binding_test/binding_returned_promises_test.go b/v2/internal/binding/binding_test/binding_returned_promises_test.go index 837d5fad..94941d0a 100644 --- a/v2/internal/binding/binding_test/binding_returned_promises_test.go +++ b/v2/internal/binding/binding_test/binding_returned_promises_test.go @@ -59,7 +59,7 @@ func TestPromises(t *testing.T) { // setup testLogger := &logger.Logger{} - b := binding.NewBindings(testLogger, []interface{}{&PromisesTest{}}, []interface{}{}, false) + b := binding.NewBindings(testLogger, []interface{}{&PromisesTest{}}, []interface{}{}, false, []interface{}{}) // then err := b.GenerateGoBindings(generationDir) diff --git a/v2/internal/binding/binding_test/binding_test.go b/v2/internal/binding/binding_test/binding_test.go index c2e35191..6d643b92 100644 --- a/v2/internal/binding/binding_test/binding_test.go +++ b/v2/internal/binding/binding_test/binding_test.go @@ -13,6 +13,7 @@ import ( type BindingTest struct { name string structs []interface{} + enums []interface{} exemptions []interface{} want string shouldError bool @@ -20,8 +21,9 @@ type BindingTest struct { } type TsGenerationOptionsTest struct { - TsPrefix string - TsSuffix string + TsPrefix string + TsSuffix string + TsOutputType string } func TestBindings_GenerateModels(t *testing.T) { @@ -31,12 +33,17 @@ func TestBindings_GenerateModels(t *testing.T) { ImportedStructTest, ImportedSliceTest, ImportedMapTest, + ImportedEnumTest, NestedFieldTest, NonStringMapKeyTest, SingleFieldTest, MultistructTest, EmptyStructTest, GeneratedJsEntityTest, + GeneratedJsEntityWithIntEnumTest, + GeneratedJsEntityWithStringEnumTest, + GeneratedJsEntityWithEnumTsName, + GeneratedJsEntityWithNestedStructInterfacesTest, AnonymousSubStructTest, AnonymousSubStructMultiLevelTest, GeneratedJsEntityWithNestedStructTest, @@ -46,13 +53,14 @@ func TestBindings_GenerateModels(t *testing.T) { testLogger := &logger.Logger{} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := binding.NewBindings(testLogger, tt.structs, tt.exemptions, false) + b := binding.NewBindings(testLogger, tt.structs, tt.exemptions, false, tt.enums) for _, s := range tt.structs { err := b.Add(s) require.NoError(t, err) } b.SetTsPrefix(tt.TsPrefix) b.SetTsSuffix(tt.TsSuffix) + b.SetOutputType(tt.TsOutputType) got, err := b.GenerateModels() if (err != nil) != tt.shouldError { t.Errorf("GenerateModels() error = %v, shouldError %v", err, tt.shouldError) diff --git a/v2/internal/binding/binding_test/binding_test_import/binding_test_import.go b/v2/internal/binding/binding_test/binding_test_import/binding_test_import.go index 6b99d43b..e7080c69 100644 --- a/v2/internal/binding/binding_test/binding_test_import/binding_test_import.go +++ b/v2/internal/binding/binding_test/binding_test_import/binding_test_import.go @@ -13,3 +13,20 @@ type ASliceWrapper struct { type AMapWrapper struct { AMap map[string]binding_test_nestedimport.A `json:"AMap"` } + +type ImportedEnum string + +const ( + ImportedEnumValue1 ImportedEnum = "value1" + ImportedEnumValue2 ImportedEnum = "value2" + ImportedEnumValue3 ImportedEnum = "value3" +) + +var AllImportedEnumValues = []struct { + Value ImportedEnum + TSName string +}{ + {ImportedEnumValue1, "Value1"}, + {ImportedEnumValue2, "Value2"}, + {ImportedEnumValue3, "Value3"}, +} diff --git a/v2/internal/binding/binding_test/binding_tsgeneration_test.go b/v2/internal/binding/binding_test/binding_tsgeneration_test.go index d2c5349c..b627772f 100644 --- a/v2/internal/binding/binding_test/binding_tsgeneration_test.go +++ b/v2/internal/binding/binding_test/binding_tsgeneration_test.go @@ -275,3 +275,235 @@ export namespace binding_test { `, } + +type IntEnum int + +const ( + IntEnumValue1 IntEnum = iota + IntEnumValue2 + IntEnumValue3 +) + +var AllIntEnumValues = []struct { + Value IntEnum + TSName string +}{ + {IntEnumValue1, "Value1"}, + {IntEnumValue2, "Value2"}, + {IntEnumValue3, "Value3"}, +} + +type EntityWithIntEnum struct { + Name string `json:"name"` + Enum IntEnum `json:"enum"` +} + +func (e EntityWithIntEnum) Get() EntityWithIntEnum { + return e +} + +var GeneratedJsEntityWithIntEnumTest = BindingTest{ + name: "GeneratedJsEntityWithIntEnumTest", + structs: []interface{}{ + &EntityWithIntEnum{}, + }, + enums: []interface{}{ + AllIntEnumValues, + }, + exemptions: nil, + shouldError: false, + TsGenerationOptionsTest: TsGenerationOptionsTest{ + TsPrefix: "MY_PREFIX_", + TsSuffix: "_MY_SUFFIX", + }, + want: `export namespace binding_test { + + export enum MY_PREFIX_IntEnum_MY_SUFFIX { + Value1 = 0, + Value2 = 1, + Value3 = 2, + } + export class MY_PREFIX_EntityWithIntEnum_MY_SUFFIX { + name: string; + enum: MY_PREFIX_IntEnum_MY_SUFFIX; + + static createFrom(source: any = {}) { + return new MY_PREFIX_EntityWithIntEnum_MY_SUFFIX(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.name = source["name"]; + this.enum = source["enum"]; + } + } + + } +`, +} + +type StringEnum string + +const ( + StringEnumValue1 StringEnum = "value1" + StringEnumValue2 StringEnum = "value2" + StringEnumValue3 StringEnum = "value3" +) + +var AllStringEnumValues = []struct { + Value StringEnum + TSName string +}{ + {StringEnumValue1, "Value1"}, + {StringEnumValue2, "Value2"}, + {StringEnumValue3, "Value3"}, +} + +type EntityWithStringEnum struct { + Name string `json:"name"` + Enum StringEnum `json:"enum"` +} + +func (e EntityWithStringEnum) Get() EntityWithStringEnum { + return e +} + +var GeneratedJsEntityWithStringEnumTest = BindingTest{ + name: "GeneratedJsEntityWithStringEnumTest", + structs: []interface{}{ + &EntityWithStringEnum{}, + }, + enums: []interface{}{ + AllStringEnumValues, + }, + exemptions: nil, + shouldError: false, + TsGenerationOptionsTest: TsGenerationOptionsTest{ + TsPrefix: "MY_PREFIX_", + TsSuffix: "_MY_SUFFIX", + }, + want: `export namespace binding_test { + + export enum MY_PREFIX_StringEnum_MY_SUFFIX { + Value1 = "value1", + Value2 = "value2", + Value3 = "value3", + } + export class MY_PREFIX_EntityWithStringEnum_MY_SUFFIX { + name: string; + enum: MY_PREFIX_StringEnum_MY_SUFFIX; + + static createFrom(source: any = {}) { + return new MY_PREFIX_EntityWithStringEnum_MY_SUFFIX(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.name = source["name"]; + this.enum = source["enum"]; + } + } + + } +`, +} + +type EnumWithTsName string + +const ( + EnumWithTsName1 EnumWithTsName = "value1" + EnumWithTsName2 EnumWithTsName = "value2" + EnumWithTsName3 EnumWithTsName = "value3" +) + +var AllEnumWithTsNameValues = []EnumWithTsName{EnumWithTsName1, EnumWithTsName2, EnumWithTsName3} + +func (v EnumWithTsName) TSName() string { + switch v { + case EnumWithTsName1: + return "TsName1" + case EnumWithTsName2: + return "TsName2" + case EnumWithTsName3: + return "TsName3" + default: + return "???" + } +} + +type EntityWithEnumTsName struct { + Name string `json:"name"` + Enum EnumWithTsName `json:"enum"` +} + +func (e EntityWithEnumTsName) Get() EntityWithEnumTsName { + return e +} + +var GeneratedJsEntityWithEnumTsName = BindingTest{ + name: "GeneratedJsEntityWithEnumTsName", + structs: []interface{}{ + &EntityWithEnumTsName{}, + }, + enums: []interface{}{ + AllEnumWithTsNameValues, + }, + exemptions: nil, + shouldError: false, + TsGenerationOptionsTest: TsGenerationOptionsTest{ + TsPrefix: "MY_PREFIX_", + TsSuffix: "_MY_SUFFIX", + }, + want: `export namespace binding_test { + + export enum MY_PREFIX_EnumWithTsName_MY_SUFFIX { + TsName1 = "value1", + TsName2 = "value2", + TsName3 = "value3", + } + export class MY_PREFIX_EntityWithEnumTsName_MY_SUFFIX { + name: string; + enum: MY_PREFIX_EnumWithTsName_MY_SUFFIX; + + static createFrom(source: any = {}) { + return new MY_PREFIX_EntityWithEnumTsName_MY_SUFFIX(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.name = source["name"]; + this.enum = source["enum"]; + } + } + + } +`, +} + +var GeneratedJsEntityWithNestedStructInterfacesTest = BindingTest{ + name: "GeneratedJsEntityWithNestedStructInterfacesTest", + structs: []interface{}{ + &ParentEntity{}, + }, + exemptions: nil, + shouldError: false, + TsGenerationOptionsTest: TsGenerationOptionsTest{ + TsPrefix: "MY_PREFIX_", + TsSuffix: "_MY_SUFFIX", + TsOutputType: "interfaces", + }, + want: `export namespace binding_test { + + export interface MY_PREFIX_ChildEntity_MY_SUFFIX { + name: string; + childProp: number; + } + export interface MY_PREFIX_ParentEntity_MY_SUFFIX { + name: string; + ref: MY_PREFIX_ChildEntity_MY_SUFFIX; + parentProp: string; + } + + } +`, +} diff --git a/v2/internal/binding/binding_test/binding_type_alias_test.go b/v2/internal/binding/binding_test/binding_type_alias_test.go index 8e7c7ca6..498c5976 100644 --- a/v2/internal/binding/binding_test/binding_type_alias_test.go +++ b/v2/internal/binding/binding_test/binding_type_alias_test.go @@ -41,7 +41,7 @@ func TestAliases(t *testing.T) { // setup testLogger := &logger.Logger{} - b := binding.NewBindings(testLogger, []interface{}{&AliasTest{}}, []interface{}{}, false) + b := binding.NewBindings(testLogger, []interface{}{&AliasTest{}}, []interface{}{}, false, []interface{}{}) // then err := b.GenerateGoBindings(generationDir) diff --git a/v2/internal/binding/boundMethod.go b/v2/internal/binding/boundMethod.go index f6ffdb60..c13e2ff3 100644 --- a/v2/internal/binding/boundMethod.go +++ b/v2/internal/binding/boundMethod.go @@ -28,7 +28,6 @@ func (b *BoundMethod) OutputCount() int { // ParseArgs method converts the input json into the types expected by the method func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) { - result := make([]interface{}, b.InputCount()) if len(args) != b.InputCount() { return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Name, b.InputCount()) diff --git a/v2/internal/binding/db.go b/v2/internal/binding/db.go index f2279868..f7b79383 100644 --- a/v2/internal/binding/db.go +++ b/v2/internal/binding/db.go @@ -2,7 +2,6 @@ package binding import ( "encoding/json" - "sort" "sync" "unsafe" ) @@ -17,24 +16,28 @@ type DB struct { methodMap map[string]*BoundMethod // This uses ids to reference bound methods at runtime - obfuscatedMethodMap map[int]*BoundMethod + obfuscatedMethodArray []*ObfuscatedMethod // Lock to ensure sync access to the data lock sync.RWMutex } +type ObfuscatedMethod struct { + method *BoundMethod + methodName string +} + func newDB() *DB { return &DB{ - store: make(map[string]map[string]map[string]*BoundMethod), - methodMap: make(map[string]*BoundMethod), - obfuscatedMethodMap: make(map[int]*BoundMethod), + store: make(map[string]map[string]map[string]*BoundMethod), + methodMap: make(map[string]*BoundMethod), + obfuscatedMethodArray: []*ObfuscatedMethod{}, } } // GetMethodFromStore returns the method for the given package/struct/method names // nil is returned if any one of those does not exist func (d *DB) GetMethodFromStore(packageName string, structName string, methodName string) *BoundMethod { - // Lock the db whilst processing and unlock on return d.lock.RLock() defer d.lock.RUnlock() @@ -53,7 +56,6 @@ func (d *DB) GetMethodFromStore(packageName string, structName string, methodNam // GetMethod returns the method for the given qualified method name // qualifiedMethodName is "packagename.structname.methodname" func (d *DB) GetMethod(qualifiedMethodName string) *BoundMethod { - // Lock the db whilst processing and unlock on return d.lock.RLock() defer d.lock.RUnlock() @@ -67,12 +69,15 @@ func (d *DB) GetObfuscatedMethod(id int) *BoundMethod { d.lock.RLock() defer d.lock.RUnlock() - return d.obfuscatedMethodMap[id] + if len(d.obfuscatedMethodArray) <= id { + return nil + } + + return d.obfuscatedMethodArray[id].method } // AddMethod adds the given method definition to the db using the given qualified path: packageName.structName.methodName func (d *DB) AddMethod(packageName string, structName string, methodName string, methodDefinition *BoundMethod) { - // Lock the db whilst processing and unlock on return d.lock.Lock() defer d.lock.Unlock() @@ -99,12 +104,11 @@ func (d *DB) AddMethod(packageName string, structName string, methodName string, // Store in the methodMap key := packageName + "." + structName + "." + methodName d.methodMap[key] = methodDefinition - + d.obfuscatedMethodArray = append(d.obfuscatedMethodArray, &ObfuscatedMethod{method: methodDefinition, methodName: key}) } // ToJSON converts the method map to JSON func (d *DB) ToJSON() (string, error) { - // Lock the db whilst processing and unlock on return d.lock.RLock() defer d.lock.RUnlock() @@ -120,20 +124,11 @@ func (d *DB) ToJSON() (string, error) { // UpdateObfuscatedCallMap sets up the secure call mappings func (d *DB) UpdateObfuscatedCallMap() map[string]int { + mappings := make(map[string]int) - var mappings = make(map[string]int) - - // Iterate map keys and sort them - keys := make([]string, 0, len(d.methodMap)) - for k := range d.methodMap { - keys = append(keys, k) + for id, k := range d.obfuscatedMethodArray { + mappings[k.methodName] = id } - sort.Strings(keys) - // Iterate sorted keys and add to obfuscated method map - for id, k := range keys { - mappings[k] = id - d.obfuscatedMethodMap[id] = d.methodMap[k] - } return mappings } diff --git a/v2/internal/binding/generate.go b/v2/internal/binding/generate.go index 8416aade..02a0bd29 100644 --- a/v2/internal/binding/generate.go +++ b/v2/internal/binding/generate.go @@ -15,12 +15,14 @@ import ( "github.com/leaanthony/slicer" ) -var mapRegex *regexp.Regexp -var keyPackageIndex int -var keyTypeIndex int -var valueArrayIndex int -var valuePackageIndex int -var valueTypeIndex int +var ( + mapRegex *regexp.Regexp + keyPackageIndex int + keyTypeIndex int + valueArrayIndex int + valuePackageIndex int + valueTypeIndex int +) func init() { mapRegex = regexp.MustCompile(`(?:map\[(?:(?P\w+)\.)?(?P\w+)])?(?P\[])?(?:\*?(?P\w+)\.)?(?P.+)`) @@ -81,9 +83,7 @@ func (b *Bindings) GenerateGoBindings(baseDir string) error { } else { jsoutput.WriteString(fmt.Sprintf(" return window['go']['%s']['%s']['%s'](%s);", packageName, structName, methodName, argsString)) } - jsoutput.WriteString("\n") - jsoutput.WriteString(fmt.Sprintf("}")) - jsoutput.WriteString("\n") + jsoutput.WriteString("\n}\n") // Generate TS tsBody.WriteString(fmt.Sprintf("\nexport function %s(", methodName)) @@ -127,12 +127,12 @@ func (b *Bindings) GenerateGoBindings(baseDir string) error { tsContent.WriteString(tsBody.String()) jsfilename := filepath.Join(packageDir, structName+".js") - err = os.WriteFile(jsfilename, jsoutput.Bytes(), 0755) + err = os.WriteFile(jsfilename, jsoutput.Bytes(), 0o755) if err != nil { return err } tsfilename := filepath.Join(packageDir, structName+".d.ts") - err = os.WriteFile(tsfilename, tsContent.Bytes(), 0755) + err = os.WriteFile(tsfilename, tsContent.Bytes(), 0o755) if err != nil { return err } @@ -186,7 +186,7 @@ func goTypeToJSDocType(input string, importNamespaces *slicer.StringSlicer) stri valueArray := matches[valueArrayIndex] valuePackage := matches[valuePackageIndex] valueType := matches[valueTypeIndex] - //fmt.Printf("input=%s, keyPackage=%s, keyType=%s, valueArray=%s, valuePackage=%s, valueType=%s\n", + // fmt.Printf("input=%s, keyPackage=%s, keyType=%s, valueArray=%s, valuePackage=%s, valueType=%s\n", // input, // keyPackage, // keyType, diff --git a/v2/internal/binding/generate_test.go b/v2/internal/binding/generate_test.go index 565fba31..8d6a833b 100644 --- a/v2/internal/binding/generate_test.go +++ b/v2/internal/binding/generate_test.go @@ -25,7 +25,7 @@ type B struct { func TestNestedStruct(t *testing.T) { bind := &BindForTest{} - testBindings := NewBindings(logger.New(nil), []interface{}{bind}, []interface{}{}, false) + testBindings := NewBindings(logger.New(nil), []interface{}{bind}, []interface{}{}, false, []interface{}{}) namesStrSlicer := testBindings.getAllStructNames() names := []string{} diff --git a/v2/internal/binding/reflect.go b/v2/internal/binding/reflect.go old mode 100755 new mode 100644 index 66a9cf7b..263af29d --- a/v2/internal/binding/reflect.go +++ b/v2/internal/binding/reflect.go @@ -25,7 +25,6 @@ func isStruct(value interface{}) bool { } func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) { - // Create result placeholder var result []*BoundMethod @@ -169,7 +168,6 @@ func getPackageName(in string) string { func getSplitReturn(in string) (string, string) { result := strings.Split(in, ".") return result[0], result[1] - } func hasElements(typ reflect.Type) bool { diff --git a/v2/internal/frontend/calls.go b/v2/internal/frontend/calls.go index 3983c24b..5401106b 100644 --- a/v2/internal/frontend/calls.go +++ b/v2/internal/frontend/calls.go @@ -1,5 +1,5 @@ package frontend type Calls interface { - Callback(string) + Callback(message string) } diff --git a/v2/internal/frontend/desktop/darwin/AppDelegate.h b/v2/internal/frontend/desktop/darwin/AppDelegate.h index e2dd841c..a8d10f64 100644 --- a/v2/internal/frontend/desktop/darwin/AppDelegate.h +++ b/v2/internal/frontend/desktop/darwin/AppDelegate.h @@ -11,13 +11,23 @@ #import #import "WailsContext.h" -@interface AppDelegate : NSResponder +@interface AppDelegate : NSResponder @property bool alwaysOnTop; @property bool startHidden; +@property (retain) NSString* singleInstanceUniqueId; +@property bool singleInstanceLockEnabled; @property bool startFullscreen; @property (retain) WailsWindow* mainWindow; @end +extern void HandleOpenFile(char *); + +extern void HandleSecondInstanceData(char * message); + +void SendDataToFirstInstance(char * singleInstanceUniqueId, char * text); + +char* GetMacOsNativeTempDir(); + #endif /* AppDelegate_h */ diff --git a/v2/internal/frontend/desktop/darwin/AppDelegate.m b/v2/internal/frontend/desktop/darwin/AppDelegate.m index b66979e7..18ecfbcf 100644 --- a/v2/internal/frontend/desktop/darwin/AppDelegate.m +++ b/v2/internal/frontend/desktop/darwin/AppDelegate.m @@ -11,9 +11,17 @@ #import "AppDelegate.h" @implementation AppDelegate -- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender { - return NO; +-(BOOL)application:(NSApplication *)sender openFile:(NSString *)filename +{ + const char* utf8FileName = filename.UTF8String; + HandleOpenFile((char*)utf8FileName); + return YES; } + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender { + return NO; +} + - (void)applicationWillFinishLaunching:(NSNotification *)aNotification { [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; if (self.alwaysOnTop) { @@ -32,6 +40,37 @@ [self.mainWindow setCollectionBehavior:behaviour]; [self.mainWindow toggleFullScreen:nil]; } + + if ( self.singleInstanceLockEnabled ) { + [[NSDistributedNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleSecondInstanceNotification:) name:self.singleInstanceUniqueId object:nil]; + } +} + +void SendDataToFirstInstance(char * singleInstanceUniqueId, char * message) { + // we pass message in object because otherwise sandboxing will prevent us from sending it https://developer.apple.com/forums/thread/129437 + NSString * myString = [NSString stringWithUTF8String:message]; + [[NSDistributedNotificationCenter defaultCenter] + postNotificationName:[NSString stringWithUTF8String:singleInstanceUniqueId] + object:(__bridge const void *)(myString) + userInfo:nil + deliverImmediately:YES]; +} + +char* GetMacOsNativeTempDir() { + NSString *tempDir = NSTemporaryDirectory(); + char *copy = strdup([tempDir UTF8String]); + + return copy; +} + +- (void)handleSecondInstanceNotification:(NSNotification *)note; +{ + if (note.object != nil) { + NSString * message = (__bridge NSString *)note.object; + const char* utf8Message = message.UTF8String; + HandleSecondInstanceData((char*)utf8Message); + } } - (void)dealloc { diff --git a/v2/internal/frontend/desktop/darwin/Application.h b/v2/internal/frontend/desktop/darwin/Application.h index 458016cc..eb679f3b 100644 --- a/v2/internal/frontend/desktop/darwin/Application.h +++ b/v2/internal/frontend/desktop/darwin/Application.h @@ -17,7 +17,7 @@ #define WindowStartsMinimised 2 #define WindowStartsFullscreen 3 -WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int devtoolsEnabled, int defaultContextMenuEnabled, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled, struct Preferences preferences); +WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int devtoolsEnabled, int defaultContextMenuEnabled, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled, struct Preferences preferences, int singleInstanceEnabled, const char* singleInstanceUniqueId); void Run(void*, const char* url); void SetTitle(void* ctx, const char *title); diff --git a/v2/internal/frontend/desktop/darwin/Application.m b/v2/internal/frontend/desktop/darwin/Application.m index e0c05212..c428b4ce 100644 --- a/v2/internal/frontend/desktop/darwin/Application.m +++ b/v2/internal/frontend/desktop/darwin/Application.m @@ -14,15 +14,15 @@ #import "WailsMenu.h" #import "WailsMenuItem.h" -WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int devtoolsEnabled, int defaultContextMenuEnabled, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled, struct Preferences preferences) { - +WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int devtoolsEnabled, int defaultContextMenuEnabled, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled, struct Preferences preferences, int singleInstanceLockEnabled, const char* singleInstanceUniqueId) { + [NSApplication sharedApplication]; WailsContext *result = [WailsContext new]; result.devtoolsEnabled = devtoolsEnabled; result.defaultContextMenuEnabled = defaultContextMenuEnabled; - + if ( windowStartState == WindowStartsFullscreen ) { fullscreen = 1; } @@ -30,7 +30,7 @@ WailsContext* Create(const char* title, int width, int height, int frameless, in [result CreateWindow:width :height :frameless :resizable :fullscreen :fullSizeContent :hideTitleBar :titlebarAppearsTransparent :hideTitle :useToolbar :hideToolbarSeparator :webviewIsTransparent :hideWindowOnClose :safeInit(appearance) :windowIsTranslucent :minWidth :minHeight :maxWidth :maxHeight :fraudulentWebsiteWarningEnabled :preferences]; [result SetTitle:safeInit(title)]; [result Center]; - + switch( windowStartState ) { case WindowStartsMaximised: [result.mainWindow zoom:nil]; @@ -43,14 +43,19 @@ WailsContext* Create(const char* title, int width, int height, int frameless, in if ( startsHidden == 1 ) { result.startHidden = true; } - + if ( fullscreen == 1 ) { result.startFullscreen = true; } - + + if ( singleInstanceLockEnabled == 1 ) { + result.singleInstanceLockEnabled = true; + result.singleInstanceUniqueId = safeInit(singleInstanceUniqueId); + } + result.alwaysOnTop = alwaysOnTop; result.hideOnClose = hideWindowOnClose; - + return result; } @@ -181,7 +186,7 @@ const char* GetPosition(void *inctx) { NSString *result = [NSString stringWithFormat:@"%d,%d",x,y]; return [result UTF8String]; } - + const bool IsFullScreen(void *inctx) { WailsContext *ctx = (__bridge WailsContext*) inctx; return [ctx IsFullScreen]; @@ -249,7 +254,7 @@ NSString* safeInit(const char* input) { void MessageDialog(void *inctx, const char* dialogType, const char* title, const char* message, const char* button1, const char* button2, const char* button3, const char* button4, const char* defaultButton, const char* cancelButton, void* iconData, int iconDataLength) { WailsContext *ctx = (__bridge WailsContext*) inctx; - + NSString *_dialogType = safeInit(dialogType); NSString *_title = safeInit(title); NSString *_message = safeInit(message); @@ -259,33 +264,33 @@ void MessageDialog(void *inctx, const char* dialogType, const char* title, const NSString *_button4 = safeInit(button4); NSString *_defaultButton = safeInit(defaultButton); NSString *_cancelButton = safeInit(cancelButton); - + ON_MAIN_THREAD( [ctx MessageDialog:_dialogType :_title :_message :_button1 :_button2 :_button3 :_button4 :_defaultButton :_cancelButton :iconData :iconDataLength]; ) } void OpenFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int allowDirectories, int allowFiles, int canCreateDirectories, int treatPackagesAsDirectories, int resolveAliases, int showHiddenFiles, int allowMultipleSelection, const char* filters) { - + WailsContext *ctx = (__bridge WailsContext*) inctx; NSString *_title = safeInit(title); NSString *_defaultFilename = safeInit(defaultFilename); NSString *_defaultDirectory = safeInit(defaultDirectory); NSString *_filters = safeInit(filters); - + ON_MAIN_THREAD( [ctx OpenFileDialog:_title :_defaultFilename :_defaultDirectory :allowDirectories :allowFiles :canCreateDirectories :treatPackagesAsDirectories :resolveAliases :showHiddenFiles :allowMultipleSelection :_filters]; ) } void SaveFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int canCreateDirectories, int treatPackagesAsDirectories, int showHiddenFiles, const char* filters) { - + WailsContext *ctx = (__bridge WailsContext*) inctx; NSString *_title = safeInit(title); NSString *_defaultFilename = safeInit(defaultFilename); NSString *_defaultDirectory = safeInit(defaultDirectory); NSString *_filters = safeInit(filters); - + ON_MAIN_THREAD( [ctx SaveFileDialog:_title :_defaultFilename :_defaultDirectory :canCreateDirectories :treatPackagesAsDirectories :showHiddenFiles :_filters]; ) @@ -367,6 +372,8 @@ void Run(void *inctx, const char* url) { delegate.mainWindow = ctx.mainWindow; delegate.alwaysOnTop = ctx.alwaysOnTop; delegate.startHidden = ctx.startHidden; + delegate.singleInstanceLockEnabled = ctx.singleInstanceLockEnabled; + delegate.singleInstanceUniqueId = ctx.singleInstanceUniqueId; delegate.startFullscreen = ctx.startFullscreen; NSString *_url = safeInit(url); @@ -389,13 +396,13 @@ void ReleaseContext(void *inctx) { // Credit: https://stackoverflow.com/q/33319295 void WindowPrint(void *inctx) { - // Check if macOS 11.0 or newer +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 if (@available(macOS 11.0, *)) { ON_MAIN_THREAD( WailsContext *ctx = (__bridge WailsContext*) inctx; WKWebView* webView = ctx.webview; - // I think this should be exposed as a config + // I think this should be exposed as a config // It directly affects the printed output/PDF NSPrintInfo *pInfo = [NSPrintInfo sharedPrintInfo]; pInfo.horizontalPagination = NSPrintingPaginationModeAutomatic; @@ -417,4 +424,5 @@ void WindowPrint(void *inctx) { [po runOperationModalForWindow:ctx.mainWindow delegate:ctx.mainWindow.delegate didRunSelector:nil contextInfo:nil]; ) } -} \ No newline at end of file +#endif +} diff --git a/v2/internal/frontend/desktop/darwin/CustomProtocol.h b/v2/internal/frontend/desktop/darwin/CustomProtocol.h new file mode 100644 index 00000000..da0e7079 --- /dev/null +++ b/v2/internal/frontend/desktop/darwin/CustomProtocol.h @@ -0,0 +1,14 @@ +#ifndef CustomProtocol_h +#define CustomProtocol_h + +#import + +extern void HandleCustomProtocol(char*); + +@interface CustomProtocolSchemeHandler : NSObject ++ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent; +@end + +void StartCustomProtocolHandler(void); + +#endif /* CustomProtocol_h */ diff --git a/v2/internal/frontend/desktop/darwin/CustomProtocol.m b/v2/internal/frontend/desktop/darwin/CustomProtocol.m new file mode 100644 index 00000000..7365e4f5 --- /dev/null +++ b/v2/internal/frontend/desktop/darwin/CustomProtocol.m @@ -0,0 +1,20 @@ +#include "CustomProtocol.h" + +@implementation CustomProtocolSchemeHandler ++ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { + [event paramDescriptorForKeyword:keyDirectObject]; + + NSString *urlStr = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; + + HandleCustomProtocol((char*)[[[event paramDescriptorForKeyword:keyDirectObject] stringValue] UTF8String]); +} +@end + +void StartCustomProtocolHandler(void) { + NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager]; + + [appleEventManager setEventHandler:[CustomProtocolSchemeHandler class] + andSelector:@selector(handleGetURLEvent:withReplyEvent:) + forEventClass:kInternetEventClass + andEventID: kAEGetURL]; +} diff --git a/v2/internal/frontend/desktop/darwin/WailsContext.h b/v2/internal/frontend/desktop/darwin/WailsContext.h index fe66f354..ab5e5c2a 100644 --- a/v2/internal/frontend/desktop/darwin/WailsContext.h +++ b/v2/internal/frontend/desktop/darwin/WailsContext.h @@ -40,6 +40,9 @@ @property bool startHidden; @property bool startFullscreen; +@property bool singleInstanceLockEnabled; +@property (retain) NSString* singleInstanceUniqueId; + @property (retain) NSEvent* mouseEvent; @property bool alwaysOnTop; @@ -58,6 +61,7 @@ struct Preferences { bool *tabFocusesLinks; bool *textInteractionEnabled; + bool *fullscreenEnabled; }; - (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString *)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled :(struct Preferences)preferences; diff --git a/v2/internal/frontend/desktop/darwin/WailsContext.m b/v2/internal/frontend/desktop/darwin/WailsContext.m index 1c84a576..fd15465a 100644 --- a/v2/internal/frontend/desktop/darwin/WailsContext.m +++ b/v2/internal/frontend/desktop/darwin/WailsContext.m @@ -219,17 +219,27 @@ typedef void (^schemeTaskCaller)(id); config.preferences.tabFocusesLinks = *preferences.tabFocusesLinks; } +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110300 if (@available(macOS 11.3, *)) { if (preferences.textInteractionEnabled != NULL) { config.preferences.textInteractionEnabled = *preferences.textInteractionEnabled; } } - -// [config.preferences setValue:[NSNumber numberWithBool:true] forKey:@"developerExtrasEnabled"]; +#endif +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 120300 + if (@available(macOS 12.3, *)) { + if (preferences.fullscreenEnabled != NULL) { + config.preferences.elementFullscreenEnabled = *preferences.fullscreenEnabled; + } + } +#endif + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 if (@available(macOS 10.15, *)) { config.preferences.fraudulentWebsiteWarningEnabled = fraudulentWebsiteWarningEnabled; } +#endif WKUserContentController* userContentController = [WKUserContentController new]; [userContentController addScriptMessageHandler:self name:@"external"]; @@ -425,10 +435,11 @@ typedef void (^schemeTaskCaller)(id); NSOpenPanel *openPanel = [NSOpenPanel openPanel]; openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 if (@available(macOS 10.14, *)) { openPanel.canChooseDirectories = parameters.allowsDirectories; } - +#endif [openPanel beginSheetModalForWindow:webView.window completionHandler:^(NSInteger result) { @@ -552,14 +563,18 @@ typedef void (^schemeTaskCaller)(id); #ifdef USE_NEW_FILTERS NSMutableArray *contentTypes = [[NSMutableArray new] autorelease]; for (NSString *filter in filterList) { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 if (@available(macOS 11.0, *)) { UTType *t = [UTType typeWithFilenameExtension:filter]; [contentTypes addObject:t]; } +#endif } +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 if (@available(macOS 11.0, *)) { [dialog setAllowedContentTypes:contentTypes]; } +#endif #else [dialog setAllowedFileTypes:filterList]; #endif @@ -632,17 +647,21 @@ typedef void (^schemeTaskCaller)(id); #ifdef USE_NEW_FILTERS NSMutableArray *contentTypes = [[NSMutableArray new] autorelease]; for (NSString *filter in filterList) { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 if (@available(macOS 11.0, *)) { UTType *t = [UTType typeWithFilenameExtension:filter]; [contentTypes addObject:t]; } +#endif } if( contentTypes.count == 0) { [dialog setAllowsOtherFileTypes:true]; } else { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 if (@available(macOS 11.0, *)) { [dialog setAllowedContentTypes:contentTypes]; } +#endif } #else diff --git a/v2/internal/frontend/desktop/darwin/callbacks.go b/v2/internal/frontend/desktop/darwin/callbacks.go index 7d930a2f..ab0d18e4 100644 --- a/v2/internal/frontend/desktop/darwin/callbacks.go +++ b/v2/internal/frontend/desktop/darwin/callbacks.go @@ -12,6 +12,7 @@ package darwin #include */ import "C" + import ( "errors" "strconv" @@ -20,7 +21,6 @@ import ( ) func (f *Frontend) handleCallback(menuItemID uint) error { - menuItem := getMenuItemForID(menuItemID) if menuItem == nil { return errors.New("unknown menuItem ID: " + strconv.Itoa(int(menuItemID))) diff --git a/v2/internal/frontend/desktop/darwin/clipboard.go b/v2/internal/frontend/desktop/darwin/clipboard.go index eea6c79a..c40ba877 100644 --- a/v2/internal/frontend/desktop/darwin/clipboard.go +++ b/v2/internal/frontend/desktop/darwin/clipboard.go @@ -16,7 +16,6 @@ func (f *Frontend) ClipboardGetText() (string, error) { } func (f *Frontend) ClipboardSetText(text string) error { - copyCmd := exec.Command("pbcopy") in, err := copyCmd.StdinPipe() if err != nil { diff --git a/v2/internal/frontend/desktop/darwin/dialog.go b/v2/internal/frontend/desktop/darwin/dialog.go index c6be559c..66bb2f13 100644 --- a/v2/internal/frontend/desktop/darwin/dialog.go +++ b/v2/internal/frontend/desktop/darwin/dialog.go @@ -11,6 +11,7 @@ package darwin #import "WailsContext.h" */ import "C" + import ( "encoding/json" "fmt" @@ -23,10 +24,12 @@ import ( ) // Obj-C dialog methods send the response to this channel -var messageDialogResponse = make(chan int) -var openFileDialogResponse = make(chan string) -var saveFileDialogResponse = make(chan string) -var dialogLock sync.Mutex +var ( + messageDialogResponse = make(chan int) + openFileDialogResponse = make(chan string) + saveFileDialogResponse = make(chan string) + dialogLock sync.Mutex +) // OpenDirectoryDialog prompts the user to select a directory func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (string, error) { @@ -74,7 +77,7 @@ func (f *Frontend) openDialog(options *frontend.OpenDialogOptions, multiple bool filters := filterStrings.Join(";") C.OpenFileDialog(f.mainWindow.context, title, defaultFilename, defaultDirectory, allowDirectories, allowFiles, canCreateDirectories, treatPackagesAsDirectories, resolveAliases, showHiddenFiles, allowMultipleFileSelection, c.String(filters)) - var result = <-openFileDialogResponse + result := <-openFileDialogResponse var parsedResults []string err := json.Unmarshal([]byte(result), &parsedResults) @@ -130,7 +133,7 @@ func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, e filters := filterStrings.Join(";") C.SaveFileDialog(f.mainWindow.context, title, defaultFilename, defaultDirectory, canCreateDirectories, treatPackagesAsDirectories, showHiddenFiles, c.String(filters)) - var result = <-saveFileDialogResponse + result := <-saveFileDialogResponse return result, nil } @@ -165,7 +168,7 @@ func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, C.MessageDialog(f.mainWindow.context, dialogType, title, message, buttons[0], buttons[1], buttons[2], buttons[3], defaultButton, cancelButton, iconData, iconDataLength) - var result = <-messageDialogResponse + result := <-messageDialogResponse selectedC := buttons[result] var selected string diff --git a/v2/internal/frontend/desktop/darwin/frontend.go b/v2/internal/frontend/desktop/darwin/frontend.go index a6a0808f..bc8a4c64 100644 --- a/v2/internal/frontend/desktop/darwin/frontend.go +++ b/v2/internal/frontend/desktop/darwin/frontend.go @@ -8,11 +8,13 @@ package darwin #cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit #import #import "Application.h" +#import "CustomProtocol.h" #import "WailsContext.h" #include */ import "C" + import ( "context" "encoding/json" @@ -35,12 +37,16 @@ import ( const startURL = "wails://wails/" -var messageBuffer = make(chan string, 100) -var requestBuffer = make(chan webview.Request, 100) -var callbackBuffer = make(chan uint, 10) +var ( + messageBuffer = make(chan string, 100) + requestBuffer = make(chan webview.Request, 100) + callbackBuffer = make(chan uint, 10) + openFilepathBuffer = make(chan string, 100) + openUrlBuffer = make(chan string, 100) + secondInstanceBuffer = make(chan options.SecondInstanceData, 1) +) type Frontend struct { - // Context ctx context.Context @@ -77,6 +83,9 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. } result.startURL, _ = url.Parse(startURL) + // this should be initialized as early as possible to handle first instance launch + C.StartCustomProtocolHandler() + if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil { result.startURL = _starturl } else { @@ -107,20 +116,46 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. go result.startMessageProcessor() go result.startCallbackProcessor() + go result.startFileOpenProcessor() + go result.startUrlOpenProcessor() + go result.startSecondInstanceProcessor() return result } +func (f *Frontend) startFileOpenProcessor() { + for filePath := range openFilepathBuffer { + f.ProcessOpenFileEvent(filePath) + } +} + +func (f *Frontend) startUrlOpenProcessor() { + for url := range openUrlBuffer { + f.ProcessOpenUrlEvent(url) + } +} + +func (f *Frontend) startSecondInstanceProcessor() { + for secondInstanceData := range secondInstanceBuffer { + if f.frontendOptions.SingleInstanceLock != nil && + f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil { + f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData) + } + } +} + func (f *Frontend) startMessageProcessor() { for message := range messageBuffer { f.processMessage(message) } } + func (f *Frontend) startRequestProcessor() { for request := range requestBuffer { f.assets.ServeWebViewRequest(request) } } + func (f *Frontend) startCallbackProcessor() { for callback := range callbackBuffer { err := f.handleCallback(callback) @@ -139,22 +174,23 @@ func (f *Frontend) WindowReloadApp() { } func (f *Frontend) WindowSetSystemDefaultTheme() { - return } func (f *Frontend) WindowSetLightTheme() { - return } func (f *Frontend) WindowSetDarkTheme() { - return } func (f *Frontend) Run(ctx context.Context) error { f.ctx = ctx - var _debug = ctx.Value("debug") - var _devtoolsEnabled = ctx.Value("devtoolsEnabled") + if f.frontendOptions.SingleInstanceLock != nil { + SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId) + } + + _debug := ctx.Value("debug") + _devtoolsEnabled := ctx.Value("devtoolsEnabled") if _debug != nil { f.debug = _debug.(bool) @@ -179,6 +215,7 @@ func (f *Frontend) Run(ctx context.Context) error { func (f *Frontend) WindowCenter() { f.mainWindow.Center() } + func (f *Frontend) WindowSetAlwaysOnTop(onTop bool) { f.mainWindow.SetAlwaysOnTop(onTop) } @@ -186,6 +223,7 @@ func (f *Frontend) WindowSetAlwaysOnTop(onTop bool) { func (f *Frontend) WindowSetPosition(x, y int) { f.mainWindow.SetPosition(x, y) } + func (f *Frontend) WindowGetPosition() (int, int) { return f.mainWindow.GetPosition() } @@ -217,6 +255,7 @@ func (f *Frontend) WindowShow() { func (f *Frontend) WindowHide() { f.mainWindow.Hide() } + func (f *Frontend) Show() { f.mainWindow.ShowApplication() } @@ -224,18 +263,23 @@ func (f *Frontend) Show() { func (f *Frontend) Hide() { f.mainWindow.HideApplication() } + func (f *Frontend) WindowMaximise() { f.mainWindow.Maximise() } + func (f *Frontend) WindowToggleMaximise() { f.mainWindow.ToggleMaximise() } + func (f *Frontend) WindowUnmaximise() { f.mainWindow.UnMaximise() } + func (f *Frontend) WindowMinimise() { f.mainWindow.Minimise() } + func (f *Frontend) WindowUnminimise() { f.mainWindow.UnMinimise() } @@ -243,6 +287,7 @@ func (f *Frontend) WindowUnminimise() { func (f *Frontend) WindowSetMinSize(width int, height int) { f.mainWindow.SetMinSize(width, height) } + func (f *Frontend) WindowSetMaxSize(width int, height int) { f.mainWindow.SetMaxSize(width, height) } @@ -309,7 +354,6 @@ func (f *Frontend) Notify(name string, data ...interface{}) { } func (f *Frontend) processMessage(message string) { - if message == "DomReady" { if f.frontendOptions.OnDomReady != nil { f.frontendOptions.OnDomReady(f.ctx) @@ -352,7 +396,18 @@ func (f *Frontend) processMessage(message string) { f.logger.Info("Unknown message returned from dispatcher: %+v", result) } }() +} +func (f *Frontend) ProcessOpenFileEvent(filePath string) { + if f.frontendOptions.Mac != nil && f.frontendOptions.Mac.OnFileOpen != nil { + f.frontendOptions.Mac.OnFileOpen(filePath) + } +} + +func (f *Frontend) ProcessOpenUrlEvent(url string) { + if f.frontendOptions.Mac != nil && f.frontendOptions.Mac.OnUrlOpen != nil { + f.frontendOptions.Mac.OnUrlOpen(url) + } } func (f *Frontend) Callback(message string) { @@ -398,3 +453,15 @@ func processCallback(callbackID uint) { func processURLRequest(_ unsafe.Pointer, wkURLSchemeTask unsafe.Pointer) { requestBuffer <- webview.NewRequest(wkURLSchemeTask) } + +//export HandleOpenFile +func HandleOpenFile(filePath *C.char) { + goFilepath := C.GoString(filePath) + openFilepathBuffer <- goFilepath +} + +//export HandleCustomProtocol +func HandleCustomProtocol(url *C.char) { + goUrl := C.GoString(url) + openUrlBuffer <- goUrl +} diff --git a/v2/internal/frontend/desktop/darwin/inspector.go b/v2/internal/frontend/desktop/darwin/inspector.go index 8499745d..dc3f0896 100644 --- a/v2/internal/frontend/desktop/darwin/inspector.go +++ b/v2/internal/frontend/desktop/darwin/inspector.go @@ -7,5 +7,4 @@ import ( ) func showInspector(_ unsafe.Pointer) { - } diff --git a/v2/internal/frontend/desktop/darwin/inspector_dev.go b/v2/internal/frontend/desktop/darwin/inspector_dev.go index f3520cb3..e79b9c3e 100644 --- a/v2/internal/frontend/desktop/darwin/inspector_dev.go +++ b/v2/internal/frontend/desktop/darwin/inspector_dev.go @@ -23,6 +23,7 @@ extern void processMessage(const char *message); @end void showInspector(void *inctx) { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 120000 ON_MAIN_THREAD( if (@available(macOS 12.0, *)) { WailsContext *ctx = (__bridge WailsContext*) inctx; @@ -47,7 +48,7 @@ void showInspector(void *inctx) { NSLog(@"Opening the inspector needs at least MacOS 12"); } ); - +#endif } void setupF12hotkey() { diff --git a/v2/internal/frontend/desktop/darwin/menu.go b/v2/internal/frontend/desktop/darwin/menu.go index 08090f89..24dbe320 100644 --- a/v2/internal/frontend/desktop/darwin/menu.go +++ b/v2/internal/frontend/desktop/darwin/menu.go @@ -13,6 +13,7 @@ package darwin #include */ import "C" + import ( "unsafe" @@ -122,7 +123,6 @@ func processMenuItem(parent *NSMenu, menuItem *menu.MenuItem) *MenuItem { } return parent.AddMenuItem(menuItem) - } func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) { diff --git a/v2/internal/frontend/desktop/darwin/menuitem.go b/v2/internal/frontend/desktop/darwin/menuitem.go index 00ad57aa..64aab84a 100644 --- a/v2/internal/frontend/desktop/darwin/menuitem.go +++ b/v2/internal/frontend/desktop/darwin/menuitem.go @@ -13,16 +13,19 @@ package darwin #include */ import "C" + import ( "log" "math" "sync" ) -var menuItemToID = make(map[*MenuItem]uint) -var idToMenuItem = make(map[uint]*MenuItem) -var menuItemLock sync.Mutex -var menuItemIDCounter uint = 0 +var ( + menuItemToID = make(map[*MenuItem]uint) + idToMenuItem = make(map[uint]*MenuItem) + menuItemLock sync.Mutex + menuItemIDCounter uint = 0 +) func createMenuItemID(item *MenuItem) uint { menuItemLock.Lock() diff --git a/v2/internal/frontend/desktop/darwin/screen.go b/v2/internal/frontend/desktop/darwin/screen.go index a96a8efa..bd64a31f 100644 --- a/v2/internal/frontend/desktop/darwin/screen.go +++ b/v2/internal/frontend/desktop/darwin/screen.go @@ -82,6 +82,7 @@ Screen GetNthScreen(int nth, void *inctx){ */ import "C" + import ( "unsafe" diff --git a/v2/internal/frontend/desktop/darwin/single_instance.go b/v2/internal/frontend/desktop/darwin/single_instance.go new file mode 100644 index 00000000..970f1f43 --- /dev/null +++ b/v2/internal/frontend/desktop/darwin/single_instance.go @@ -0,0 +1,93 @@ +//go:build darwin +// +build darwin + +package darwin + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework Cocoa +#import "AppDelegate.h" + +*/ +import "C" + +import ( + "encoding/json" + "fmt" + "os" + "strings" + "syscall" + "unsafe" + + "github.com/wailsapp/wails/v2/pkg/options" +) + +func SetupSingleInstance(uniqueID string) { + lockFilePath := getTempDir() + lockFileName := uniqueID + ".lock" + _, err := createLockFile(lockFilePath + "/" + lockFileName) + // if lockFile exist – send notification to second instance + if err != nil { + c := NewCalloc() + defer c.Free() + singleInstanceUniqueId := c.String(uniqueID) + + data, err := options.NewSecondInstanceData() + if err != nil { + return + } + + serialized, err := json.Marshal(data) + if err != nil { + return + } + + C.SendDataToFirstInstance(singleInstanceUniqueId, c.String(string(serialized))) + + os.Exit(0) + } +} + +//export HandleSecondInstanceData +func HandleSecondInstanceData(secondInstanceMessage *C.char) { + message := C.GoString(secondInstanceMessage) + + var secondInstanceData options.SecondInstanceData + + err := json.Unmarshal([]byte(message), &secondInstanceData) + if err == nil { + secondInstanceBuffer <- secondInstanceData + } +} + +// CreateLockFile tries to create a file with given name and acquire an +// exclusive lock on it. If the file already exists AND is still locked, it will +// fail. +func createLockFile(filename string) (*os.File, error) { + file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0o600) + if err != nil { + fmt.Printf("Failed to open lockfile %s: %s", filename, err) + return nil, err + } + + err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) + if err != nil { + // Flock failed for some other reason than other instance already lock it. Print it in logs for possible debugging. + if !strings.Contains(err.Error(), "resource temporarily unavailable") { + fmt.Printf("Failed to lock lockfile %s: %s", filename, err) + } + file.Close() + return nil, err + } + + return file, nil +} + +// If app is sandboxed, golang os.TempDir() will return path that will not be accessible. So use native macOS temp dir function. +func getTempDir() string { + cstring := C.GetMacOsNativeTempDir() + path := C.GoString(cstring) + C.free(unsafe.Pointer(cstring)) + + return path +} diff --git a/v2/internal/frontend/desktop/darwin/window.go b/v2/internal/frontend/desktop/darwin/window.go index 803d04c1..236026ee 100644 --- a/v2/internal/frontend/desktop/darwin/window.go +++ b/v2/internal/frontend/desktop/darwin/window.go @@ -13,6 +13,7 @@ package darwin #include */ import "C" + import ( "log" "runtime" @@ -46,7 +47,6 @@ func bool2CboolPtr(value bool) *C.bool { } func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window { - c := NewCalloc() defer c.Free() @@ -58,6 +58,7 @@ func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window startsHidden := bool2Cint(frontendOptions.StartHidden) devtoolsEnabled := bool2Cint(devtools) defaultContextMenuEnabled := bool2Cint(debug || frontendOptions.EnableDefaultContextMenu) + singleInstanceEnabled := bool2Cint(frontendOptions.SingleInstanceLock != nil) var fullSizeContent, hideTitleBar, hideTitle, useToolbar, webviewIsTransparent C.int var titlebarAppearsTransparent, hideToolbarSeparator, windowIsTranslucent C.int @@ -74,6 +75,12 @@ func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window title = c.String(frontendOptions.Title) + singleInstanceUniqueIdStr := "" + if frontendOptions.SingleInstanceLock != nil { + singleInstanceUniqueIdStr = frontendOptions.SingleInstanceLock.UniqueId + } + singleInstanceUniqueId := c.String(singleInstanceUniqueIdStr) + enableFraudulentWebsiteWarnings := C.bool(frontendOptions.EnableFraudulentWebsiteDetection) if frontendOptions.Mac != nil { @@ -95,6 +102,10 @@ func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window if mac.Preferences.TextInteractionEnabled.IsSet() { preferences.textInteractionEnabled = bool2CboolPtr(mac.Preferences.TextInteractionEnabled.Get()) } + + if mac.Preferences.FullscreenEnabled.IsSet() { + preferences.fullscreenEnabled = bool2CboolPtr(mac.Preferences.FullscreenEnabled.Get()) + } } windowIsTranslucent = bool2Cint(mac.WindowIsTranslucent) @@ -105,7 +116,9 @@ func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window var context *C.WailsContext = C.Create(title, width, height, frameless, resizable, fullscreen, fullSizeContent, hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent, alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, devtoolsEnabled, defaultContextMenuEnabled, - windowStartState, startsHidden, minWidth, minHeight, maxWidth, maxHeight, enableFraudulentWebsiteWarnings, preferences) + windowStartState, startsHidden, minWidth, minHeight, maxWidth, maxHeight, enableFraudulentWebsiteWarnings, + preferences, singleInstanceEnabled, singleInstanceUniqueId, + ) // Create menu result := &Window{ @@ -183,6 +196,7 @@ func (w *Window) SetTitle(title string) { func (w *Window) Maximise() { C.Maximise(w.context) } + func (w *Window) ToggleMaximise() { C.ToggleMaximise(w.context) } @@ -238,6 +252,7 @@ func (w *Window) Show() { func (w *Window) Hide() { C.Hide(w.context) } + func (w *Window) ShowApplication() { C.ShowApplication(w.context) } diff --git a/v2/internal/frontend/desktop/linux/frontend.go b/v2/internal/frontend/desktop/linux/frontend.go index 70f8468b..2410457b 100644 --- a/v2/internal/frontend/desktop/linux/frontend.go +++ b/v2/internal/frontend/desktop/linux/frontend.go @@ -102,6 +102,8 @@ var initOnce = sync.Once{} const startURL = "wails://wails/" +var secondInstanceBuffer = make(chan options.SecondInstanceData, 1) + type Frontend struct { // Context @@ -201,6 +203,8 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. C.free(unsafe.Pointer(prgname)) } + go result.startSecondInstanceProcessor() + return result } @@ -235,6 +239,10 @@ func (f *Frontend) Run(ctx context.Context) error { } }() + if f.frontendOptions.SingleInstanceLock != nil { + SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId) + } + f.mainWindow.Run(f.startURL.String()) return nil @@ -505,3 +513,12 @@ func (f *Frontend) startRequestProcessor() { func processURLRequest(request unsafe.Pointer) { requestBuffer <- webview.NewRequest(request) } + +func (f *Frontend) startSecondInstanceProcessor() { + for secondInstanceData := range secondInstanceBuffer { + if f.frontendOptions.SingleInstanceLock != nil && + f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil { + f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData) + } + } +} diff --git a/v2/internal/frontend/desktop/linux/single_instance.go b/v2/internal/frontend/desktop/linux/single_instance.go new file mode 100644 index 00000000..9d6426e1 --- /dev/null +++ b/v2/internal/frontend/desktop/linux/single_instance.go @@ -0,0 +1,69 @@ +//go:build linux +// +build linux + +package linux + +import ( + "encoding/json" + "github.com/godbus/dbus/v5" + "github.com/wailsapp/wails/v2/pkg/options" + "os" + "strings" +) + +type dbusHandler func(string) + +func (f dbusHandler) SendMessage(message string) *dbus.Error { + f(message) + return nil +} + +func SetupSingleInstance(uniqueID string) { + id := "wails_app_" + strings.ReplaceAll(strings.ReplaceAll(uniqueID, "-", "_"), ".", "_") + + dbusName := "org." + id + ".SingleInstance" + dbusPath := "/org/" + id + "/SingleInstance" + + conn, err := dbus.ConnectSessionBus() + // if we will reach any error during establishing connection or sending message we will just continue. + // It should not be the case that such thing will happen actually, but just in case. + if err != nil { + return + } + + f := dbusHandler(func(message string) { + var secondInstanceData options.SecondInstanceData + + err := json.Unmarshal([]byte(message), &secondInstanceData) + if err == nil { + secondInstanceBuffer <- secondInstanceData + } + }) + + err = conn.Export(f, dbus.ObjectPath(dbusPath), dbusName) + if err != nil { + return + } + + reply, err := conn.RequestName(dbusName, dbus.NameFlagDoNotQueue) + if err != nil { + return + } + + // if name already taken, try to send args to existing instance, if no success just launch new instance + if reply == dbus.RequestNameReplyExists { + data := options.SecondInstanceData{ + Args: os.Args[1:], + } + serialized, err := json.Marshal(data) + if err != nil { + return + } + + err = conn.Object(dbusName, dbus.ObjectPath(dbusPath)).Call(dbusName+".SendMessage", 0, string(serialized)).Store() + if err != nil { + return + } + os.Exit(1) + } +} diff --git a/v2/internal/frontend/desktop/linux/window.go b/v2/internal/frontend/desktop/linux/window.go index 82030f43..fbe8d7b5 100644 --- a/v2/internal/frontend/desktop/linux/window.go +++ b/v2/internal/frontend/desktop/linux/window.go @@ -25,6 +25,7 @@ import ( "github.com/wailsapp/wails/v2/internal/frontend" "github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/linux" ) func gtkBool(input bool) C.gboolean { @@ -90,6 +91,9 @@ func NewWindow(appoptions *options.App, debug bool, devtoolsEnabled bool) *Windo var webviewGpuPolicy int if appoptions.Linux != nil { webviewGpuPolicy = int(appoptions.Linux.WebviewGpuPolicy) + } else { + // workaround for https://github.com/wailsapp/wails/issues/2977 + webviewGpuPolicy = int(linux.WebviewGpuPolicyNever) } webview := C.SetupWebview( diff --git a/v2/internal/frontend/desktop/windows/dialog.go b/v2/internal/frontend/desktop/windows/dialog.go index 1ca422b7..7aca4289 100644 --- a/v2/internal/frontend/desktop/windows/dialog.go +++ b/v2/internal/frontend/desktop/windows/dialog.go @@ -47,7 +47,7 @@ func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (stri return cfd.NewSelectFolderDialog(config) }, false) - if err != nil && err != cfd.ErrorCancelled { + if err != nil && err != cfd.ErrCancelled { return "", err } return result.(string), nil @@ -72,7 +72,7 @@ func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, e return cfd.NewOpenFileDialog(config) }, false) - if err != nil && err != cfd.ErrorCancelled { + if err != nil && err != cfd.ErrCancelled { return "", err } return result.(string), nil @@ -99,7 +99,7 @@ func (f *Frontend) OpenMultipleFilesDialog(options frontend.OpenDialogOptions) ( return cfd.NewOpenMultipleFilesDialog(config) }, true) - if err != nil && err != cfd.ErrorCancelled { + if err != nil && err != cfd.ErrCancelled { return nil, err } return result.([]string), nil @@ -126,7 +126,7 @@ func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, e return cfd.NewSaveFileDialog(config) }, false) - if err != nil && err != cfd.ErrorCancelled { + if err != nil && err != cfd.ErrCancelled { return "", err } return result.(string), nil diff --git a/v2/internal/frontend/desktop/windows/frontend.go b/v2/internal/frontend/desktop/windows/frontend.go index a753bbd3..97eef741 100644 --- a/v2/internal/frontend/desktop/windows/frontend.go +++ b/v2/internal/frontend/desktop/windows/frontend.go @@ -35,6 +35,8 @@ import ( const startURL = "http://wails.localhost/" +var secondInstanceBuffer = make(chan options.SecondInstanceData, 1) + type Screen = frontend.Screen type Frontend struct { @@ -113,6 +115,8 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. } result.assets = assets + go result.startSecondInstanceProcessor() + return result } @@ -137,6 +141,10 @@ func (f *Frontend) Run(ctx context.Context) error { f.chromium = edge.NewChromium() + if f.frontendOptions.SingleInstanceLock != nil { + SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId) + } + mainWindow := NewWindow(nil, f.frontendOptions, f.versionInfo, f.chromium) f.mainWindow = mainWindow @@ -211,6 +219,7 @@ func (f *Frontend) WindowCenter() { func (f *Frontend) WindowSetAlwaysOnTop(b bool) { runtime.LockOSThread() + defer runtime.UnlockOSThread() f.mainWindow.SetAlwaysOnTop(b) } @@ -534,6 +543,10 @@ func (f *Frontend) setupChromium() { if err != nil { log.Fatal(err) } + err = settings.PutIsPinchZoomEnabled(!opts.DisablePinchZoom) + if err != nil { + log.Fatal(err) + } } err = settings.PutIsStatusBarEnabled(false) @@ -825,3 +838,12 @@ func (f *Frontend) ShowWindow() { func (f *Frontend) onFocus(arg *winc.Event) { f.chromium.Focus() } + +func (f *Frontend) startSecondInstanceProcessor() { + for secondInstanceData := range secondInstanceBuffer { + if f.frontendOptions.SingleInstanceLock != nil && + f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil { + f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData) + } + } +} diff --git a/v2/internal/frontend/desktop/windows/single_instance.go b/v2/internal/frontend/desktop/windows/single_instance.go new file mode 100644 index 00000000..222d9fe1 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/single_instance.go @@ -0,0 +1,126 @@ +//go:build windows + +package windows + +import ( + "encoding/json" + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" + "github.com/wailsapp/wails/v2/pkg/options" + "golang.org/x/sys/windows" + "os" + "syscall" + "unsafe" +) + +type COPYDATASTRUCT struct { + dwData uintptr + cbData uint32 + lpData uintptr +} + +// WMCOPYDATA_SINGLE_INSTANCE_DATA we define our own type for WM_COPYDATA message +const WMCOPYDATA_SINGLE_INSTANCE_DATA = 1542 + +func SendMessage(hwnd w32.HWND, data string) { + arrUtf16, _ := syscall.UTF16FromString(data) + + pCopyData := new(COPYDATASTRUCT) + pCopyData.dwData = WMCOPYDATA_SINGLE_INSTANCE_DATA + pCopyData.cbData = uint32(len(arrUtf16)*2 + 1) + pCopyData.lpData = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(data))) + + w32.SendMessage(hwnd, w32.WM_COPYDATA, 0, uintptr(unsafe.Pointer(pCopyData))) +} + +// SetupSingleInstance single instance Windows app +func SetupSingleInstance(uniqueId string) { + id := "wails-app-" + uniqueId + + className := id + "-sic" + windowName := id + "-siw" + mutexName := id + "sim" + + _, err := windows.CreateMutex(nil, false, windows.StringToUTF16Ptr(mutexName)) + + if err != nil { + if err == windows.ERROR_ALREADY_EXISTS { + // app is already running + hwnd := w32.FindWindowW(windows.StringToUTF16Ptr(className), windows.StringToUTF16Ptr(windowName)) + + if hwnd != 0 { + data := options.SecondInstanceData{ + Args: os.Args[1:], + } + serialized, _ := json.Marshal(data) + + SendMessage(hwnd, string(serialized)) + // exit second instance of app after sending message + os.Exit(0) + } + // if we got any other unknown error we will just start new application instance + } + } else { + createEventTargetWindow(className, windowName) + } +} + +func createEventTargetWindow(className string, windowName string) w32.HWND { + // callback handler in the event target window + wndProc := func( + hwnd w32.HWND, msg uint32, wparam w32.WPARAM, lparam w32.LPARAM, + ) w32.LRESULT { + if msg == w32.WM_COPYDATA { + ldata := (*COPYDATASTRUCT)(unsafe.Pointer(lparam)) + + if ldata.dwData == WMCOPYDATA_SINGLE_INSTANCE_DATA { + serialized := windows.UTF16PtrToString((*uint16)(unsafe.Pointer(ldata.lpData))) + + var secondInstanceData options.SecondInstanceData + + err := json.Unmarshal([]byte(serialized), &secondInstanceData) + + if err == nil { + secondInstanceBuffer <- secondInstanceData + } + } + + return w32.LRESULT(0) + } + + return w32.DefWindowProc(hwnd, msg, wparam, lparam) + } + + var class w32.WNDCLASSEX + class.Size = uint32(unsafe.Sizeof(class)) + class.Style = 0 + class.WndProc = syscall.NewCallback(wndProc) + class.ClsExtra = 0 + class.WndExtra = 0 + class.Instance = w32.GetModuleHandle("") + class.Icon = 0 + class.Cursor = 0 + class.Background = 0 + class.MenuName = nil + class.ClassName = windows.StringToUTF16Ptr(className) + class.IconSm = 0 + + w32.RegisterClassEx(&class) + + // create event window that will not be visible for user + hwnd := w32.CreateWindowEx( + 0, + windows.StringToUTF16Ptr(className), + windows.StringToUTF16Ptr(windowName), + 0, + 0, + 0, + 0, + 0, + w32.HWND_MESSAGE, + 0, + w32.GetModuleHandle(""), + nil, + ) + + return hwnd +} diff --git a/v2/internal/frontend/desktop/windows/winc/controlbase.go b/v2/internal/frontend/desktop/windows/winc/controlbase.go index b745cb1b..086609ae 100644 --- a/v2/internal/frontend/desktop/windows/winc/controlbase.go +++ b/v2/internal/frontend/desktop/windows/winc/controlbase.go @@ -334,7 +334,23 @@ func (cba *ControlBase) ClientHeight() int { } func (cba *ControlBase) Show() { - w32.ShowWindow(cba.hwnd, w32.SW_SHOWDEFAULT) + // WindowPos is used with HWND_TOPMOST to guarantee bring our app on top + // force set our main window on top + w32.SetWindowPos( + cba.hwnd, + w32.HWND_TOPMOST, + 0, 0, 0, 0, + w32.SWP_SHOWWINDOW|w32.SWP_NOSIZE|w32.SWP_NOMOVE, + ) + // remove topmost to allow normal windows manipulations + w32.SetWindowPos( + cba.hwnd, + w32.HWND_NOTOPMOST, + 0, 0, 0, 0, + w32.SWP_SHOWWINDOW|w32.SWP_NOSIZE|w32.SWP_NOMOVE, + ) + // put main window on tops foreground + w32.SetForegroundWindow(cba.hwnd) } func (cba *ControlBase) Hide() { diff --git a/v2/internal/frontend/desktop/windows/winc/form.go b/v2/internal/frontend/desktop/windows/winc/form.go index 9b9cadb2..8a42d63f 100644 --- a/v2/internal/frontend/desktop/windows/winc/form.go +++ b/v2/internal/frontend/desktop/windows/winc/form.go @@ -136,6 +136,15 @@ func (fm *Form) Minimise() { } func (fm *Form) Restore() { + // SC_RESTORE param for WM_SYSCOMMAND to restore app if it is minimized + const SC_RESTORE = 0xF120 + // restore the minimized window, if it is + w32.SendMessage( + fm.hwnd, + w32.WM_SYSCOMMAND, + SC_RESTORE, + 0, + ) w32.ShowWindow(fm.hwnd, w32.SW_RESTORE) } diff --git a/v2/internal/frontend/desktop/windows/winc/w32/user32.go b/v2/internal/frontend/desktop/windows/winc/w32/user32.go index ec5e4a59..8ca72ce4 100644 --- a/v2/internal/frontend/desktop/windows/winc/w32/user32.go +++ b/v2/internal/frontend/desktop/windows/winc/w32/user32.go @@ -24,6 +24,7 @@ var ( procShowWindowAsync = moduser32.NewProc("ShowWindowAsync") procUpdateWindow = moduser32.NewProc("UpdateWindow") procCreateWindowEx = moduser32.NewProc("CreateWindowExW") + procFindWindowW = moduser32.NewProc("FindWindowW") procAdjustWindowRect = moduser32.NewProc("AdjustWindowRect") procAdjustWindowRectEx = moduser32.NewProc("AdjustWindowRectEx") procDestroyWindow = moduser32.NewProc("DestroyWindow") @@ -263,6 +264,14 @@ func CreateWindowEx(exStyle uint, className, windowName *uint16, return HWND(ret) } +func FindWindowW(className, windowName *uint16) HWND { + ret, _, _ := procFindWindowW.Call( + uintptr(unsafe.Pointer(className)), + uintptr(unsafe.Pointer(windowName))) + + return HWND(ret) +} + func AdjustWindowRectEx(rect *RECT, style uint, menu bool, exStyle uint) bool { ret, _, _ := procAdjustWindowRectEx.Call( uintptr(unsafe.Pointer(rect)), diff --git a/v2/internal/frontend/dispatcher/calls.go b/v2/internal/frontend/dispatcher/calls.go index 491d17fe..ba106291 100644 --- a/v2/internal/frontend/dispatcher/calls.go +++ b/v2/internal/frontend/dispatcher/calls.go @@ -15,7 +15,6 @@ type callMessage struct { } func (d *Dispatcher) processCallMessage(message string, sender frontend.Frontend) (string, error) { - var payload callMessage err := json.Unmarshal([]byte(message[1:]), &payload) if err != nil { diff --git a/v2/internal/frontend/dispatcher/events.go b/v2/internal/frontend/dispatcher/events.go index 11680651..12fe7b89 100644 --- a/v2/internal/frontend/dispatcher/events.go +++ b/v2/internal/frontend/dispatcher/events.go @@ -3,6 +3,7 @@ package dispatcher import ( "encoding/json" "errors" + "github.com/wailsapp/wails/v2/internal/frontend" ) diff --git a/v2/internal/frontend/dispatcher/securecalls.go b/v2/internal/frontend/dispatcher/securecalls.go index 40accaa2..8cdcdfb8 100644 --- a/v2/internal/frontend/dispatcher/securecalls.go +++ b/v2/internal/frontend/dispatcher/securecalls.go @@ -3,6 +3,7 @@ package dispatcher import ( "encoding/json" "fmt" + "github.com/wailsapp/wails/v2/internal/frontend" ) @@ -13,7 +14,6 @@ type secureCallMessage struct { } func (d *Dispatcher) processSecureCallMessage(message string, sender frontend.Frontend) (string, error) { - var payload secureCallMessage err := json.Unmarshal([]byte(message[1:]), &payload) if err != nil { diff --git a/v2/internal/frontend/dispatcher/systemcalls.go b/v2/internal/frontend/dispatcher/systemcalls.go index b810d9be..b090a416 100644 --- a/v2/internal/frontend/dispatcher/systemcalls.go +++ b/v2/internal/frontend/dispatcher/systemcalls.go @@ -4,9 +4,10 @@ import ( "encoding/json" "errors" "fmt" - "github.com/wailsapp/wails/v2/pkg/runtime" "strings" + "github.com/wailsapp/wails/v2/pkg/runtime" + "github.com/wailsapp/wails/v2/internal/frontend" ) @@ -23,7 +24,6 @@ type size struct { } func (d *Dispatcher) processSystemCall(payload callMessage, sender frontend.Frontend) (interface{}, error) { - // Strip prefix name := strings.TrimPrefix(payload.Name, systemCallPrefix) @@ -64,5 +64,4 @@ func (d *Dispatcher) processSystemCall(payload callMessage, sender frontend.Fron default: return nil, fmt.Errorf("unknown systemcall message: %s", payload.Name) } - } diff --git a/v2/internal/frontend/frontend.go b/v2/internal/frontend/frontend.go index 27eb4a2e..6b2ccbca 100644 --- a/v2/internal/frontend/frontend.go +++ b/v2/internal/frontend/frontend.go @@ -77,7 +77,7 @@ type MessageDialogOptions struct { } type Frontend interface { - Run(context.Context) error + Run(ctx context.Context) error RunMainLoop() ExecJS(js string) Hide() @@ -123,7 +123,7 @@ type Frontend interface { WindowClose() WindowPrint() - //Screen + // Screen ScreenGetAll() ([]Screen, error) // Menus diff --git a/v2/internal/frontend/runtime/events.go b/v2/internal/frontend/runtime/events.go index ac9d6299..1f2e0a6e 100644 --- a/v2/internal/frontend/runtime/events.go +++ b/v2/internal/frontend/runtime/events.go @@ -143,7 +143,7 @@ func (e *Events) notifyBackend(eventName string, data ...interface{}) { } // Do we have items to delete? - if itemsToDelete == true { + if itemsToDelete { // Create a new Listeners slice var newListeners []*eventListener diff --git a/v2/internal/frontend/runtime/package-lock.json b/v2/internal/frontend/runtime/package-lock.json index f0b59c22..f6535549 100644 --- a/v2/internal/frontend/runtime/package-lock.json +++ b/v2/internal/frontend/runtime/package-lock.json @@ -1211,10 +1211,16 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -1404,9 +1410,9 @@ } }, "node_modules/postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "funding": [ { @@ -1416,10 +1422,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -2829,9 +2839,9 @@ "dev": true }, "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true }, "nice-try": { @@ -2964,12 +2974,12 @@ "dev": true }, "postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "requires": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } diff --git a/v2/internal/fs/fs.go b/v2/internal/fs/fs.go index 5bfa71b1..5662c020 100644 --- a/v2/internal/fs/fs.go +++ b/v2/internal/fs/fs.go @@ -2,6 +2,7 @@ package fs import ( "crypto/md5" + "encoding/hex" "fmt" "io" "io/fs" @@ -27,14 +28,14 @@ func RelativeToCwd(relativePath string) (string, error) { // Mkdir will create the given directory func Mkdir(dirname string) error { - return os.Mkdir(dirname, 0755) + return os.Mkdir(dirname, 0o755) } // MkDirs creates the given nested directories. // Returns error on failure func MkDirs(fullPath string, mode ...os.FileMode) error { var perms os.FileMode - perms = 0755 + perms = 0o755 if len(mode) == 1 { perms = mode[0] } @@ -111,7 +112,7 @@ func RelativePath(relativepath string, optionalpaths ...string) string { // I'm allowing this for 1 reason only: It's fatal if the path // supplied is wrong as it's only used internally in Wails. If we get // that path wrong, we should know about it immediately. The other reason is - // that it cuts down a ton of unnecassary error handling. + // that it cuts down a ton of unnecessary error handling. panic(err) } return result @@ -141,7 +142,7 @@ func MD5File(filename string) (string, error) { return "", err } - return fmt.Sprintf("%x", h.Sum(nil)), nil + return hex.EncodeToString(h.Sum(nil)), nil } // MustMD5File will call MD5File and abort the program on error @@ -157,7 +158,7 @@ func MustMD5File(filename string) string { // MustWriteString will attempt to write the given data to the given filename // It will abort the program in the event of a failure func MustWriteString(filename string, data string) { - err := os.WriteFile(filename, []byte(data), 0755) + err := os.WriteFile(filename, []byte(data), 0o755) if err != nil { fatal("Unable to write file", filename, ":", err.Error()) os.Exit(1) @@ -194,7 +195,6 @@ func GetSubdirectories(rootDir string) (*slicer.StringSlicer, error) { } func DirIsEmpty(dir string) (bool, error) { - // CREDIT: https://stackoverflow.com/a/30708914/8325411 f, err := os.Open(dir) if err != nil { @@ -284,7 +284,6 @@ func SetPermissions(dir string, perm os.FileMode) error { // Symlinks are ignored and skipped. // Credit: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04 func CopyDirExtended(src string, dst string, ignore []string) (err error) { - ignoreList := slicer.String(ignore) src = filepath.Clean(src) dst = filepath.Clean(dst) @@ -382,7 +381,6 @@ func FindPathToFile(fsys fs.FS, file string) (string, error) { // FindFileInParents searches for a file in the current directory and all parent directories. // Returns the absolute path to the file if found, otherwise an empty string func FindFileInParents(path string, filename string) string { - // Check for bad paths if _, err := os.Stat(path); err != nil { return "" diff --git a/v2/internal/github/github.go b/v2/internal/github/github.go index db3b70c5..c16e1d9d 100644 --- a/v2/internal/github/github.go +++ b/v2/internal/github/github.go @@ -3,13 +3,14 @@ package github import ( "encoding/json" "fmt" - "github.com/charmbracelet/glamour" "io" "net/http" "net/url" "runtime" "sort" "strings" + + "github.com/charmbracelet/glamour" ) func GetReleaseNotes(tagVersion string, noColour bool) string { @@ -57,7 +58,6 @@ func GetReleaseNotes(tagVersion string, noColour bool) string { // GetVersionTags gets the list of tags on the Wails repo // It returns a list of sorted tags in descending order func GetVersionTags() ([]*SemanticVersion, error) { - result := []*SemanticVersion{} var err error @@ -97,7 +97,6 @@ func GetVersionTags() ([]*SemanticVersion, error) { // GetLatestStableRelease gets the latest stable release on GitHub func GetLatestStableRelease() (result *SemanticVersion, err error) { - tags, err := GetVersionTags() if err != nil { return nil, err @@ -114,7 +113,6 @@ func GetLatestStableRelease() (result *SemanticVersion, err error) { // GetLatestPreRelease gets the latest prerelease on GitHub func GetLatestPreRelease() (result *SemanticVersion, err error) { - tags, err := GetVersionTags() if err != nil { return nil, err diff --git a/v2/internal/go-common-file-dialog/cfd/CommonFileDialog_nonWindows.go b/v2/internal/go-common-file-dialog/cfd/CommonFileDialog_nonWindows.go index 3ab96985..04c7cbcf 100644 --- a/v2/internal/go-common-file-dialog/cfd/CommonFileDialog_nonWindows.go +++ b/v2/internal/go-common-file-dialog/cfd/CommonFileDialog_nonWindows.go @@ -5,24 +5,24 @@ package cfd import "fmt" -var unsupportedError = fmt.Errorf("common file dialogs are only available on windows") +var errUnsupported = fmt.Errorf("common file dialogs are only available on windows") // TODO doc func NewOpenFileDialog(config DialogConfig) (OpenFileDialog, error) { - return nil, unsupportedError + return nil, errUnsupported } // TODO doc func NewOpenMultipleFilesDialog(config DialogConfig) (OpenMultipleFilesDialog, error) { - return nil, unsupportedError + return nil, errUnsupported } // TODO doc func NewSelectFolderDialog(config DialogConfig) (SelectFolderDialog, error) { - return nil, unsupportedError + return nil, errUnsupported } // TODO doc func NewSaveFileDialog(config DialogConfig) (SaveFileDialog, error) { - return nil, unsupportedError + return nil, errUnsupported } diff --git a/v2/internal/go-common-file-dialog/cfd/errors.go b/v2/internal/go-common-file-dialog/cfd/errors.go index c097c8eb..6f21fedb 100644 --- a/v2/internal/go-common-file-dialog/cfd/errors.go +++ b/v2/internal/go-common-file-dialog/cfd/errors.go @@ -2,6 +2,4 @@ package cfd import "errors" -var ( - ErrorCancelled = errors.New("cancelled by user") -) +var ErrCancelled = errors.New("cancelled by user") diff --git a/v2/internal/go-common-file-dialog/cfd/iFileOpenDialog.go b/v2/internal/go-common-file-dialog/cfd/iFileOpenDialog.go index 8c82cda2..4c080c91 100644 --- a/v2/internal/go-common-file-dialog/cfd/iFileOpenDialog.go +++ b/v2/internal/go-common-file-dialog/cfd/iFileOpenDialog.go @@ -178,7 +178,7 @@ func (vtbl *iFileOpenDialogVtbl) getResultsStrings(objPtr unsafe.Pointer) ([]str return nil, err } if shellItemArray == nil { - return nil, ErrorCancelled + return nil, ErrCancelled } defer shellItemArray.vtbl.release(unsafe.Pointer(shellItemArray)) count, err := shellItemArray.vtbl.getCount(unsafe.Pointer(shellItemArray)) diff --git a/v2/internal/go-common-file-dialog/cfd/iShellItemArray.go b/v2/internal/go-common-file-dialog/cfd/iShellItemArray.go index 84f26fa2..d904e72b 100644 --- a/v2/internal/go-common-file-dialog/cfd/iShellItemArray.go +++ b/v2/internal/go-common-file-dialog/cfd/iShellItemArray.go @@ -60,7 +60,7 @@ func (vtbl *iShellItemArrayVtbl) getItemAt(objPtr unsafe.Pointer, index uintptr) return "", err } if shellItem == nil { - return "", ErrorCancelled + return "", ErrCancelled } defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) return shellItem.vtbl.getDisplayName(unsafe.Pointer(shellItem)) diff --git a/v2/internal/go-common-file-dialog/cfd/vtblCommonFunc.go b/v2/internal/go-common-file-dialog/cfd/vtblCommonFunc.go index a9210001..929d5a2b 100644 --- a/v2/internal/go-common-file-dialog/cfd/vtblCommonFunc.go +++ b/v2/internal/go-common-file-dialog/cfd/vtblCommonFunc.go @@ -179,7 +179,7 @@ func (vtbl *iFileDialogVtbl) getResultString(objPtr unsafe.Pointer) (string, err return "", err } if shellItem == nil { - return "", ErrorCancelled + return "", ErrCancelled } defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) return shellItem.vtbl.getDisplayName(unsafe.Pointer(shellItem)) diff --git a/v2/internal/go-common-file-dialog/cfdutil/CFDUtil.go b/v2/internal/go-common-file-dialog/cfdutil/CFDUtil.go index bde52d74..655266bc 100644 --- a/v2/internal/go-common-file-dialog/cfdutil/CFDUtil.go +++ b/v2/internal/go-common-file-dialog/cfdutil/CFDUtil.go @@ -10,7 +10,9 @@ func ShowOpenFileDialog(config cfd.DialogConfig) (string, error) { if err != nil { return "", err } - defer dialog.Release() + defer func() { + _ = dialog.Release() + }() return dialog.ShowAndGetResult() } @@ -20,7 +22,9 @@ func ShowOpenMultipleFilesDialog(config cfd.DialogConfig) ([]string, error) { if err != nil { return nil, err } - defer dialog.Release() + defer func() { + _ = dialog.Release() + }() return dialog.ShowAndGetResults() } @@ -30,7 +34,9 @@ func ShowPickFolderDialog(config cfd.DialogConfig) (string, error) { if err != nil { return "", err } - defer dialog.Release() + defer func() { + _ = dialog.Release() + }() return dialog.ShowAndGetResult() } @@ -40,6 +46,8 @@ func ShowSaveFileDialog(config cfd.DialogConfig) (string, error) { if err != nil { return "", err } - defer dialog.Release() + defer func() { + _ = dialog.Release() + }() return dialog.ShowAndGetResult() } diff --git a/v2/internal/gomod/gomod.go b/v2/internal/gomod/gomod.go index 8ab7e0b6..c38e60f0 100644 --- a/v2/internal/gomod/gomod.go +++ b/v2/internal/gomod/gomod.go @@ -2,6 +2,7 @@ package gomod import ( "fmt" + "github.com/Masterminds/semver" "golang.org/x/mod/modfile" ) diff --git a/v2/internal/gomod/gomod_data_unix.go b/v2/internal/gomod/gomod_data_unix.go index f3a5e04c..c6004f48 100644 --- a/v2/internal/gomod/gomod_data_unix.go +++ b/v2/internal/gomod/gomod_data_unix.go @@ -10,6 +10,7 @@ require github.com/wailsapp/wails/v2 v2.0.0-beta.7 //replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => /home/lea/wails/v2 ` + const basicUpdated string = `module changeme go 1.17 @@ -29,6 +30,7 @@ require ( //replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => /home/lea/wails/v2 ` + const multilineReplace = `module changeme go 1.17 @@ -98,6 +100,7 @@ require ( replace github.com/wailsapp/wails/v2 v2.0.0-beta.20 => /home/lea/wails/v2 ` + const multilineReplaceNoVersionUpdated = `module changeme go 1.17 @@ -108,6 +111,7 @@ require ( replace github.com/wailsapp/wails/v2 => /home/lea/wails/v2 ` + const multilineReplaceNoVersionBlockUpdated = `module changeme go 1.17 diff --git a/v2/internal/logger/custom_logger.go b/v2/internal/logger/custom_logger.go index 5e24aa09..51e07c0f 100644 --- a/v2/internal/logger/custom_logger.go +++ b/v2/internal/logger/custom_logger.go @@ -86,7 +86,6 @@ func (l *customLogger) Warning(format string, args ...interface{}) { func (l *customLogger) Error(format string, args ...interface{}) { format = fmt.Sprintf("%s | %s", l.name, format) l.logger.Error(format, args...) - } // Fatal level logging. Works like Sprintf. diff --git a/v2/internal/logger/default_logger.go b/v2/internal/logger/default_logger.go index fe5c0538..5c72ae20 100644 --- a/v2/internal/logger/default_logger.go +++ b/v2/internal/logger/default_logger.go @@ -84,7 +84,6 @@ func (l *Logger) Info(format string, args ...interface{}) { if l.logLevel <= logger.INFO { l.output.Info(fmt.Sprintf(format, args...)) } - } // Warning level logging. Works like Sprintf. @@ -99,7 +98,6 @@ func (l *Logger) Error(format string, args ...interface{}) { if l.logLevel <= logger.ERROR { l.output.Error(fmt.Sprintf(format, args...)) } - } // Fatal level logging. Works like Sprintf. diff --git a/v2/internal/menumanager/applicationmenu.go b/v2/internal/menumanager/applicationmenu.go index 424834e5..4446a00c 100644 --- a/v2/internal/menumanager/applicationmenu.go +++ b/v2/internal/menumanager/applicationmenu.go @@ -3,7 +3,6 @@ package menumanager import "github.com/wailsapp/wails/v2/pkg/menu" func (m *Manager) SetApplicationMenu(applicationMenu *menu.Menu) error { - if applicationMenu == nil { return nil } @@ -38,7 +37,6 @@ func (m *Manager) UpdateApplicationMenu() (string, error) { } func (m *Manager) processApplicationMenu() error { - // Process the menu m.processedApplicationMenu = NewWailsMenu(m.applicationMenuItemMap, m.applicationMenu) m.processRadioGroups(m.processedApplicationMenu, m.applicationMenuItemMap) diff --git a/v2/internal/menumanager/contextmenu.go b/v2/internal/menumanager/contextmenu.go index 77c47891..f05bcdc4 100644 --- a/v2/internal/menumanager/contextmenu.go +++ b/v2/internal/menumanager/contextmenu.go @@ -23,7 +23,6 @@ func (t *ContextMenu) AsJSON() (string, error) { } func NewContextMenu(contextMenu *menu.ContextMenu) *ContextMenu { - result := &ContextMenu{ ID: contextMenu.ID, menu: contextMenu.Menu, @@ -37,7 +36,6 @@ func NewContextMenu(contextMenu *menu.ContextMenu) *ContextMenu { } func (m *Manager) AddContextMenu(contextMenu *menu.ContextMenu) { - newContextMenu := NewContextMenu(contextMenu) // Save the references diff --git a/v2/internal/menumanager/menuitemmap.go b/v2/internal/menumanager/menuitemmap.go index 790d5d06..e4e291be 100644 --- a/v2/internal/menumanager/menuitemmap.go +++ b/v2/internal/menumanager/menuitemmap.go @@ -2,8 +2,10 @@ package menumanager import ( "fmt" - "github.com/wailsapp/wails/v2/pkg/menu" + "strconv" "sync" + + "github.com/wailsapp/wails/v2/pkg/menu" ) // MenuItemMap holds a mapping between menuIDs and menu items @@ -48,14 +50,13 @@ func (m *MenuItemMap) Dump() { // GenerateMenuID returns a unique string ID for a menu item func (m *MenuItemMap) generateMenuID() string { m.menuIDCounterMutex.Lock() - result := fmt.Sprintf("%d", m.menuIDCounter) + result := strconv.FormatInt(m.menuIDCounter, 10) m.menuIDCounter++ m.menuIDCounterMutex.Unlock() return result } func (m *MenuItemMap) processMenuItem(item *menu.MenuItem) { - if item.SubMenu != nil { for _, submenuitem := range item.SubMenu.Items { m.processMenuItem(submenuitem) diff --git a/v2/internal/menumanager/menumanager.go b/v2/internal/menumanager/menumanager.go index ea793941..0c6be0df 100644 --- a/v2/internal/menumanager/menumanager.go +++ b/v2/internal/menumanager/menumanager.go @@ -2,11 +2,11 @@ package menumanager import ( "fmt" + "github.com/wailsapp/wails/v2/pkg/menu" ) type Manager struct { - // The application menu. applicationMenu *menu.Menu applicationMenuJSON string @@ -43,7 +43,6 @@ func (m *Manager) getMenuItemByID(menuMap *MenuItemMap, menuId string) *menu.Men } func (m *Manager) ProcessClick(menuID string, data string, menuType string, parentID string) error { - var menuItemMap *MenuItemMap switch menuType { @@ -93,7 +92,7 @@ func (m *Manager) ProcessClick(menuID string, data string, menuType string, pare // Create new Callback struct callbackData := &menu.CallbackData{ MenuItem: menuItem, - //ContextData: data, + // ContextData: data, } // Call back! diff --git a/v2/internal/menumanager/processedMenu.go b/v2/internal/menumanager/processedMenu.go index 8d886e19..0f235184 100644 --- a/v2/internal/menumanager/processedMenu.go +++ b/v2/internal/menumanager/processedMenu.go @@ -2,6 +2,7 @@ package menumanager import ( "encoding/json" + "github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v2/pkg/menu/keys" ) @@ -11,7 +12,7 @@ type ProcessedMenuItem struct { // Label is what appears as the menu text Label string `json:",omitempty"` // Role is a predefined menu type - //Role menu.Role `json:",omitempty"` + // Role menu.Role `json:",omitempty"` // Accelerator holds a representation of a key binding Accelerator *keys.Accelerator `json:",omitempty"` // Type of MenuItem, EG: Checkbox, Text, Separator, Radio, Submenu @@ -23,7 +24,7 @@ type ProcessedMenuItem struct { // Checked indicates if the item is selected (used by Checkbox and Radio types only) Checked bool `json:",omitempty"` // Submenu contains a list of menu items that will be shown as a submenu - //SubMenu []*MenuItem `json:"SubMenu,omitempty"` + // SubMenu []*MenuItem `json:"SubMenu,omitempty"` SubMenu *ProcessedMenu `json:",omitempty"` /* // Colour @@ -47,7 +48,6 @@ type ProcessedMenuItem struct { } func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *ProcessedMenuItem { - ID := menuItemMap.menuItemToIDMap[menuItem] // Parse ANSI text @@ -63,21 +63,21 @@ func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *Pr result := &ProcessedMenuItem{ ID: ID, Label: menuItem.Label, - //Role: menuItem.Role, + // Role: menuItem.Role, Accelerator: menuItem.Accelerator, Type: menuItem.Type, Disabled: menuItem.Disabled, Hidden: menuItem.Hidden, Checked: menuItem.Checked, SubMenu: nil, - //BackgroundColour: menuItem.BackgroundColour, - //FontSize: menuItem.FontSize, - //FontName: menuItem.FontName, - //Image: menuItem.Image, - //MacTemplateImage: menuItem.MacTemplateImage, - //MacAlternate: menuItem.MacAlternate, - //Tooltip: menuItem.Tooltip, - //StyledLabel: styledLabel, + // BackgroundColour: menuItem.BackgroundColour, + // FontSize: menuItem.FontSize, + // FontName: menuItem.FontName, + // Image: menuItem.Image, + // MacTemplateImage: menuItem.MacTemplateImage, + // MacAlternate: menuItem.MacAlternate, + // Tooltip: menuItem.Tooltip, + // StyledLabel: styledLabel, } if menuItem.SubMenu != nil { @@ -92,7 +92,6 @@ type ProcessedMenu struct { } func NewProcessedMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *ProcessedMenu { - result := &ProcessedMenu{} if menu != nil { for _, item := range menu.Items { @@ -131,7 +130,6 @@ func NewWailsMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *WailsMenu { } func (w *WailsMenu) AsJSON() (string, error) { - menuAsJSON, err := json.Marshal(w) if err != nil { return "", err @@ -150,7 +148,6 @@ func (w *WailsMenu) processRadioGroups() { } func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) { - switch item.Type { // We need to recurse submenus @@ -172,7 +169,6 @@ func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) { } func (w *WailsMenu) finaliseRadioGroup() { - // If we were processing a radio group, fix up the references if len(w.currentRadioGroup) > 0 { diff --git a/v2/internal/menumanager/traymenu.go b/v2/internal/menumanager/traymenu.go index aed5b05a..5efc4a86 100644 --- a/v2/internal/menumanager/traymenu.go +++ b/v2/internal/menumanager/traymenu.go @@ -13,8 +13,10 @@ import ( "github.com/wailsapp/wails/v2/pkg/menu" ) -var trayMenuID int -var trayMenuIDMutex sync.Mutex +var ( + trayMenuID int + trayMenuIDMutex sync.Mutex +) func generateTrayID() string { var idStr string @@ -51,7 +53,6 @@ func (t *TrayMenu) AsJSON() (string, error) { } func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu { - // Parse ANSI text var styledLabel []*ansi.StyledText tempLabel := trayMenu.Label @@ -205,7 +206,6 @@ func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) { } return string(data), nil - } func (m *Manager) GetContextMenus() ([]string, error) { diff --git a/v2/internal/process/process.go b/v2/internal/process/process.go index 6d497ed8..18c9f45d 100644 --- a/v2/internal/process/process.go +++ b/v2/internal/process/process.go @@ -25,7 +25,6 @@ func NewProcess(cmd string, args ...string) *Process { // Start the process func (p *Process) Start(exitCodeChannel chan int) error { - err := p.cmd.Start() if err != nil { return err diff --git a/v2/internal/project/project.go b/v2/internal/project/project.go index 023ca1df..0d84f185 100644 --- a/v2/internal/project/project.go +++ b/v2/internal/project/project.go @@ -12,7 +12,6 @@ import ( // Project holds the data related to a Wails project type Project struct { - /*** Application Data ***/ Name string `json:"name"` AssetDirectory string `json:"assetdir,omitempty"` @@ -144,11 +143,10 @@ func (p *Project) Save() error { if err != nil { return err } - return os.WriteFile(p.filename, data, 0755) + return os.WriteFile(p.filename, data, 0o755) } func (p *Project) setDefaults() { - if p.Path == "" { p.Path = lo.Must(os.Getwd()) } @@ -216,11 +214,27 @@ type Author struct { } type Info struct { - CompanyName string `json:"companyName"` - ProductName string `json:"productName"` - ProductVersion string `json:"productVersion"` - Copyright *string `json:"copyright"` - Comments *string `json:"comments"` + CompanyName string `json:"companyName"` + ProductName string `json:"productName"` + ProductVersion string `json:"productVersion"` + Copyright *string `json:"copyright"` + Comments *string `json:"comments"` + FileAssociations []FileAssociation `json:"fileAssociations"` + Protocols []Protocol `json:"protocols"` +} + +type FileAssociation struct { + Ext string `json:"ext"` + Name string `json:"name"` + Description string `json:"description"` + IconName string `json:"iconName"` + Role string `json:"role"` +} + +type Protocol struct { + Scheme string `json:"scheme"` + Description string `json:"description"` + Role string `json:"role"` } type Bindings struct { @@ -228,8 +242,9 @@ type Bindings struct { } type TsGeneration struct { - Prefix string `json:"prefix"` - Suffix string `json:"suffix"` + Prefix string `json:"prefix"` + Suffix string `json:"suffix"` + OutputType string `json:"outputType"` } // Parse the given JSON data into a Project struct diff --git a/v2/internal/s/s.go b/v2/internal/s/s.go index 86536e24..f363d654 100644 --- a/v2/internal/s/s.go +++ b/v2/internal/s/s.go @@ -2,6 +2,7 @@ package s import ( "crypto/md5" + "encoding/hex" "fmt" "io" "io/ioutil" @@ -74,9 +75,10 @@ func CD(dir string) { checkError(err) log("CD %s [%s]", dir, CWD()) } + func MKDIR(path string, mode ...os.FileMode) { var perms os.FileMode - perms = 0755 + perms = 0o755 if len(mode) == 1 { perms = mode[0] } @@ -88,7 +90,7 @@ func MKDIR(path string, mode ...os.FileMode) { // ENDIR ensures that the path gets created if it doesn't exist func ENDIR(path string, mode ...os.FileMode) { var perms os.FileMode - perms = 0755 + perms = 0o755 if len(mode) == 1 { perms = mode[0] } @@ -210,17 +212,13 @@ func ISDIR(path string) bool { // ISDIREMPTY returns true if the given directory is empty func ISDIREMPTY(dir string) bool { - // CREDIT: https://stackoverflow.com/a/30708914/8325411 f, err := os.Open(dir) checkError(err) defer closefile(f) _, err = f.Readdirnames(1) // Or f.Readdir(1) - if err == io.EOF { - return true - } - return false + return err == io.EOF } // ISFILE returns true if the given file exists @@ -270,7 +268,7 @@ func LOADSTRING(filename string) string { // SAVEBYTES will create a file with the given string func SAVEBYTES(filename string, data []byte) { log("SAVEBYTES %s", filename) - err := os.WriteFile(filename, data, 0755) + err := os.WriteFile(filename, data, 0o755) checkError(err) } @@ -297,7 +295,7 @@ func MD5FILE(filename string) string { _, err = io.Copy(h, f) checkError(err) - return fmt.Sprintf("%x", h.Sum(nil)) + return hex.EncodeToString(h.Sum(nil)) } // Sub is the substitution type diff --git a/v2/internal/shell/shell.go b/v2/internal/shell/shell.go index badea2b3..349e27bf 100644 --- a/v2/internal/shell/shell.go +++ b/v2/internal/shell/shell.go @@ -42,6 +42,7 @@ func (c *Command) Run() error { func (c *Command) Stdout() string { return c.stdo.String() } + func (c *Command) Stderr() string { return c.stde.String() } @@ -93,8 +94,5 @@ func RunCommandVerbose(directory string, command string, args ...string) error { // CommandExists returns true if the given command can be found on the shell func CommandExists(name string) bool { _, err := exec.LookPath(name) - if err != nil { - return false - } - return true + return err == nil } diff --git a/v2/internal/signal/signal.go b/v2/internal/signal/signal.go index 96e10bee..fa797453 100644 --- a/v2/internal/signal/signal.go +++ b/v2/internal/signal/signal.go @@ -9,8 +9,10 @@ import ( var signalChannel = make(chan os.Signal, 2) -var callbacks []func() -var lock sync.Mutex +var ( + callbacks []func() + lock sync.Mutex +) func OnShutdown(callback func()) { lock.Lock() @@ -20,20 +22,17 @@ func OnShutdown(callback func()) { // Start the Signal Manager func Start() { - // Hook into interrupts gosignal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) // Spin off signal listener and wait for either a cancellation // or signal go func() { - select { - case <-signalChannel: - println("") - println("Ctrl+C detected. Shutting down...") - for _, callback := range callbacks { - callback() - } + <-signalChannel + println("") + println("Ctrl+C detected. Shutting down...") + for _, callback := range callbacks { + callback() } }() } diff --git a/v2/internal/staticanalysis/staticanalysis.go b/v2/internal/staticanalysis/staticanalysis.go index 0d8fb92d..d5cc8c6e 100644 --- a/v2/internal/staticanalysis/staticanalysis.go +++ b/v2/internal/staticanalysis/staticanalysis.go @@ -2,9 +2,10 @@ package staticanalysis import ( "go/ast" - "golang.org/x/tools/go/packages" "path/filepath" "strings" + + "golang.org/x/tools/go/packages" ) type EmbedDetails struct { diff --git a/v2/internal/staticanalysis/test/standard/go.mod b/v2/internal/staticanalysis/test/standard/go.mod index 56184c62..0c7e2cc7 100644 --- a/v2/internal/staticanalysis/test/standard/go.mod +++ b/v2/internal/staticanalysis/test/standard/go.mod @@ -25,11 +25,11 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect ) // replace github.com/wailsapp/wails/v2 v2.0.0 => C:\Users\leaan diff --git a/v2/internal/staticanalysis/test/standard/go.sum b/v2/internal/staticanalysis/test/standard/go.sum index 54b3fbb0..d2c8dd68 100644 --- a/v2/internal/staticanalysis/test/standard/go.sum +++ b/v2/internal/staticanalysis/test/standard/go.sum @@ -57,13 +57,13 @@ github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhw github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= github.com/wailsapp/wails/v2 v2.3.1 h1:ZJz+pyIBKyASkgO8JO31NuHO1gTTHmvwiHYHwei1CqM= github.com/wailsapp/wails/v2 v2.3.1/go.mod h1:zlNLI0E2c2qA6miiuAHtp0Bac8FaGH0tlhA19OssR/8= -golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg= -golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -73,12 +73,12 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v2/internal/system/packagemanager/pm.go b/v2/internal/system/packagemanager/pm.go index dfd39429..bba45cd0 100644 --- a/v2/internal/system/packagemanager/pm.go +++ b/v2/internal/system/packagemanager/pm.go @@ -16,9 +16,9 @@ type packagemap = map[string][]*Package type PackageManager interface { Name() string Packages() packagemap - PackageInstalled(*Package) (bool, error) - PackageAvailable(*Package) (bool, error) - InstallCommand(*Package) string + PackageInstalled(pkg *Package) (bool, error) + PackageAvailable(pkg *Package) (bool, error) + InstallCommand(pkg *Package) string } // Dependency represents a system package that we require @@ -37,7 +37,6 @@ type DependencyList []*Dependency // InstallAllRequiredCommand returns the command you need to use to install all required dependencies func (d DependencyList) InstallAllRequiredCommand() string { - result := "" for _, dependency := range d { if !dependency.Installed && !dependency.Optional { @@ -50,7 +49,6 @@ func (d DependencyList) InstallAllRequiredCommand() string { // InstallAllOptionalCommand returns the command you need to use to install all optional dependencies func (d DependencyList) InstallAllOptionalCommand() string { - result := "" for _, dependency := range d { if !dependency.Installed && dependency.Optional { diff --git a/v2/internal/system/packagemanager/zypper.go b/v2/internal/system/packagemanager/zypper.go index c486b53e..efaeb0b1 100644 --- a/v2/internal/system/packagemanager/zypper.go +++ b/v2/internal/system/packagemanager/zypper.go @@ -45,6 +45,7 @@ func (z *Zypper) Packages() packagemap { }, "npm": []*Package{ {Name: "npm10", SystemPackage: true}, + {Name: "npm20", SystemPackage: true}, }, "docker": []*Package{ {Name: "docker", SystemPackage: true, Optional: true}, diff --git a/v2/internal/system/system.go b/v2/internal/system/system.go index a633989e..67453538 100644 --- a/v2/internal/system/system.go +++ b/v2/internal/system/system.go @@ -9,12 +9,10 @@ import ( "github.com/wailsapp/wails/v2/internal/system/packagemanager" ) -var ( - IsAppleSilicon bool -) +var IsAppleSilicon bool // Info holds information about the current operating system, -// package manager and required dependancies +// package manager and required dependencies type Info struct { OS *operatingsystem.OS PM packagemanager.PackageManager @@ -23,7 +21,7 @@ type Info struct { // GetInfo scans the system for operating system details, // the system package manager and the status of required -// dependancies. +// dependencies. func GetInfo() (*Info, error) { var result Info err := result.discover() @@ -34,7 +32,6 @@ func GetInfo() (*Info, error) { } func checkNodejs() *packagemanager.Dependency { - // Check for Nodejs output, err := exec.Command("node", "-v").Output() installed := true @@ -58,7 +55,6 @@ func checkNodejs() *packagemanager.Dependency { } func checkNPM() *packagemanager.Dependency { - // Check for npm output, err := exec.Command("npm", "-version").Output() installed := true @@ -80,7 +76,6 @@ func checkNPM() *packagemanager.Dependency { } func checkUPX() *packagemanager.Dependency { - // Check for npm output, err := exec.Command("upx", "-V").Output() installed := true @@ -102,7 +97,6 @@ func checkUPX() *packagemanager.Dependency { } func checkNSIS() *packagemanager.Dependency { - // Check for nsis installer output, err := exec.Command("makensis", "-VERSION").Output() installed := true @@ -141,7 +135,6 @@ func checkLibrary(name string) func() *packagemanager.Dependency { } func checkDocker() *packagemanager.Dependency { - // Check for npm output, err := exec.Command("docker", "version").Output() installed := true diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index b14059a5..c06a8b2e 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -104,6 +104,7 @@ type TypeScriptify struct { Namespace string KnownStructs *slicer.StringSlicer + KnownEnums *slicer.StringSlicer } func New() *TypeScriptify { @@ -156,10 +157,10 @@ func (t *TypeScriptify) deepFields(typeOf reflect.Type) []reflect.StructField { kind := f.Type.Kind() isPointer := kind == reflect.Ptr && f.Type.Elem().Kind() == reflect.Struct if f.Anonymous && kind == reflect.Struct { - //fmt.Println(v.Interface()) + // fmt.Println(v.Interface()) fields = append(fields, t.deepFields(f.Type)...) } else if f.Anonymous && isPointer { - //fmt.Println(v.Interface()) + // fmt.Println(v.Interface()) fields = append(fields, t.deepFields(f.Type.Elem())...) } else { // Check we have a json tag @@ -439,7 +440,7 @@ func (t TypeScriptify) backup(fileName string) error { backupFn = path.Join(t.BackupDir, backupFn) } - return ioutil.WriteFile(backupFn, bytes, os.FileMode(0700)) + return ioutil.WriteFile(backupFn, bytes, os.FileMode(0o700)) } func (t TypeScriptify) ConvertToFile(fileName string, packageName string) error { @@ -697,7 +698,7 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m builder.AddMapField(jsonFieldName, field) } else if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array { // Slice: - if field.Type.Elem().Kind() == reflect.Ptr { //extract ptr type + if field.Type.Elem().Kind() == reflect.Ptr { // extract ptr type field.Type = field.Type.Elem() } @@ -723,7 +724,16 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m } } else { // Simple field: t.logf(depth, "- simple field %s.%s", typeOf.Name(), field.Name) - err = builder.AddSimpleField(jsonFieldName, field, fldOpts) + // check if type is in known enum. If so, then replace TStype with enum name to avoid missing types + isKnownEnum := t.KnownEnums.Contains(getStructFQN(field.Type.String())) + if isKnownEnum { + err = builder.AddSimpleField(jsonFieldName, field, TypeOptions{ + TSType: getStructFQN(field.Type.String()), + TSTransform: fldOpts.TSTransform, + }) + } else { + err = builder.AddSimpleField(jsonFieldName, field, fldOpts) + } } if err != nil { return "", err @@ -935,6 +945,6 @@ func typeClashWithReservedKeyword(input string) bool { func warnAboutTypesClash(entity string) { // TODO: Refactor logging l := log.New(os.Stderr, "", 0) - l.Println(fmt.Sprintf("Usage of reserved keyword found and not supported: %s", entity)) + l.Printf("Usage of reserved keyword found and not supported: %s", entity) log.Println("Please rename returned type or consider adding bindings config to your wails.json") } diff --git a/v2/internal/webview2runtime/webview2installer.go b/v2/internal/webview2runtime/webview2installer.go index a2a2922d..3645dae0 100644 --- a/v2/internal/webview2runtime/webview2installer.go +++ b/v2/internal/webview2runtime/webview2installer.go @@ -11,7 +11,7 @@ var setupexe []byte // WriteInstallerToFile writes the installer file to the given file. func WriteInstallerToFile(targetFile string) error { - return os.WriteFile(targetFile, setupexe, 0755) + return os.WriteFile(targetFile, setupexe, 0o755) } // WriteInstaller writes the installer exe file to the given directory and returns the path to it. diff --git a/v2/pkg/application/application.go b/v2/pkg/application/application.go index 8d8d72ef..8ba58696 100644 --- a/v2/pkg/application/application.go +++ b/v2/pkg/application/application.go @@ -2,11 +2,12 @@ package application import ( "context" + "sync" + "github.com/wailsapp/wails/v2/internal/app" "github.com/wailsapp/wails/v2/internal/signal" "github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v2/pkg/options" - "sync" ) // Application is the main Wails application @@ -86,7 +87,6 @@ func (a *Application) Bind(boundStruct any) { } func (a *Application) On(eventType EventType, callback func()) { - c := func(ctx context.Context) { callback() } diff --git a/v2/pkg/assetserver/assethandler.go b/v2/pkg/assetserver/assethandler.go index c85bf81e..76d41147 100644 --- a/v2/pkg/assetserver/assethandler.go +++ b/v2/pkg/assetserver/assethandler.go @@ -10,6 +10,7 @@ import ( "net/http" "os" "path" + "strconv" "strings" "github.com/wailsapp/wails/v2/pkg/options/assetserver" @@ -37,7 +38,6 @@ type assetHandler struct { } func NewAssetHandler(options assetserver.Options, log Logger) (http.Handler, error) { - vfs := options.Assets if vfs != nil { if _, err := vfs.Open("."); err != nil { @@ -178,7 +178,8 @@ func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, fi return nil } - rw.Header().Set(HeaderContentLength, fmt.Sprintf("%d", statInfo.Size())) + size := strconv.FormatInt(statInfo.Size(), 10) + rw.Header().Set(HeaderContentLength, size) // Write the first 512 bytes used for MimeType sniffing _, err = io.Copy(rw, bytes.NewReader(buf[:n])) diff --git a/v2/pkg/assetserver/assetserver.go b/v2/pkg/assetserver/assetserver.go index 625c3f24..54fe5d06 100644 --- a/v2/pkg/assetserver/assetserver.go +++ b/v2/pkg/assetserver/assetserver.go @@ -5,7 +5,6 @@ import ( "fmt" "math/rand" "net/http" - "net/http/httptest" "strings" "golang.org/x/net/html" @@ -111,23 +110,58 @@ func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { return } - header := rw.Header() if d.servingFromDisk { - header.Add(HeaderCacheControl, "no-cache") + rw.Header().Add(HeaderCacheControl, "no-cache") + } + + handler := d.handler + if req.Method != http.MethodGet { + handler.ServeHTTP(rw, req) + return } path := req.URL.Path - switch path { - case "", "/", "/index.html": - recorder := httptest.NewRecorder() - d.handler.ServeHTTP(recorder, req) - for k, v := range recorder.HeaderMap { - header[k] = v + if path == runtimeJSPath { + d.writeBlob(rw, path, d.runtimeJS) + } else if path == runtimePath && d.runtimeHandler != nil { + d.runtimeHandler.HandleRuntimeCall(rw, req) + } else if path == ipcJSPath { + content := d.runtime.DesktopIPC() + if d.ipcJS != nil { + content = d.ipcJS(req) + } + d.writeBlob(rw, path, content) + + } else if script, ok := d.pluginScripts[path]; ok { + d.writeBlob(rw, path, []byte(script)) + } else if d.isRuntimeInjectionMatch(path) { + recorder := &bodyRecorder{ + ResponseWriter: rw, + doRecord: func(code int, h http.Header) bool { + if code == http.StatusNotFound { + return true + } + + if code != http.StatusOK { + return false + } + + return strings.Contains(h.Get(HeaderContentType), "text/html") + }, } - switch recorder.Code { + handler.ServeHTTP(recorder, req) + + body := recorder.Body() + if body == nil { + // The body has been streamed and not recorded, we are finished + return + } + + code := recorder.Code() + switch code { case http.StatusOK: - content, err := d.processIndexHTML(recorder.Body.Bytes()) + content, err := d.processIndexHTML(body.Bytes()) if err != nil { d.serveError(rw, err, "Unable to processIndexHTML") return @@ -138,34 +172,12 @@ func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { d.writeBlob(rw, indexHTML, defaultHTML) default: - rw.WriteHeader(recorder.Code) + rw.WriteHeader(code) } - case runtimeJSPath: - d.writeBlob(rw, path, d.runtimeJS) - - case runtimePath: - if d.runtimeHandler != nil { - d.runtimeHandler.HandleRuntimeCall(rw, req) - } else { - d.handler.ServeHTTP(rw, req) - } - - case ipcJSPath: - content := d.runtime.DesktopIPC() - if d.ipcJS != nil { - content = d.ipcJS(req) - } - d.writeBlob(rw, path, content) - - default: - // Check if this is a plugin script - if script, ok := d.pluginScripts[path]; ok { - d.writeBlob(rw, path, []byte(script)) - return - } - d.handler.ServeHTTP(rw, req) + } else { + handler.ServeHTTP(rw, req) } } @@ -229,3 +241,12 @@ func (d *AssetServer) logError(message string, args ...interface{}) { d.logger.Error("[AssetServer] "+message, args...) } } + +func (AssetServer) isRuntimeInjectionMatch(path string) bool { + if path == "" { + path = "/" + } + + return strings.HasSuffix(path, "/") || + strings.HasSuffix(path, "/"+indexHTML) +} diff --git a/v2/pkg/assetserver/assetserver_webview.go b/v2/pkg/assetserver/assetserver_webview.go index 575c81bb..dfab1f2d 100644 --- a/v2/pkg/assetserver/assetserver_webview.go +++ b/v2/pkg/assetserver/assetserver_webview.go @@ -133,7 +133,8 @@ func (d *AssetServer) processWebViewRequestInternal(r webview.Request) { if req.ContentLength == 0 { req.ContentLength, _ = strconv.ParseInt(req.Header.Get(HeaderContentLength), 10, 64) } else { - req.Header.Set(HeaderContentLength, fmt.Sprintf("%d", req.ContentLength)) + size := strconv.FormatInt(req.ContentLength, 10) + req.Header.Set(HeaderContentLength, size) } if host := req.Header.Get(HeaderHost); host != "" { diff --git a/v2/pkg/assetserver/body_recorder.go b/v2/pkg/assetserver/body_recorder.go new file mode 100644 index 00000000..fa3bc1e7 --- /dev/null +++ b/v2/pkg/assetserver/body_recorder.go @@ -0,0 +1,61 @@ +package assetserver + +import ( + "bytes" + "net/http" +) + +type bodyRecorder struct { + http.ResponseWriter + doRecord func(code int, header http.Header) bool + + body *bytes.Buffer + code int + wroteHeader bool +} + +func (rw *bodyRecorder) Write(buf []byte) (int, error) { + rw.writeHeader(buf, http.StatusOK) + if rw.body != nil { + return rw.body.Write(buf) + } + return rw.ResponseWriter.Write(buf) +} + +func (rw *bodyRecorder) WriteHeader(code int) { + rw.writeHeader(nil, code) +} + +func (rw *bodyRecorder) Code() int { + return rw.code +} + +func (rw *bodyRecorder) Body() *bytes.Buffer { + return rw.body +} + +func (rw *bodyRecorder) writeHeader(buf []byte, code int) { + if rw.wroteHeader { + return + } + + if rw.doRecord != nil { + header := rw.Header() + if len(buf) != 0 { + if _, hasType := header[HeaderContentType]; !hasType { + header.Set(HeaderContentType, http.DetectContentType(buf)) + } + } + + if rw.doRecord(code, header) { + rw.body = bytes.NewBuffer(nil) + } + } + + if rw.body == nil { + rw.ResponseWriter.WriteHeader(code) + } + + rw.code = code + rw.wroteHeader = true +} diff --git a/v2/pkg/assetserver/common.go b/v2/pkg/assetserver/common.go index 01e51f2b..57934e08 100644 --- a/v2/pkg/assetserver/common.go +++ b/v2/pkg/assetserver/common.go @@ -3,9 +3,9 @@ package assetserver import ( "bytes" "errors" - "fmt" "io" "net/http" + "strconv" "strings" "github.com/wailsapp/wails/v2/pkg/options" @@ -44,7 +44,7 @@ const ( func serveFile(rw http.ResponseWriter, filename string, blob []byte) error { header := rw.Header() - header.Set(HeaderContentLength, fmt.Sprintf("%d", len(blob))) + header.Set(HeaderContentLength, strconv.Itoa(len(blob))) if mimeType := header.Get(HeaderContentType); mimeType == "" { mimeType = GetMimetype(filename, blob) header.Set(HeaderContentType, mimeType) diff --git a/v2/pkg/assetserver/webview/request_darwin.go b/v2/pkg/assetserver/webview/request_darwin.go index f0e85780..c44e5f19 100644 --- a/v2/pkg/assetserver/webview/request_darwin.go +++ b/v2/pkg/assetserver/webview/request_darwin.go @@ -197,7 +197,10 @@ func (r *request) Close() error { if r.body != nil { err = r.body.Close() } - r.Response().Finish() + err = r.Response().Finish() + if err != nil { + return err + } C.URLSchemeTaskRelease(r.task) return err } diff --git a/v2/pkg/buildassets/build/darwin/Info.dev.plist b/v2/pkg/buildassets/build/darwin/Info.dev.plist index 02e7358e..04727c23 100644 --- a/v2/pkg/buildassets/build/darwin/Info.dev.plist +++ b/v2/pkg/buildassets/build/darwin/Info.dev.plist @@ -23,10 +23,46 @@ true NSHumanReadableCopyright {{.Info.Copyright}} + {{if .Info.FileAssociations}} + CFBundleDocumentTypes + + {{range .Info.FileAssociations}} + + CFBundleTypeExtensions + + {{.Ext}} + + CFBundleTypeName + {{.Name}} + CFBundleTypeRole + {{.Role}} + CFBundleTypeIconFile + {{.IconName}} + + {{end}} + + {{end}} + {{if .Info.Protocols}} + CFBundleURLTypes + + {{range .Info.Protocols}} + + CFBundleURLName + com.wails.{{.Scheme}} + CFBundleURLSchemes + + {{.Scheme}} + + CFBundleTypeRole + {{.Role}} + + {{end}} + + {{end}} NSAppTransportSecurity NSAllowsLocalNetworking - \ No newline at end of file + diff --git a/v2/pkg/buildassets/build/darwin/Info.plist b/v2/pkg/buildassets/build/darwin/Info.plist index e7819a7e..19cc9370 100644 --- a/v2/pkg/buildassets/build/darwin/Info.plist +++ b/v2/pkg/buildassets/build/darwin/Info.plist @@ -23,5 +23,41 @@ true NSHumanReadableCopyright {{.Info.Copyright}} + {{if .Info.FileAssociations}} + CFBundleDocumentTypes + + {{range .Info.FileAssociations}} + + CFBundleTypeExtensions + + {{.Ext}} + + CFBundleTypeName + {{.Name}} + CFBundleTypeRole + {{.Role}} + CFBundleTypeIconFile + {{.IconName}} + + {{end}} + + {{end}} + {{if .Info.Protocols}} + CFBundleURLTypes + + {{range .Info.Protocols}} + + CFBundleURLName + com.wails.{{.Scheme}} + CFBundleURLSchemes + + {{.Scheme}} + + CFBundleTypeRole + {{.Role}} + + {{end}} + + {{end}} - \ No newline at end of file + diff --git a/v2/pkg/buildassets/build/windows/installer/project.nsi b/v2/pkg/buildassets/build/windows/installer/project.nsi index 13cc4f02..654ae2e4 100644 --- a/v2/pkg/buildassets/build/windows/installer/project.nsi +++ b/v2/pkg/buildassets/build/windows/installer/project.nsi @@ -3,10 +3,10 @@ Unicode true #### ## Please note: Template replacements don't work in this file. They are provided with default defines like ## mentioned underneath. -## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo. -## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually +## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually ## from outside of Wails for debugging and development of the installer. -## +## ## For development first make a wails nsis build to populate the "wails_tools.nsh": ## > wails build --target windows/amd64 --nsis ## Then you can call makensis on this file with specifying the path to your binary: @@ -17,7 +17,7 @@ Unicode true ## For a installer with both architectures: ## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe #### -## The following information is taken from the ProjectInfo file, but they can be overwritten here. +## The following information is taken from the ProjectInfo file, but they can be overwritten here. #### ## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}" ## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}" @@ -85,16 +85,19 @@ Section !insertmacro wails.webview2runtime SetOutPath $INSTDIR - + !insertmacro wails.files CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + !insertmacro wails.associateFiles + !insertmacro wails.associateCustomProtocols + !insertmacro wails.writeUninstaller SectionEnd -Section "uninstall" +Section "uninstall" !insertmacro wails.setShellContext RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath @@ -104,5 +107,8 @@ Section "uninstall" Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + !insertmacro wails.unassociateFiles + !insertmacro wails.unassociateCustomProtocols + !insertmacro wails.deleteUninstaller SectionEnd diff --git a/v2/pkg/buildassets/build/windows/installer/wails_tools.nsh b/v2/pkg/buildassets/build/windows/installer/wails_tools.nsh index 467c349a..f9c0f885 100644 --- a/v2/pkg/buildassets/build/windows/installer/wails_tools.nsh +++ b/v2/pkg/buildassets/build/windows/installer/wails_tools.nsh @@ -163,17 +163,87 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" Goto ok ${EndIf} ${EndIf} - + SetDetailsPrint both DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" SetDetailsPrint listonly - + InitPluginsDir CreateDirectory "$pluginsdir\webview2bootstrapper" SetOutPath "$pluginsdir\webview2bootstrapper" File "tmp\MicrosoftEdgeWebview2Setup.exe" ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' - + SetDetailsPrint both ok: -!macroend \ No newline at end of file +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + {{range .Info.FileAssociations}} + !insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" + + File "..\{{.IconName}}.ico" + {{end}} +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + {{range .Info.FileAssociations}} + !insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}" + + Delete "$INSTDIR\{{.IconName}}.ico" + {{end}} +!macroend + +!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" +!macroend + +!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" +!macroend + +!macro wails.associateCustomProtocols + ; Create custom protocols associations + {{range .Info.Protocols}} + !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" + + {{end}} +!macroend + +!macro wails.unassociateCustomProtocols + ; Delete app custom protocol associations + {{range .Info.Protocols}} + !insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}" + {{end}} +!macroend diff --git a/v2/pkg/buildassets/buildassets.go b/v2/pkg/buildassets/buildassets.go index 26401745..778d97fb 100644 --- a/v2/pkg/buildassets/buildassets.go +++ b/v2/pkg/buildassets/buildassets.go @@ -128,12 +128,12 @@ func writeFileSystemFile(projectData *project.Project, file string, content []by targetPath := GetLocalPath(projectData, file) if dir := filepath.Dir(targetPath); !fs.DirExists(dir) { - if err := fs.MkDirs(dir, 0755); err != nil { + if err := fs.MkDirs(dir, 0o755); err != nil { return fmt.Errorf("Unable to create directory: %w", err) } } - if err := os.WriteFile(targetPath, content, 0644); err != nil { + if err := os.WriteFile(targetPath, content, 0o644); err != nil { return err } return nil diff --git a/v2/pkg/commands/bindings/bindings.go b/v2/pkg/commands/bindings/bindings.go index 71c1747b..23d4d635 100644 --- a/v2/pkg/commands/bindings/bindings.go +++ b/v2/pkg/commands/bindings/bindings.go @@ -18,15 +18,16 @@ type Options struct { Filename string Tags []string ProjectDirectory string + Compiler string GoModTidy bool TsPrefix string TsSuffix string + TsOutputType string } // GenerateBindings generates bindings for the Wails project in the given ProjectDirectory. // If no project directory is given then the current working directory is used. func GenerateBindings(options Options) (string, error) { - filename, _ := lo.Coalesce(options.Filename, "wailsbindings") if runtime.GOOS == "windows" { filename += ".exe" @@ -46,17 +47,25 @@ func GenerateBindings(options Options) (string, error) { tagString := buildtags.Stringify(genModuleTags) if options.GoModTidy { - stdout, stderr, err = shell.RunCommand(workingDirectory, "go", "mod", "tidy") + stdout, stderr, err = shell.RunCommand(workingDirectory, options.Compiler, "mod", "tidy") if err != nil { return stdout, fmt.Errorf("%s\n%s\n%s", stdout, stderr, err) } } - stdout, stderr, err = shell.RunCommand(workingDirectory, "go", "build", "-tags", tagString, "-o", filename) + stdout, stderr, err = shell.RunCommand(workingDirectory, options.Compiler, "build", "-tags", tagString, "-o", filename) if err != nil { return stdout, fmt.Errorf("%s\n%s\n%s", stdout, stderr, err) } + if runtime.GOOS == "darwin" { + // Remove quarantine attribute + stdout, stderr, err = shell.RunCommand(workingDirectory, "xattr", "-rc", filename) + if err != nil { + return stdout, fmt.Errorf("%s\n%s\n%s", stdout, stderr, err) + } + } + defer func() { // Best effort removal of temp file _ = os.Remove(filename) @@ -66,6 +75,7 @@ func GenerateBindings(options Options) (string, error) { env := os.Environ() env = shell.SetEnv(env, "tsprefix", options.TsPrefix) env = shell.SetEnv(env, "tssuffix", options.TsSuffix) + env = shell.SetEnv(env, "tsoutputtype", options.TsOutputType) stdout, stderr, err = shell.RunCommandWithEnv(env, workingDirectory, filename) if err != nil { diff --git a/v2/pkg/commands/bindings/bindings_test.go b/v2/pkg/commands/bindings/bindings_test.go index a2cbed43..53f42f2c 100644 --- a/v2/pkg/commands/bindings/bindings_test.go +++ b/v2/pkg/commands/bindings/bindings_test.go @@ -1,13 +1,14 @@ package bindings import ( - "github.com/matryer/is" - "github.com/wailsapp/wails/v2/pkg/templates" "os" "path/filepath" "runtime" "strings" "testing" + + "github.com/matryer/is" + "github.com/wailsapp/wails/v2/pkg/templates" ) const standardBindings = `// @ts-check @@ -80,6 +81,7 @@ func TestGenerateBindings(t *testing.T) { name: "should generate standard bindings with no user tags", options: Options{ ProjectDirectory: projectDir, + Compiler: "go", GoModTidy: true, }, expectedBindings: standardBindings, @@ -90,6 +92,7 @@ func TestGenerateBindings(t *testing.T) { name: "should generate bindings when given tags", options: Options{ ProjectDirectory: projectDir, + Compiler: "go", Tags: []string{"test"}, GoModTidy: true, }, @@ -101,6 +104,7 @@ func TestGenerateBindings(t *testing.T) { name: "should generate obfuscated bindings", options: Options{ ProjectDirectory: projectDir, + Compiler: "go", Tags: []string{"obfuscated"}, GoModTidy: true, }, diff --git a/v2/pkg/commands/build/base.go b/v2/pkg/commands/build/base.go index abfbafff..6595aff0 100644 --- a/v2/pkg/commands/build/base.go +++ b/v2/pkg/commands/build/base.go @@ -74,7 +74,6 @@ func (b *BaseBuilder) convertFileToIntegerString(filename string) (string, error } func (b *BaseBuilder) convertByteSliceToIntegerString(data []byte) string { - // Create string builder var result strings.Builder @@ -85,8 +84,7 @@ func (b *BaseBuilder) convertByteSliceToIntegerString(data []byte) string { result.WriteString(fmt.Sprintf("%v,", data[i])) } - result.WriteString(fmt.Sprintf("%v", data[len(data)-1])) - + result.WriteString(strconv.FormatUint(uint64(data[len(data)-1]), 10)) } return result.String() @@ -94,10 +92,8 @@ func (b *BaseBuilder) convertByteSliceToIntegerString(data []byte) string { // CleanUp does post-build housekeeping func (b *BaseBuilder) CleanUp() { - // Delete all the files b.filesToDelete.Each(func(filename string) { - // if file doesn't exist, ignore if !b.fileExists(filename) { return @@ -106,7 +102,6 @@ func (b *BaseBuilder) CleanUp() { // Delete file. We ignore errors because these files will be overwritten // by the next build anyway. _ = os.Remove(filename) - }) } @@ -159,7 +154,6 @@ func (b *BaseBuilder) OutputFilename(options *Options) string { // CompileProject compiles the project func (b *BaseBuilder) CompileProject(options *Options) error { - // Check if the runtime wrapper exists err := generateRuntimeWrapper(options) if err != nil { @@ -402,7 +396,7 @@ Please reinstall by doing the following: return nil } - var args = []string{"--best", "--no-color", "--no-progress", options.CompiledBinary} + args := []string{"--best", "--no-color", "--no-progress", options.CompiledBinary} if options.CompressFlags != "" { args = strings.Split(options.CompressFlags, " ") @@ -426,7 +420,6 @@ Please reinstall by doing the following: } func generateRuntimeWrapper(options *Options) error { - if options.WailsJSDir == "" { cwd, err := os.Getwd() if err != nil { @@ -452,7 +445,6 @@ func (b *BaseBuilder) NpmInstall(sourceDir string, verbose bool) error { // NpmInstallUsingCommand runs the given install command in the specified npm project directory func (b *BaseBuilder) NpmInstallUsingCommand(sourceDir string, installCommand string, verbose bool) error { - packageJSON := filepath.Join(sourceDir, "package.json") // Check package.json exists @@ -492,7 +484,7 @@ func (b *BaseBuilder) NpmInstallUsingCommand(sourceDir string, installCommand st } // Shortcut installation - if install == false { + if !install { if verbose { pterm.Println("Skipping npm install") } @@ -549,7 +541,6 @@ func (b *BaseBuilder) NpmRunWithEnvironment(projectDir, buildTarget string, verb // BuildFrontend executes the `npm build` command for the frontend directory func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error { - verbose := b.options.Verbosity == VERBOSE frontendDir := b.projectData.GetFrontendDir() diff --git a/v2/pkg/commands/build/build.go b/v2/pkg/commands/build/build.go index a29840b1..a2b8ffcb 100644 --- a/v2/pkg/commands/build/build.go +++ b/v2/pkg/commands/build/build.go @@ -71,9 +71,12 @@ type Options struct { SkipBindings bool // Skip binding generation } +func (o *Options) IsWindowsTargetPlatform() bool { + return strings.Contains(strings.ToLower(o.Platform), "windows") +} + // Build the project! func Build(options *Options) (string, error) { - // Extract logger outputLogger := options.Logger @@ -171,7 +174,7 @@ func CreateEmbedDirectories(cwd string, buildOptions *Options) error { for _, embedDetail := range embedDetails { fullPath := embedDetail.GetFullPath() if _, err := os.Stat(fullPath); os.IsNotExist(err) { - err := os.MkdirAll(fullPath, 0755) + err := os.MkdirAll(fullPath, 0o755) if err != nil { return err } @@ -184,7 +187,6 @@ func CreateEmbedDirectories(cwd string, buildOptions *Options) error { } return nil - } func fatal(message string) { @@ -213,7 +215,6 @@ func printBulletPoint(text string, args ...any) { } func GenerateBindings(buildOptions *Options) error { - obfuscated := buildOptions.Obfuscated if obfuscated { printBulletPoint("Generating obfuscated bindings: ") @@ -222,12 +223,18 @@ func GenerateBindings(buildOptions *Options) error { printBulletPoint("Generating bindings: ") } + if buildOptions.ProjectData.Bindings.TsGeneration.OutputType == "" { + buildOptions.ProjectData.Bindings.TsGeneration.OutputType = "classes" + } + // Generate Bindings output, err := bindings.GenerateBindings(bindings.Options{ - Tags: buildOptions.UserTags, - GoModTidy: !buildOptions.SkipModTidy, - TsPrefix: buildOptions.ProjectData.Bindings.TsGeneration.Prefix, - TsSuffix: buildOptions.ProjectData.Bindings.TsGeneration.Suffix, + Compiler: buildOptions.Compiler, + Tags: buildOptions.UserTags, + GoModTidy: !buildOptions.SkipModTidy, + TsPrefix: buildOptions.ProjectData.Bindings.TsGeneration.Prefix, + TsSuffix: buildOptions.ProjectData.Bindings.TsGeneration.Suffix, + TsOutputType: buildOptions.ProjectData.Bindings.TsGeneration.OutputType, }) if err != nil { return err @@ -319,6 +326,20 @@ func execBuildApplication(builder Builder, options *Options) (string, error) { } } + if runtime.GOOS == "darwin" { + // Remove quarantine attribute + if _, err := os.Stat(options.CompiledBinary); os.IsNotExist(err) { + return "", fmt.Errorf("compiled binary does not exist at path: %s", options.CompiledBinary) + } + stdout, stderr, err := shell.RunCommand(options.BinDirectory, "xattr", "-rc", options.CompiledBinary) + if err != nil { + return "", fmt.Errorf("%s - %s", err.Error(), stderr) + } + if options.Verbosity == VERBOSE && stdout != "" { + pterm.Info.Println(stdout) + } + } + pterm.Println("Done.") // Do we need to pack the app for non-windows? @@ -371,7 +392,6 @@ func execPostBuildHook(outputLogger *clilogger.CLILogger, options *Options, hook } return executeBuildHook(outputLogger, options, hookIdentifier, argReplacements, postBuildHook, "post") - } func executeBuildHook(_ *clilogger.CLILogger, options *Options, hookIdentifier string, argReplacements map[string]string, buildHook string, hookName string) error { @@ -407,7 +427,6 @@ func executeBuildHook(_ *clilogger.CLILogger, options *Options, hookIdentifier s if options.Verbosity == VERBOSE { pterm.Info.Println(strings.Join(args, " ")) - } if !fs.DirExists(options.BinDirectory) { diff --git a/v2/pkg/commands/build/builder.go b/v2/pkg/commands/build/builder.go index 4840341c..6a220c53 100644 --- a/v2/pkg/commands/build/builder.go +++ b/v2/pkg/commands/build/builder.go @@ -8,8 +8,8 @@ import ( // Builder defines a builder that can build Wails applications type Builder interface { SetProjectData(projectData *project.Project) - BuildFrontend(*clilogger.CLILogger) error - CompileProject(*Options) error - OutputFilename(*Options) string + BuildFrontend(logger *clilogger.CLILogger) error + CompileProject(options *Options) error + OutputFilename(options *Options) string CleanUp() } diff --git a/v2/pkg/commands/build/nsis_installer.go b/v2/pkg/commands/build/nsis_installer.go index 11f1407a..820df2d1 100644 --- a/v2/pkg/commands/build/nsis_installer.go +++ b/v2/pkg/commands/build/nsis_installer.go @@ -41,7 +41,7 @@ func GenerateNSISInstaller(options *Options, amd64Binary string, arm64Binary str // Write the WebView2 SetupFile webviewSetup := buildassets.GetLocalPath(options.ProjectData, path.Join(nsisFolder, nsisWebView2SetupFile)) if dir := filepath.Dir(webviewSetup); !fs.DirExists(dir) { - if err := fs.MkDirs(dir, 0755); err != nil { + if err := fs.MkDirs(dir, 0o755); err != nil { return err } } @@ -92,7 +92,7 @@ func makeNSIS(options *Options, installerKind string, amd64Binary string, arm64B outputLogger := options.Logger outputLogger.Print(" - Building '%s' installer: ", installerKind) - var args = []string{} + args := []string{} if amd64Binary != "" { args = append(args, "-DARG_WAILS_AMD64_BINARY="+amd64Binary) } diff --git a/v2/pkg/commands/build/packager.go b/v2/pkg/commands/build/packager.go index 92ce37e9..54aea2e6 100644 --- a/v2/pkg/commands/build/packager.go +++ b/v2/pkg/commands/build/packager.go @@ -3,13 +3,15 @@ package build import ( "bytes" "fmt" - "github.com/leaanthony/winicon" - "github.com/tc-hib/winres" - "github.com/tc-hib/winres/version" "image" "os" "path/filepath" + "github.com/leaanthony/winicon" + "github.com/tc-hib/winres" + "github.com/tc-hib/winres/version" + "github.com/wailsapp/wails/v2/internal/project" + "github.com/jackmordaunt/icns" "github.com/pkg/errors" "github.com/wailsapp/wails/v2/pkg/buildassets" @@ -19,7 +21,6 @@ import ( // PackageProject packages the application func packageProject(options *Options, platform string) error { - var err error switch platform { case "darwin": @@ -41,7 +42,6 @@ func packageProject(options *Options, platform string) error { // cleanBinDirectory will remove an existing bin directory and recreate it func cleanBinDirectory(options *Options) error { - buildDirectory := options.BinDirectory // Clear out old builds @@ -53,7 +53,7 @@ func cleanBinDirectory(options *Options) error { } // Create clean directory - err := os.MkdirAll(buildDirectory, 0700) + err := os.MkdirAll(buildDirectory, 0o700) if err != nil { return err } @@ -62,7 +62,6 @@ func cleanBinDirectory(options *Options) error { } func packageApplicationForDarwin(options *Options) error { - var err error // Create directory structure @@ -73,12 +72,12 @@ func packageApplicationForDarwin(options *Options) error { contentsDirectory := filepath.Join(options.BinDirectory, bundlename, "/Contents") exeDir := filepath.Join(contentsDirectory, "/MacOS") - err = fs.MkDirs(exeDir, 0755) + err = fs.MkDirs(exeDir, 0o755) if err != nil { return err } resourceDir := filepath.Join(contentsDirectory, "/Resources") - err = fs.MkDirs(resourceDir, 0755) + err = fs.MkDirs(resourceDir, 0o755) if err != nil { return err } @@ -95,19 +94,26 @@ func packageApplicationForDarwin(options *Options) error { return err } - // Generate Icons - err = processApplicationIcon(options, resourceDir) + // Generate App Icon + err = processDarwinIcon(options.ProjectData, "appicon", resourceDir, "iconfile") if err != nil { return err } + // Generate FileAssociation Icons + for _, fileAssociation := range options.ProjectData.Info.FileAssociations { + err = processDarwinIcon(options.ProjectData, fileAssociation.IconName, resourceDir, "") + if err != nil { + return err + } + } + options.CompiledBinary = packedBinaryPath return nil } func processPList(options *Options, contentsDirectory string) error { - sourcePList := "Info.plist" if options.Mode == Dev { // Use Info.dev.plist if using build mode @@ -121,11 +127,11 @@ func processPList(options *Options, contentsDirectory string) error { } targetFile := filepath.Join(contentsDirectory, "Info.plist") - return os.WriteFile(targetFile, content, 0644) + return os.WriteFile(targetFile, content, 0o644) } -func processApplicationIcon(options *Options, resourceDir string) (err error) { - appIcon, err := buildassets.ReadFile(options.ProjectData, "appicon.png") +func processDarwinIcon(projectData *project.Project, iconName string, resourceDir string, destIconName string) (err error) { + appIcon, err := buildassets.ReadFile(projectData, iconName+".png") if err != nil { return err } @@ -135,11 +141,14 @@ func processApplicationIcon(options *Options, resourceDir string) (err error) { return err } - tgtBundle := filepath.Join(resourceDir, "iconfile.icns") + if destIconName == "" { + destIconName = iconName + } + + tgtBundle := filepath.Join(resourceDir, destIconName+".icns") dest, err := os.Create(tgtBundle) if err != nil { return err - } defer func() { err = dest.Close() @@ -151,13 +160,21 @@ func processApplicationIcon(options *Options, resourceDir string) (err error) { } func packageApplicationForWindows(options *Options) error { - // Generate icon + // Generate app icon var err error - err = generateIcoFile(options) + err = generateIcoFile(options, "appicon", "icon") if err != nil { return err } + // Generate FileAssociation Icons + for _, fileAssociation := range options.ProjectData.Info.FileAssociations { + err = generateIcoFile(options, fileAssociation.IconName, "") + if err != nil { + return err + } + } + // Create syso file err = compileResources(options) if err != nil { @@ -171,21 +188,26 @@ func packageApplicationForLinux(_ *Options) error { return nil } -func generateIcoFile(options *Options) error { - content, err := buildassets.ReadFile(options.ProjectData, "appicon.png") +func generateIcoFile(options *Options, iconName string, destIconName string) error { + content, err := buildassets.ReadFile(options.ProjectData, iconName+".png") if err != nil { return err } + + if destIconName == "" { + destIconName = iconName + } + // Check ico file exists already - icoFile := buildassets.GetLocalPath(options.ProjectData, "windows/icon.ico") + icoFile := buildassets.GetLocalPath(options.ProjectData, "windows/"+destIconName+".ico") if !fs.FileExists(icoFile) { if dir := filepath.Dir(icoFile); !fs.DirExists(dir) { - if err := fs.MkDirs(dir, 0755); err != nil { + if err := fs.MkDirs(dir, 0o755); err != nil { return err } } - output, err := os.OpenFile(icoFile, os.O_CREATE|os.O_WRONLY, 0644) + output, err := os.OpenFile(icoFile, os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return err } @@ -200,13 +222,12 @@ func generateIcoFile(options *Options) error { } func compileResources(options *Options) error { - currentDir, err := os.Getwd() if err != nil { return err } defer func() { - os.Chdir(currentDir) + _ = os.Chdir(currentDir) }() windowsDir := filepath.Join(options.ProjectData.GetBuildDir(), "windows") err = os.Chdir(windowsDir) diff --git a/v2/pkg/logger/filelogger.go b/v2/pkg/logger/filelogger.go index 5cf68fc1..954c46f5 100644 --- a/v2/pkg/logger/filelogger.go +++ b/v2/pkg/logger/filelogger.go @@ -19,7 +19,7 @@ func NewFileLogger(filename string) Logger { // Print works like Sprintf. func (l *FileLogger) Print(message string) { - f, err := os.OpenFile(l.filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + f, err := os.OpenFile(l.filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { log.Fatal(err) } diff --git a/v2/pkg/mac/login_darwin.go b/v2/pkg/mac/login_darwin.go index 2ff49be8..b2390e30 100644 --- a/v2/pkg/mac/login_darwin.go +++ b/v2/pkg/mac/login_darwin.go @@ -33,7 +33,7 @@ func StartAtLogin(enabled bool) error { } _, stde, err := shell.RunCommand("/tmp", "osascript", "-e", command) if err != nil { - errors.Wrap(err, stde) + return errors.Wrap(err, stde) } return nil } diff --git a/v2/pkg/mac/notification_darwin.go b/v2/pkg/mac/notification_darwin.go index a06ecb53..b7743df9 100644 --- a/v2/pkg/mac/notification_darwin.go +++ b/v2/pkg/mac/notification_darwin.go @@ -24,7 +24,7 @@ func ShowNotification(title string, subtitle string, message string, sound strin } _, stde, err := shell.RunCommand("/tmp", "osascript", "-e", command) if err != nil { - errors.Wrap(err, stde) + return errors.Wrap(err, stde) } return nil } diff --git a/v2/pkg/menu/callback.go b/v2/pkg/menu/callback.go index fe616036..a02664ac 100644 --- a/v2/pkg/menu/callback.go +++ b/v2/pkg/menu/callback.go @@ -2,7 +2,7 @@ package menu type CallbackData struct { MenuItem *MenuItem - //ContextData string + // ContextData string } type Callback func(*CallbackData) diff --git a/v2/pkg/menu/colours/colours.go b/v2/pkg/menu/colours/colours.go index 28564a09..5fb74eab 100644 --- a/v2/pkg/menu/colours/colours.go +++ b/v2/pkg/menu/colours/colours.go @@ -36,7 +36,6 @@ type InputCol struct { var Template string func main() { - var Cols []InputCol resp, err := http.Get("https://jonasjacek.github.io/colors/data.json") @@ -62,5 +61,8 @@ func main() { if err != nil { log.Fatal(err) } - os.WriteFile(filepath.Join("..", "cols.go"), buffer.Bytes(), 0755) + err = os.WriteFile(filepath.Join("..", "cols.go"), buffer.Bytes(), 0o755) + if err != nil { + log.Fatal(err) + } } diff --git a/v2/pkg/menu/keys/keys.go b/v2/pkg/menu/keys/keys.go index fa8027a3..961edab2 100644 --- a/v2/pkg/menu/keys/keys.go +++ b/v2/pkg/menu/keys/keys.go @@ -16,7 +16,7 @@ const ( // ShiftKey represents the shift key on all systems ShiftKey Modifier = "shift" // SuperKey represents Command on Mac and the Windows key on the other platforms - //SuperKey Modifier = "super" + // SuperKey Modifier = "super" // ControlKey represents the control key on all systems ControlKey Modifier = "ctrl" ) diff --git a/v2/pkg/menu/keys/parser.go b/v2/pkg/menu/keys/parser.go index 91a05783..6e8e1237 100644 --- a/v2/pkg/menu/keys/parser.go +++ b/v2/pkg/menu/keys/parser.go @@ -11,7 +11,6 @@ import ( var namedKeys = slicer.String([]string{"backspace", "tab", "return", "enter", "escape", "left", "right", "up", "down", "space", "delete", "home", "end", "page up", "page down", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23", "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31", "f32", "f33", "f34", "f35", "numlock"}) func parseKey(key string) (string, bool) { - // Lowercase! key = strings.ToLower(key) @@ -38,11 +37,9 @@ func parseKey(key string) (string, bool) { } return "", false - } func Parse(shortcut string) (*Accelerator, error) { - var result Accelerator // Split the shortcut by + diff --git a/v2/pkg/menu/keys/stringify.go b/v2/pkg/menu/keys/stringify.go index ccc8c5e9..92498f5d 100644 --- a/v2/pkg/menu/keys/stringify.go +++ b/v2/pkg/menu/keys/stringify.go @@ -1,8 +1,9 @@ package keys import ( - "github.com/leaanthony/slicer" "strings" + + "github.com/leaanthony/slicer" ) var modifierStringMap = map[string]map[Modifier]string{ @@ -11,21 +12,21 @@ var modifierStringMap = map[string]map[Modifier]string{ ControlKey: "Ctrl", OptionOrAltKey: "Alt", ShiftKey: "Shift", - //SuperKey: "Win", + // SuperKey: "Win", }, "darwin": { CmdOrCtrlKey: "Cmd", ControlKey: "Ctrl", OptionOrAltKey: "Option", ShiftKey: "Shift", - //SuperKey: "Cmd", + // SuperKey: "Cmd", }, "linux": { CmdOrCtrlKey: "Ctrl", ControlKey: "Ctrl", OptionOrAltKey: "Alt", ShiftKey: "Shift", - //SuperKey: "Super", + // SuperKey: "Super", }, } diff --git a/v2/pkg/menu/menu.go b/v2/pkg/menu/menu.go index 819939bb..86acbd1d 100644 --- a/v2/pkg/menu/menu.go +++ b/v2/pkg/menu/menu.go @@ -59,8 +59,7 @@ func (m *Menu) Prepend(item *MenuItem) { } func NewMenuFromItems(first *MenuItem, rest ...*MenuItem) *Menu { - - var result = NewMenu() + result := NewMenu() result.Append(first) for _, item := range rest { result.Append(item) diff --git a/v2/pkg/menu/menuitem.go b/v2/pkg/menu/menuitem.go index f6ea681d..264b2ebd 100644 --- a/v2/pkg/menu/menuitem.go +++ b/v2/pkg/menu/menuitem.go @@ -23,7 +23,7 @@ type MenuItem struct { // Checked indicates if the item is selected (used by Checkbox and Radio types only) Checked bool // Submenu contains a list of menu items that will be shown as a submenu - //SubMenu []*MenuItem `json:"SubMenu,omitempty"` + // SubMenu []*MenuItem `json:"SubMenu,omitempty"` SubMenu *Menu // Callback function when menu clicked @@ -106,7 +106,6 @@ func (m *MenuItem) removeChild(item *MenuItem) { // menu. If there is no parent menu (we are a top level menu) then false is // returned func (m *MenuItem) InsertAfter(item *MenuItem) bool { - // We need to find my parent if m.parent == nil { return false @@ -120,7 +119,6 @@ func (m *MenuItem) InsertAfter(item *MenuItem) bool { // menu. If there is no parent menu (we are a top level menu) then false is // returned func (m *MenuItem) InsertBefore(item *MenuItem) bool { - // We need to find my parent if m.parent == nil { return false @@ -134,8 +132,8 @@ func (m *MenuItem) InsertBefore(item *MenuItem) bool { // in this item's submenu. If we are not a submenu, // then something bad has happened :/ func (m *MenuItem) insertNewItemAfterGivenItem(target *MenuItem, - newItem *MenuItem) bool { - + newItem *MenuItem, +) bool { if !m.isSubMenu() { return false } @@ -154,8 +152,8 @@ func (m *MenuItem) insertNewItemAfterGivenItem(target *MenuItem, // target in this item's submenu. If we are not a submenu, then something bad // has happened :/ func (m *MenuItem) insertNewItemBeforeGivenItem(target *MenuItem, - newItem *MenuItem) bool { - + newItem *MenuItem, +) bool { if !m.isSubMenu() { return false } @@ -176,7 +174,6 @@ func (m *MenuItem) isSubMenu() bool { // getItemIndex returns the index of the given target relative to this menu func (m *MenuItem) getItemIndex(target *MenuItem) int { - // This should only be called on submenus if !m.isSubMenu() { return -1 @@ -196,7 +193,6 @@ func (m *MenuItem) getItemIndex(target *MenuItem) int { // the given index // Credit: https://stackoverflow.com/a/61822301 func (m *MenuItem) insertItemAtIndex(index int, target *MenuItem) bool { - // If index is OOB, return false if index > len(m.SubMenu.Items) { return false diff --git a/v2/pkg/menu/menuroles.go b/v2/pkg/menu/menuroles.go index e6b15b24..bcc0657f 100644 --- a/v2/pkg/menu/menuroles.go +++ b/v2/pkg/menu/menuroles.go @@ -11,29 +11,29 @@ const ( AppMenuRole Role = 1 EditMenuRole = 2 WindowMenuRole = 3 - //AboutRole Role = "about" - //UndoRole Role = "undo" - //RedoRole Role = "redo" - //CutRole Role = "cut" - //CopyRole Role = "copy" - //PasteRole Role = "paste" - //PasteAndMatchStyleRole Role = "pasteAndMatchStyle" - //SelectAllRole Role = "selectAll" - //DeleteRole Role = "delete" - //MinimizeRole Role = "minimize" - //QuitRole Role = "quit" - //TogglefullscreenRole Role = "togglefullscreen" - //FileMenuRole Role = "fileMenu" - //ViewMenuRole Role = "viewMenu" - //WindowMenuRole Role = "windowMenu" - //HideRole Role = "hide" - //HideOthersRole Role = "hideOthers" - //UnhideRole Role = "unhide" - //FrontRole Role = "front" - //ZoomRole Role = "zoom" - //WindowSubMenuRole Role = "windowSubMenu" - //HelpSubMenuRole Role = "helpSubMenu" - //SeparatorItemRole Role = "separatorItem" + // AboutRole Role = "about" + // UndoRole Role = "undo" + // RedoRole Role = "redo" + // CutRole Role = "cut" + // CopyRole Role = "copy" + // PasteRole Role = "paste" + // PasteAndMatchStyleRole Role = "pasteAndMatchStyle" + // SelectAllRole Role = "selectAll" + // DeleteRole Role = "delete" + // MinimizeRole Role = "minimize" + // QuitRole Role = "quit" + // TogglefullscreenRole Role = "togglefullscreen" + // FileMenuRole Role = "fileMenu" + // ViewMenuRole Role = "viewMenu" + // WindowMenuRole Role = "windowMenu" + // HideRole Role = "hide" + // HideOthersRole Role = "hideOthers" + // UnhideRole Role = "unhide" + // FrontRole Role = "front" + // ZoomRole Role = "zoom" + // WindowSubMenuRole Role = "windowSubMenu" + // HelpSubMenuRole Role = "helpSubMenu" + // SeparatorItemRole Role = "separatorItem" ) /* diff --git a/v2/pkg/menu/styledlabel.go b/v2/pkg/menu/styledlabel.go index 11a0254c..1e996b97 100644 --- a/v2/pkg/menu/styledlabel.go +++ b/v2/pkg/menu/styledlabel.go @@ -29,24 +29,31 @@ type StyledText struct { func (s *StyledText) Bold() bool { return s.Style&Bold == Bold } + func (s *StyledText) Faint() bool { return s.Style&Faint == Faint } + func (s *StyledText) Italic() bool { return s.Style&Italic == Italic } + func (s *StyledText) Blinking() bool { return s.Style&Blinking == Blinking } + func (s *StyledText) Inversed() bool { return s.Style&Inversed == Inversed } + func (s *StyledText) Invisible() bool { return s.Style&Invisible == Invisible } + func (s *StyledText) Underlined() bool { return s.Style&Underlined == Underlined } + func (s *StyledText) Strikethrough() bool { return s.Style&Strikethrough == Strikethrough } diff --git a/v2/pkg/menu/tray.go b/v2/pkg/menu/tray.go index 7554795a..c8728f1f 100644 --- a/v2/pkg/menu/tray.go +++ b/v2/pkg/menu/tray.go @@ -2,7 +2,6 @@ package menu // TrayMenu are the options type TrayMenu struct { - // Label is the text we wish to display in the tray Label string @@ -27,7 +26,7 @@ type TrayMenu struct { Tooltip string // Callback function when menu clicked - //Click Callback `json:"-"` + // Click Callback `json:"-"` // Disabled makes the item unselectable Disabled bool diff --git a/v2/pkg/options/linux/linux.go b/v2/pkg/options/linux/linux.go index 062119cb..797450c2 100644 --- a/v2/pkg/options/linux/linux.go +++ b/v2/pkg/options/linux/linux.go @@ -28,6 +28,11 @@ type Options struct { // - WebviewGpuPolicyAlways // - WebviewGpuPolicyOnDemand // - WebviewGpuPolicyNever + // + // Due to https://github.com/wailsapp/wails/issues/2977, if options.Linux is nil + // in the call to wails.Run(), WebviewGpuPolicy is set by default to WebviewGpuPolicyNever. + // Client code may override this behavior by passing a non-nil Options and set + // WebviewGpuPolicy as needed. WebviewGpuPolicy WebviewGpuPolicy // ProgramName is used to set the program's name for the window manager via GTK's g_set_prgname(). diff --git a/v2/pkg/options/mac/mac.go b/v2/pkg/options/mac/mac.go index 9838b145..9d2ccc95 100644 --- a/v2/pkg/options/mac/mac.go +++ b/v2/pkg/options/mac/mac.go @@ -21,7 +21,9 @@ type Options struct { WebviewIsTransparent bool WindowIsTranslucent bool Preferences *Preferences - //ActivationPolicy ActivationPolicy - About *AboutInfo - //URLHandlers map[string]func(string) + // ActivationPolicy ActivationPolicy + About *AboutInfo + OnFileOpen func(filePath string) `json:"-"` + OnUrlOpen func(filePath string) `json:"-"` + // URLHandlers map[string]func(string) } diff --git a/v2/pkg/options/mac/preferences.go b/v2/pkg/options/mac/preferences.go index 2c690e7f..0749ccb1 100644 --- a/v2/pkg/options/mac/preferences.go +++ b/v2/pkg/options/mac/preferences.go @@ -2,8 +2,10 @@ package mac import "github.com/leaanthony/u" -var Enabled = u.True -var Disabled = u.False +var ( + Enabled = u.True + Disabled = u.False +) // Preferences allows to set webkit preferences type Preferences struct { @@ -13,4 +15,7 @@ type Preferences struct { // A Boolean value that indicates whether to allow people to select or otherwise interact with text. // Set to true by default. TextInteractionEnabled u.Bool + // A Boolean value that indicates whether a web view can display content full screen. + // Set to false by default + FullscreenEnabled u.Bool } diff --git a/v2/pkg/options/mac/titlebar.go b/v2/pkg/options/mac/titlebar.go index c18c4eea..51e0832c 100644 --- a/v2/pkg/options/mac/titlebar.go +++ b/v2/pkg/options/mac/titlebar.go @@ -41,7 +41,6 @@ func TitleBarHidden() *TitleBar { // TitleBarHiddenInset results in a hidden title bar with an alternative look where // the traffic light buttons are slightly more inset from the window edge. func TitleBarHiddenInset() *TitleBar { - return &TitleBar{ TitlebarAppearsTransparent: true, HideTitle: true, @@ -50,5 +49,4 @@ func TitleBarHiddenInset() *TitleBar { UseToolbar: true, HideToolbarSeparator: true, } - } diff --git a/v2/pkg/options/options.go b/v2/pkg/options/options.go index b64befe1..66d56cea 100644 --- a/v2/pkg/options/options.go +++ b/v2/pkg/options/options.go @@ -5,6 +5,8 @@ import ( "html" "io/fs" "net/http" + "os" + "path/filepath" "runtime" "github.com/wailsapp/wails/v2/pkg/options/assetserver" @@ -26,8 +28,7 @@ const ( Fullscreen WindowStartState = 3 ) -type Experimental struct { -} +type Experimental struct{} // App contains options for creating the App type App struct { @@ -62,6 +63,7 @@ type App struct { OnShutdown func(ctx context.Context) `json:"-"` OnBeforeClose func(ctx context.Context) (prevent bool) `json:"-"` Bind []interface{} + EnumBind []interface{} WindowStartState WindowStartState // ErrorFormatter overrides the formatting of errors returned by backend methods @@ -82,6 +84,8 @@ type App struct { // services of Apple and Microsoft. EnableFraudulentWebsiteDetection bool + SingleInstanceLock *SingleInstanceLock + Windows *windows.Options Mac *mac.Options Linux *linux.Options @@ -165,6 +169,30 @@ func MergeDefaults(appoptions *App) { processDragOptions(appoptions) } +type SingleInstanceLock struct { + // uniqueId that will be used for setting up messaging between instances + UniqueId string + OnSecondInstanceLaunch func(secondInstanceData SecondInstanceData) +} + +type SecondInstanceData struct { + Args []string + WorkingDirectory string +} + +func NewSecondInstanceData() (*SecondInstanceData, error) { + ex, err := os.Executable() + if err != nil { + return nil, err + } + workingDirectory := filepath.Dir(ex) + + return &SecondInstanceData{ + Args: os.Args[1:], + WorkingDirectory: workingDirectory, + }, nil +} + func processMenus(appoptions *App) { switch runtime.GOOS { case "darwin": diff --git a/v2/pkg/options/windows/windows.go b/v2/pkg/options/windows/windows.go index 7adf0bf7..39b91ee8 100644 --- a/v2/pkg/options/windows/windows.go +++ b/v2/pkg/options/windows/windows.go @@ -36,7 +36,7 @@ const ( ) func RGB(r, g, b uint8) int32 { - var col = int32(b) + col := int32(b) col = col<<8 | int32(g) col = col<<8 | int32(r) return col @@ -68,6 +68,8 @@ type Options struct { IsZoomControlEnabled bool ZoomFactor float64 + DisablePinchZoom bool + // Disable all window decorations in Frameless mode, which means no "Aero Shadow" and no "Rounded Corner" will be shown. // "Rounded Corners" are only available on Windows 11. DisableFramelessWindowDecorations bool diff --git a/v2/pkg/runtime/dialog.go b/v2/pkg/runtime/dialog.go index d53a89c1..16ae659e 100644 --- a/v2/pkg/runtime/dialog.go +++ b/v2/pkg/runtime/dialog.go @@ -3,6 +3,7 @@ package runtime import ( "context" "fmt" + "github.com/wailsapp/wails/v2/internal/frontend" "github.com/wailsapp/wails/v2/internal/fs" ) diff --git a/v2/pkg/runtime/events.go b/v2/pkg/runtime/events.go index 493d8116..84aff7d7 100644 --- a/v2/pkg/runtime/events.go +++ b/v2/pkg/runtime/events.go @@ -10,7 +10,7 @@ func EventsOn(ctx context.Context, eventName string, callback func(optionalData return events.On(eventName, callback) } -// EventsOff unregisters a listener for the given event name, optionally multiple listeneres can be unregistered via `additionalEventNames` +// EventsOff unregisters a listener for the given event name, optionally multiple listeners can be unregistered via `additionalEventNames` func EventsOff(ctx context.Context, eventName string, additionalEventNames ...string) { events := getEvents(ctx) events.Off(eventName) @@ -22,7 +22,7 @@ func EventsOff(ctx context.Context, eventName string, additionalEventNames ...st } } -// EventsOff unregisters a listener for the given event name, optionally multiple listeneres can be unregistered via `additionalEventNames` +// EventsOff unregisters a listener for the given event name, optionally multiple listeners can be unregistered via `additionalEventNames` func EventsOffAll(ctx context.Context) { events := getEvents(ctx) events.OffAll() diff --git a/v2/pkg/runtime/log.go b/v2/pkg/runtime/log.go index 4d3f56d3..3c2756f0 100644 --- a/v2/pkg/runtime/log.go +++ b/v2/pkg/runtime/log.go @@ -3,6 +3,7 @@ package runtime import ( "context" "fmt" + "github.com/wailsapp/wails/v2/pkg/logger" ) diff --git a/v2/pkg/runtime/menu.go b/v2/pkg/runtime/menu.go index 176c9bb1..09bd640c 100644 --- a/v2/pkg/runtime/menu.go +++ b/v2/pkg/runtime/menu.go @@ -2,6 +2,7 @@ package runtime import ( "context" + "github.com/wailsapp/wails/v2/pkg/menu" ) diff --git a/v2/pkg/runtime/runtime.go b/v2/pkg/runtime/runtime.go index 4702b439..6de5ea79 100644 --- a/v2/pkg/runtime/runtime.go +++ b/v2/pkg/runtime/runtime.go @@ -27,6 +27,7 @@ func getFrontend(ctx context.Context) frontend.Frontend { log.Fatalf("cannot call '%s': %s", funcName, contextError) return nil } + func getLogger(ctx context.Context) *logger.Logger { if ctx == nil { pc, _, _, _ := goruntime.Caller(1) diff --git a/v2/pkg/runtime/screen.go b/v2/pkg/runtime/screen.go index d92ed830..af8fb626 100644 --- a/v2/pkg/runtime/screen.go +++ b/v2/pkg/runtime/screen.go @@ -1,7 +1,10 @@ package runtime -import "context" -import "github.com/wailsapp/wails/v2/internal/frontend" +import ( + "context" + + "github.com/wailsapp/wails/v2/internal/frontend" +) type Screen = frontend.Screen diff --git a/v2/pkg/templates/generate/generate.go b/v2/pkg/templates/generate/generate.go index 3b01e5f2..6842dc19 100644 --- a/v2/pkg/templates/generate/generate.go +++ b/v2/pkg/templates/generate/generate.go @@ -159,7 +159,6 @@ var templates = []*template{ } func main() { - rebuildRuntime() for _, t := range templates { diff --git a/v2/pkg/templates/templates.go b/v2/pkg/templates/templates.go index d982454d..9b42ef36 100644 --- a/v2/pkg/templates/templates.go +++ b/v2/pkg/templates/templates.go @@ -72,7 +72,6 @@ type Options struct { // Template holds data relating to a template // including the metadata stored in template.json type Template struct { - // Template details Name string `json:"name"` ShortName string `json:"shortname"` @@ -100,7 +99,6 @@ func parseTemplate(template gofs.FS) (Template, error) { // List returns the list of available templates func List() ([]Template, error) { - // If the cache isn't loaded, load it if templateCache == nil { err := loadTemplateCache() @@ -114,7 +112,6 @@ func List() ([]Template, error) { // getTemplateByShortname returns the template with the given short name func getTemplateByShortname(shortname string) (Template, error) { - var result Template // If the cache isn't loaded, load it @@ -136,7 +133,6 @@ func getTemplateByShortname(shortname string) (Template, error) { // Loads the template cache func loadTemplateCache() error { - templatesFS, err := debme.FS(templates, "templates") if err != nil { return err @@ -309,11 +305,9 @@ func gitclone(options *Options) (string, error) { _, err = git.PlainClone(dirname, false, cloneOption) return dirname, err - } func generateIDEFiles(options *Options) error { - switch options.IDE { case "vscode": return generateVSCodeFiles(options) @@ -361,7 +355,6 @@ func generateVSCodeFiles(options *Options) error { options: options, } return installIDEFiles(ideoptions) - } func installIDEFiles(o ideOptions) error { diff --git a/v2/pkg/templates/templates/preact/frontend/src/assets/preact.svg b/v2/pkg/templates/templates/preact/frontend/src/assets/preact.svg index 8d4155b1..23433fcf 100644 --- a/v2/pkg/templates/templates/preact/frontend/src/assets/preact.svg +++ b/v2/pkg/templates/templates/preact/frontend/src/assets/preact.svg @@ -1,10 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/v2/tools/release/release.go b/v2/tools/release/release.go index dc6299ed..4178fcc9 100644 --- a/v2/tools/release/release.go +++ b/v2/tools/release/release.go @@ -33,7 +33,7 @@ func updateVersion() string { minorVersion++ vsplit[len(vsplit)-1] = strconv.Itoa(minorVersion) newVersion := strings.Join(vsplit, ".") - err = os.WriteFile(versionFile, []byte(newVersion), 0755) + err = os.WriteFile(versionFile, []byte(newVersion), 0o755) checkError(err) return newVersion } @@ -68,7 +68,7 @@ func main() { newVersion = os.Args[1] currentVersion, err := os.ReadFile(versionFile) checkError(err) - err = os.WriteFile(versionFile, []byte(newVersion), 0755) + err = os.WriteFile(versionFile, []byte(newVersion), 0o755) checkError(err) isPointRelease = IsPointRelease(string(currentVersion), newVersion) } else { @@ -89,7 +89,7 @@ func main() { // Add the new version to the top of the changelog newChangelog := changelogSplit[0] + "## [Unreleased]\n\n## " + newVersion + " - " + today + changelogSplit[1] // Write the changelog back - err = os.WriteFile("src/pages/changelog.mdx", []byte(newChangelog), 0755) + err = os.WriteFile("src/pages/changelog.mdx", []byte(newChangelog), 0o755) checkError(err) if !isPointRelease { @@ -112,7 +112,7 @@ func main() { versions = versions[0 : len(versions)-1] newVersions, err := json.Marshal(&versions) checkError(err) - err = os.WriteFile("versions.json", newVersions, 0755) + err = os.WriteFile("versions.json", newVersions, 0o755) checkError(err) s.ECHO("Removing old version: " + oldestVersion) diff --git a/website/docs/community/showcase/espstudio.mdx b/website/docs/community/showcase/espstudio.mdx new file mode 100644 index 00000000..44db858f --- /dev/null +++ b/website/docs/community/showcase/espstudio.mdx @@ -0,0 +1,13 @@ +# ESP Studio + +```mdx-code-block +

+ +
+

+``` + +[ESP Studio](https://github.com/torabian/esp-studio) - Cross platform, Desktop, Cloud, and Embedded software +for controlling ESP/Arduino devices, and building complex IOT workflows and control systems diff --git a/website/docs/community/showcase/snippetexpander.mdx b/website/docs/community/showcase/snippetexpander.mdx new file mode 100644 index 00000000..1f9fb615 --- /dev/null +++ b/website/docs/community/showcase/snippetexpander.mdx @@ -0,0 +1,27 @@ +# Snippet Expander + +```mdx-code-block +

+ +
+ Screenshot of Snippet Expander's Select Snippet window +

+

+ +
+ Screenshot of Snippet Expander's Add Snippet screen +

+

+ +
+ Screenshot of Snippet Expander's Search & Paste window +

+``` + +[Snippet Expander](https://snippetexpander.org) is "Your little expandable text snippets helper", for Linux. + +Snippet Expander comprises of a GUI application built with Wails for managing snippets and settings, with a Search & Paste window mode for quickly selecting and pasting a snippet. + +The Wails based GUI, go-lang CLI and vala-lang auto expander daemon all communicate with a go-lang daemon via D-Bus. The daemon does the majority of the work, managing the database of snippets and common settings, and providing services for expanding and pasting snippets etc. + +Check out the [source code](https://git.sr.ht/~ianmjones/snippetexpander/tree/trunk/item/cmd/snippetexpandergui/app.go#L38) to see how the Wails app sends messages from the UI to the backend that are then sent to the daemon, and subscribes to a D-Bus event to monitor changes to snippets via another instance of the app or CLI and show them instantly in the UI via a Wails event. diff --git a/website/docs/community/showcase/tinyrdm.mdx b/website/docs/community/showcase/tinyrdm.mdx new file mode 100644 index 00000000..cd9cec8b --- /dev/null +++ b/website/docs/community/showcase/tinyrdm.mdx @@ -0,0 +1,10 @@ +# Tiny RDM + +```mdx-code-block +

+ +
+

+``` + +The [Tiny RDM](https://redis.tinycraft.cc/) application is an open-source, modern lightweight Redis GUI. It has a beautful UI, intuitive Redis database management, and compatible with Windows, Mac, and Linux. It provides visual key-value data operations, supports various data decoding and viewing options, built-in console for executing commands, slow log queries and more. diff --git a/website/docs/community/templates.mdx b/website/docs/community/templates.mdx index 6ff8344d..98726c60 100644 --- a/website/docs/community/templates.mdx +++ b/website/docs/community/templates.mdx @@ -40,6 +40,7 @@ If you are unsure about a template, inspect `package.json` and `wails.json` for - [wails-react-template](https://github.com/AlienRecall/wails-react-template) - A template using reactjs - [wails-react-template](https://github.com/flin7/wails-react-template) - A minimal template for React that supports live development - [wails-template-nextjs](https://github.com/LGiki/wails-template-nextjs) - A template using Next.js and TypeScript +- [wails-template-nextjs-app-router](https://github.com/thisisvk-in/wails-template-nextjs-app-router) - A template using Next.js and TypeScript with App router - [wails-vite-react-ts-tailwind-template](https://github.com/hotafrika/wails-vite-react-ts-tailwind-template) - A template for React + TypeScript + Vite + TailwindCSS - [wails-vite-react-ts-tailwind-shadcnui-template](https://github.com/Mahcks/wails-vite-react-tailwind-shadcnui-ts) - A template with Vite, React, TypeScript, TailwindCSS, and shadcn/ui @@ -61,6 +62,10 @@ If you are unsure about a template, inspect `package.json` and `wails.json` for - [wails-elm-template](https://github.com/benjamin-thomas/wails-elm-template) - Develop your GUI app with functional programming and a **snappy** hot-reload setup :tada: :rocket: - [wails-template-elm-tailwind](https://github.com/rnice01/wails-template-elm-tailwind) - Combine the powers :muscle: of Elm + Tailwind CSS + Wails! Hot reloading supported. +## HTMX + +- [wails-htmx-templ-chi-tailwind](https://github.com/PylotLight/wails-hmtx-templ-template) - Use a unique combination of pure htmx for interactivity plus templ for creating components and forms + ## Pure JavaScript (Vanilla) - [wails-pure-js-template](https://github.com/KiddoV/wails-pure-js-template) - A template with nothing but just basic JavaScript, HTML, and CSS diff --git a/website/docs/guides/application-development.mdx b/website/docs/guides/application-development.mdx index 9d04fe91..78a6df3b 100644 --- a/website/docs/guides/application-development.mdx +++ b/website/docs/guides/application-development.mdx @@ -144,6 +144,65 @@ func main() { } ``` +Also you might want to use Enums in your structs and have models for them on frontend. +In that case you should create array that will contain all possible enum values, instrument enum type and bind it to the app: + +```go {16-18} title="app.go" +type Weekday string + +const ( + Sunday Weekday = "Sunday" + Monday Weekday = "Monday" + Tuesday Weekday = "Tuesday" + Wednesday Weekday = "Wednesday" + Thursday Weekday = "Thursday" + Friday Weekday = "Friday" + Saturday Weekday = "Saturday" +) + +var AllWeekdays = []struct { + Value Weekday + TSName string +}{ + {Sunday, "SUNDAY"}, + {Monday, "MONDAY"}, + {Tuesday, "TUESDAY"}, + {Wednesday, "WEDNESDAY"}, + {Thursday, "THURSDAY"}, + {Friday, "FRIDAY"}, + {Saturday, "SATURDAY"}, +} +``` + +In the main application configuration, the `EnumBind` key is where we can tell Wails what we want to bind enums as well: + +```go {11-13} title="main.go" +func main() { + + app := NewApp() + + err := wails.Run(&options.App{ + Title: "My App", + Width: 800, + Height: 600, + OnStartup: app.startup, + OnShutdown: app.shutdown, + Bind: []interface{}{ + app, + }, + EnumBind: []interface{}{ + AllWeekdays, + }, + }) + if err != nil { + log.Fatal(err) + } +} + +``` + +This will add missing enums to your `model.ts` file. + More information on Binding can be found [here](../howdoesitwork.mdx#method-binding). ## Application Menu diff --git a/website/docs/guides/custom-protocol-schemes.mdx b/website/docs/guides/custom-protocol-schemes.mdx new file mode 100644 index 00000000..c56634f0 --- /dev/null +++ b/website/docs/guides/custom-protocol-schemes.mdx @@ -0,0 +1,189 @@ +# Custom Protocol Scheme association + +Custom Protocols feature allows you to associate specific custom protocol with your app so that when users open links with this protocol, +your app is launched to handle them. This can be particularly useful to connect your desktop app with your web app. +In this guide, we'll walk through the steps to implement custom protocols in Wails app. + + +## Set Up Custom Protocol Schemes Association: +To set up custom protocol, you need to modify your application's wails.json file. +In "info" section add a "protocols" section specifying the protocols your app should be associated with. + +For example: +```json +{ + "info": { + "protocols": [ + { + "scheme": "myapp", + "description": "My App Protocol", + "role": "Editor" + } + ] + } +} +``` + +| Property | Description | +|:------------|:--------------------------------------------------------------------------------------| +| scheme | Custom Protocol scheme. e.g. myapp | +| description | Windows-only. The description. | +| role | macOS-only. The app’s role with respect to the type. Corresponds to CFBundleTypeRole. | + +## Platform Specifics: + +### macOS +When you open custom protocol with your app, the system will launch your app and call the `OnUrlOpen` function in your Wails app. Example: +```go title="main.go" +func main() { + // Create application with options + err := wails.Run(&options.App{ + Title: "wails-open-file", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, + Mac: &mac.Options{ + OnUrlOpen: func(url string) { println(url) }, + }, + Bind: []interface{}{ + app, + }, + }) + + if err != nil { + println("Error:", err.Error()) + } +} +``` + + +### Windows +On Windows Custom Protocol Schemes is supported only with NSIS installer. During installation, the installer will create a +registry entry for your schemes. When you open url with your app, new instance of app is launched and url is passed +as argument to your app. To handle this you should parse command line arguments in your app. Example: +```go title="main.go" +func main() { + argsWithoutProg := os.Args[1:] + + if len(argsWithoutProg) != 0 { + println("launchArgs", argsWithoutProg) + } +} +``` + +You also can enable single instance lock for your app. In this case, when you open url with your app, new instance of app is not launched +and arguments are passed to already running instance. Check single instance lock guide for details. Example: +```go title="main.go" +func main() { + // Create application with options + err := wails.Run(&options.App{ + Title: "wails-open-file", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, + SingleInstanceLock: &options.SingleInstanceLock{ + UniqueId: "e3984e08-28dc-4e3d-b70a-45e961589cdc", + OnSecondInstanceLaunch: app.onSecondInstanceLaunch, + }, + Bind: []interface{}{ + app, + }, + }) +} +``` + +### Linux +Currently, Wails doesn't support bundling for Linux. So, you need to create file associations manually. +For example if you distribute your app as a .deb package, you can create file associations by adding required files in you bundle. +You can use [nfpm](https://nfpm.goreleaser.com/) to create .deb package for your app. + +1. Create a .desktop file for your app and specify file associations there (note that `%u` is important in Exec). Example: +```ini +[Desktop Entry] +Categories=Office +Exec=/usr/bin/wails-open-file %u +Icon=wails-open-file.png +Name=wails-open-file +Terminal=false +Type=Application +MimeType=x-scheme-handler/myapp; +``` + +2. Prepare postInstall/postRemove scripts for your package. Example: +```sh +# reload desktop database to load app in list of available +update-desktop-database /usr/share/applications +``` +3. Configure nfpm to use your scripts and files. Example: +```yaml +name: "wails-open-file" +arch: "arm64" +platform: "linux" +version: "1.0.0" +section: "default" +priority: "extra" +maintainer: "FooBarCorp " +description: "Sample Package" +vendor: "FooBarCorp" +homepage: "http://example.com" +license: "MIT" +contents: +- src: ../bin/wails-open-file + dst: /usr/bin/wails-open-file +- src: ./main.desktop + dst: /usr/share/applications/wails-open-file.desktop +- src: ../appicon.svg + dst: /usr/share/icons/hicolor/scalable/apps/wails-open-file.svg +# copy icons to Yaru theme as well. For some reason Ubuntu didn't pick up fileicons from hicolor theme +- src: ../appicon.svg + dst: /usr/share/icons/Yaru/scalable/apps/wails-open-file.svg +scripts: + postinstall: ./postInstall.sh + postremove: ./postRemove.sh +``` +6. Build your .deb package using nfpm: +```sh +nfpm pkg --packager deb --target . +``` +7. Now when your package is installed, your app will be associated with custom protocol scheme. When you open url with your app, +new instance of app is launched and file path is passed as argument to your app. +To handle this you should parse command line arguments in your app. Example: +```go title="main.go" +func main() { + argsWithoutProg := os.Args[1:] + + if len(argsWithoutProg) != 0 { + println("launchArgs", argsWithoutProg) + } +} +``` + +You also can enable single instance lock for your app. In this case, when you open url with your app, new instance of app is not launched +and arguments are passed to already running instance. Check single instance lock guide for details. Example: +```go title="main.go" +func main() { + // Create application with options + err := wails.Run(&options.App{ + Title: "wails-open-file", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, + SingleInstanceLock: &options.SingleInstanceLock{ + UniqueId: "e3984e08-28dc-4e3d-b70a-45e961589cdc", + OnSecondInstanceLaunch: app.onSecondInstanceLaunch, + }, + Bind: []interface{}{ + app, + }, + }) +} +``` diff --git a/website/docs/guides/file-association.mdx b/website/docs/guides/file-association.mdx new file mode 100644 index 00000000..71bbff37 --- /dev/null +++ b/website/docs/guides/file-association.mdx @@ -0,0 +1,228 @@ +# File Association + +File association feature allows you to associate specific file types with your app so that when users open those files, +your app is launched to handle them. This can be particularly useful for text editors, image viewers, or any application +that works with specific file formats. In this guide, we'll walk through the steps to implement file association in Wails app. + + +## Set Up File Association: +To set up file association, you need to modify your application's wails.json file. +In "info" section add a "fileAssociations" section specifying the file types your app should be associated with. + +For example: +```json +{ + "info": { + "fileAssociations": [ + { + "ext": "wails", + "name": "Wails", + "description": "Wails Application File", + "iconName": "wailsFileIcon", + "role": "Editor" + }, + { + "ext": "jpg", + "name": "JPEG", + "description": "Image File", + "iconName": "jpegFileIcon", + "role": "Editor" + } + ] + } +} +``` + +| Property | Description | +|:------------|:---------------------------------------------------------------------------------------------------------------------------------------------------| +| ext | The extension (minus the leading period). e.g. png | +| name | The name. e.g. PNG File | +| iconName | The icon name without extension. Icons should be located in build folder. Proper icons will be generated from .png file for both macOS and Windows | +| description | Windows-only. The description. It is displayed on the `Type` column on Windows Explorer. | +| role | macOS-only. The app’s role with respect to the type. Corresponds to CFBundleTypeRole. | + +## Platform Specifics: + +### macOS +When you open file (or files) with your app, the system will launch your app and call the `OnFileOpen` function in your Wails app. Example: +```go title="main.go" +func main() { + // Create application with options + err := wails.Run(&options.App{ + Title: "wails-open-file", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, + Mac: &mac.Options{ + OnFileOpen: func(filePaths []string) { println(filestring) }, + }, + Bind: []interface{}{ + app, + }, + }) + + if err != nil { + println("Error:", err.Error()) + } +} +``` + + +### Windows +On Windows file association is supported only with NSIS installer. During installation, the installer will create a +registry entry for your file associations. When you open file with your app, new instance of app is launched and file path is passed +as argument to your app. To handle this you should parse command line arguments in your app. Example: +```go title="main.go" +func main() { + argsWithoutProg := os.Args[1:] + + if len(argsWithoutProg) != 0 { + println("launchArgs", argsWithoutProg) + } +} +``` + +You also can enable single instance lock for your app. In this case, when you open file with your app, new instance of app is not launched +and arguments are passed to already running instance. Check single instance lock guide for details. Example: +```go title="main.go" +func main() { + // Create application with options + err := wails.Run(&options.App{ + Title: "wails-open-file", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, + SingleInstanceLock: &options.SingleInstanceLock{ + UniqueId: "e3984e08-28dc-4e3d-b70a-45e961589cdc", + OnSecondInstanceLaunch: app.onSecondInstanceLaunch, + }, + Bind: []interface{}{ + app, + }, + }) +} +``` + +### Linux +Currently, Wails doesn't support bundling for Linux. So, you need to create file associations manually. +For example if you distribute your app as a .deb package, you can create file associations by adding required files in you bundle. +You can use [nfpm](https://nfpm.goreleaser.com/) to create .deb package for your app. + +1. Create a .desktop file for your app and specify file associations there. Example: +```ini +[Desktop Entry] +Categories=Office +Exec=/usr/bin/wails-open-file %u +Icon=wails-open-file.png +Name=wails-open-file +Terminal=false +Type=Application +MimeType=application/x-wails;application/x-test +``` + +2. Create mime types file. Example: +```xml + + + + Wails Application File + + + +``` + +3. Create icons for your file types. SVG icons are recommended. +4. Prepare postInstall/postRemove scripts for your package. Example: +```sh +# reload mime types to register file associations +update-mime-database /usr/share/mime +# reload desktop database to load app in list of available +update-desktop-database /usr/share/applications +# update icons +update-icon-caches /usr/share/icons/* +``` +5. Configure nfpm to use your scripts and files. Example: +```yaml +name: "wails-open-file" +arch: "arm64" +platform: "linux" +version: "1.0.0" +section: "default" +priority: "extra" +maintainer: "FooBarCorp " +description: "Sample Package" +vendor: "FooBarCorp" +homepage: "http://example.com" +license: "MIT" +contents: +- src: ../bin/wails-open-file + dst: /usr/bin/wails-open-file +- src: ./main.desktop + dst: /usr/share/applications/wails-open-file.desktop +- src: ./application-wails-mime.xml + dst: /usr/share/mime/packages/application-x-wails.xml +- src: ./application-test-mime.xml + dst: /usr/share/mime/packages/application-x-test.xml +- src: ../appicon.svg + dst: /usr/share/icons/hicolor/scalable/apps/wails-open-file.svg +- src: ../wailsFileIcon.svg + dst: /usr/share/icons/hicolor/scalable/mimetypes/application-x-wails.svg +- src: ../testFileIcon.svg + dst: /usr/share/icons/hicolor/scalable/mimetypes/application-x-test.svg +# copy icons to Yaru theme as well. For some reason Ubuntu didn't pick up fileicons from hicolor theme +- src: ../appicon.svg + dst: /usr/share/icons/Yaru/scalable/apps/wails-open-file.svg +- src: ../wailsFileIcon.svg + dst: /usr/share/icons/Yaru/scalable/mimetypes/application-x-wails.svg +- src: ../testFileIcon.svg + dst: /usr/share/icons/Yaru/scalable/mimetypes/application-x-test.svg +scripts: + postinstall: ./postInstall.sh + postremove: ./postRemove.sh +``` +6. Build your .deb package using nfpm: +```sh +nfpm pkg --packager deb --target . +``` +7. Now when your package is installed, your app will be associated with specified file types. When you open file with your app, +new instance of app is launched and file path is passed as argument to your app. +To handle this you should parse command line arguments in your app. Example: +```go title="main.go" +func main() { + argsWithoutProg := os.Args[1:] + + if len(argsWithoutProg) != 0 { + println("launchArgs", argsWithoutProg) + } +} +``` + +You also can enable single instance lock for your app. In this case, when you open file with your app, new instance of app is not launched +and arguments are passed to already running instance. Check single instance lock guide for details. Example: +```go title="main.go" +func main() { + // Create application with options + err := wails.Run(&options.App{ + Title: "wails-open-file", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, + SingleInstanceLock: &options.SingleInstanceLock{ + UniqueId: "e3984e08-28dc-4e3d-b70a-45e961589cdc", + OnSecondInstanceLaunch: app.onSecondInstanceLaunch, + }, + Bind: []interface{}{ + app, + }, + }) +} +``` diff --git a/website/docs/guides/linux.mdx b/website/docs/guides/linux.mdx index fa74fe6c..1b55297b 100644 --- a/website/docs/guides/linux.mdx +++ b/website/docs/guides/linux.mdx @@ -18,3 +18,55 @@ videoTag.addEventListener("timeupdate", (event) => { Source: [Lyimmi](https://github.com/Lyimmi) on the [discussions board](https://github.com/wailsapp/wails/issues/1729#issuecomment-1212291275) + +## GStreamer error when using Audio or Video elements + +If you are seeing the following error when including `