chore: show doctor icon if it is not passed

This commit is contained in:
qianlifeng 2024-10-12 16:18:25 +08:00
parent b9c01f8aca
commit d362fa5b6a
No known key found for this signature in database
16 changed files with 227 additions and 159 deletions

3
.gitignore vendored
View File

@ -18,4 +18,5 @@ Wox.UI.React/dist/
node_modules/
__pycache__/
Release/
__debug_bin*
__debug_bin*
Wox/log/

29
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,29 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Run Go",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/Wox",
},
{
"name": "Run Flutter",
"cwd": "Wox.UI.Flutter/wox",
"request": "launch",
"type": "dart",
// "flutterMode": "release"
}
],
"compounds": [
{
"name": "Run Wox",
"configurations": ["Run Go", "Run Flutter"],
"stopAll": true
}
]
}

View File

@ -1,19 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "wox (debug)",
"request": "launch",
"type": "dart"
},
{
"name": "wox (release)",
"request": "launch",
"type": "dart",
"flutterMode": "release",
}
]
}

View File

@ -125,4 +125,8 @@ class WoxApi {
Future<List<AIModel>> findAIModels() async {
return await WoxHttpUtil.instance.postData("/ai/models", null);
}
Future<bool> doctorCheck() async {
return await WoxHttpUtil.instance.postData("/doctor/check", null);
}
}

View File

@ -39,6 +39,10 @@ class PlainQuery {
return PlainQuery(queryId: "", queryType: "", queryText: "", querySelection: Selection.empty());
}
static PlainQuery text(String text) {
return PlainQuery(queryId: "", queryType: WoxQueryTypeEnum.WOX_QUERY_TYPE_INPUT.code, queryText: text, querySelection: Selection.empty());
}
static PlainQuery emptyInput() {
return PlainQuery(queryId: "", queryType: WoxQueryTypeEnum.WOX_QUERY_TYPE_INPUT.code, queryText: "", querySelection: Selection.empty());
}
@ -393,3 +397,18 @@ class WoxRefreshableResult {
return data;
}
}
class QueryIconInfo {
final WoxImage icon;
final Function()? onPressed;
QueryIconInfo({
required this.icon,
this.onPressed,
});
static QueryIconInfo empty() {
return QueryIconInfo(icon: WoxImage.empty());
}
}

View File

@ -52,7 +52,11 @@ Future<void> initialServices(List<String> arguments) async {
await initArgs(arguments);
await WoxThemeUtil.instance.loadTheme();
await WoxSettingUtil.instance.loadSetting();
var launcherController = WoxLauncherController()..startRefreshSchedule();
var launcherController = WoxLauncherController();
launcherController.startRefreshSchedule();
launcherController.startDoctorCheckSchedule();
await WoxWebsocketMsgUtil.instance.initialize(Uri.parse("ws://localhost:${Env.serverPort}/ws"), onMessageReceived: launcherController.handleWebSocketMessage);
HeartbeatChecker().startChecking();
Get.put(launcherController);

View File

@ -141,7 +141,16 @@ class WoxQueryBoxView extends GetView<WoxLauncherController> {
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: WoxImageView(woxImage: controller.queryIcon.value, width: 24, height: 24),
child: MouseRegion(
cursor: controller.queryIcon.value.onPressed != null ? SystemMouseCursors.click : SystemMouseCursors.basic,
child: GestureDetector(
onTap: () {
controller.queryIcon.value.onPressed?.call();
controller.queryBoxFocusNode.requestFocus();
},
child: WoxImageView(woxImage: controller.queryIcon.value.icon, width: 24, height: 24),
),
),
),
)),
),

View File

@ -40,7 +40,7 @@ class WoxLauncherController extends GetxController {
final queryBoxFocusNode = FocusNode();
final queryBoxTextFieldController = TextEditingController();
final queryBoxScrollController = ScrollController(initialScrollOffset: 0.0);
final queryIcon = WoxImage.empty().obs;
final queryIcon = QueryIconInfo.empty().obs;
//preview related variables
final currentPreview = WoxPreview.empty().obs;
@ -81,6 +81,9 @@ class WoxLauncherController extends GetxController {
/// toolbar related variables
final toolbarTip = ''.obs;
/// The result of the doctor check.
var doctorCheckPassed = true;
/// Triggered when received query results from the server.
void onReceivedQueryResults(List<WoxQueryResult> receivedResults) {
if (receivedResults.isEmpty) {
@ -775,6 +778,13 @@ class WoxLauncherController extends GetxController {
});
}
startDoctorCheckSchedule() {
Timer.periodic(const Duration(minutes: 1), (timer) async {
doctorCheckPassed = await WoxApi.instance.doctorCheck();
Logger.instance.debug(const UuidV4().generate(), "doctor check result: $doctorCheckPassed");
});
}
@override
void dispose() {
queryBoxFocusNode.dispose();
@ -880,26 +890,43 @@ class WoxLauncherController extends GetxController {
/// Change the query icon based on the query
Future<void> changeQueryIcon(String traceId, PlainQuery query) async {
//if doctor check is not passed and query is empty, show doctor icon
if (query.isEmpty && !doctorCheckPassed) {
queryIcon.value = QueryIconInfo(
icon: WoxImage(imageType: WoxImageTypeEnum.WOX_IMAGE_TYPE_BASE64.code, imageData: QUERY_ICON_DOCTOR_WARNING),
onPressed: () {
onQueryChanged(traceId, PlainQuery.text("doctor "), "user click query icon");
},
);
return;
}
if (query.queryType == WoxQueryTypeEnum.WOX_QUERY_TYPE_SELECTION.code) {
if (query.querySelection.type == WoxSelectionTypeEnum.WOX_SELECTION_TYPE_FILE.code) {
queryIcon.value = WoxImage(imageType: WoxImageTypeEnum.WOX_IMAGE_TYPE_SVG.code, imageData: QUERY_ICON_SELECTION_FILE);
queryIcon.value = QueryIconInfo(
icon: WoxImage(imageType: WoxImageTypeEnum.WOX_IMAGE_TYPE_SVG.code, imageData: QUERY_ICON_SELECTION_FILE),
);
}
if (query.querySelection.type == WoxSelectionTypeEnum.WOX_SELECTION_TYPE_TEXT.code) {
queryIcon.value = WoxImage(imageType: WoxImageTypeEnum.WOX_IMAGE_TYPE_SVG.code, imageData: QUERY_ICON_SELECTION_TEXT);
queryIcon.value = QueryIconInfo(
icon: WoxImage(imageType: WoxImageTypeEnum.WOX_IMAGE_TYPE_SVG.code, imageData: QUERY_ICON_SELECTION_TEXT),
);
}
return;
}
if (query.queryType == WoxQueryTypeEnum.WOX_QUERY_TYPE_INPUT.code) {
var img = await WoxApi.instance.getQueryIcon(query);
queryIcon.value = img;
queryIcon.value = QueryIconInfo(
icon: img,
);
return;
}
queryIcon.value = WoxImage.empty();
queryIcon.value = QueryIconInfo.empty();
}
void showToolbarTips(String tip) {
toolbarTip.value = tip;
}
}
}

View File

@ -8,3 +8,5 @@ const String QUERY_ICON_SELECTION_FILE =
const String QUERY_ICON_SELECTION_TEXT =
'<svg t="1704958243895" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5762" width="200" height="200"><path d="M925.48105 1024H98.092461a98.51895 98.51895 0 0 1-97.879217-98.945439V98.732195A98.732195 98.732195 0 0 1 98.092461 0.426489h827.388589a98.732195 98.732195 0 0 1 98.305706 98.305706v826.322366a98.51895 98.51895 0 0 1-98.305706 98.945439z m-829.094544-959.600167a33.052895 33.052895 0 0 0-32.199917 32.626406v829.734277a32.83965 32.83965 0 0 0 32.199917 33.26614h831.653477a32.83965 32.83965 0 0 0 31.773428-33.26614V97.026239a33.266139 33.266139 0 0 0-32.626406-32.626406z" fill="#0077F0" p-id="5763"></path><path d="M281.69596 230.943773h460.60808v73.569347h-187.655144v488.969596h-85.297792V304.51312h-187.655144z" fill="#0077F0" opacity=".5" p-id="5764"></path></svg>';
const String QUERY_ICON_DOCTOR_WARNING = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAACY0lEQVR4nO1ZTU8UQRCdoAmcFG/AxcSAVWtCOABy5GgiAcVEfgrCeTnD4m/AyyIk/gIORJxqlhCWmyf0YNYQPbpyoUw1C2T5CD3TNR8bp5JKNtnZ7feqqnteVwVBYYUV5m3MQRfvDj1nwgUm/MiEdTbwmwlOrNvPWGfCdfuMPMtBV/bAa9jPBpeY4Dsb5EhO8M3+tob96QPfHn7EBlfZYDMy8OvetP+1P9KbDniDb9jgTwXgVxwa8t/JAa8G91pRVwaOV0oLK7KWLvitxz1sYDNx8OYiG5uypl7k5fRIDTyeZ+ITb03e9yeQRtmY20jAisaGzQa8sVk4ZcLZeOBrTx4ywY+4i0+8eNXmHvuhIcd2nOi/94meHgEUX40Gnp71scE/OSLQ5D0YiBL9Jd/6VSbAgsldmJ3plHwRIDhyEoBWKfpHK4kMMBOMuZTPYm4JhKX5uwmIns8rAYNVBwJwmFsChHWHEoJfOSZw7JKBkxwT+PsfEDCdX0KHnb2JSecYTcirqb3IEvHQ5UWmJCUScXKREkpiLgHwR87dPA05/XltlKffvrS+szamQaLsBF7rQiPAz0+gmbmpdC80Gt0IVQKElSCqcTj4wOdSL2UjwMW/fBj1IACN2H1T21aR1oZ//caN/CmHpdexwF+QkF5lZgRg2Qt85q3FqlKTl78Odqfa3CXYkDVVwLdnAlYS3RMkrURYVm+vtxEhnG0NI5QJQCN2HzQyif2RXtURE2EltRHTDUO+ciztJNrGYDmTId+NAtA8HWfCd60T68De7C7HrHLLO7DfhaV5UZW5GLMWVljQ+fYPzbicDniMwzoAAAAASUVORK5CYII=';

View File

@ -950,10 +950,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev"
source: hosted
version: "14.2.4"
version: "14.2.5"
web:
dependency: transitive
description:

View File

@ -1,15 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Run",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "main.go"
}
]
}

View File

@ -3,18 +3,18 @@ package i18n
import (
"context"
"fmt"
"github.com/tidwall/gjson"
"os"
"path"
"strings"
"sync"
"wox/resource"
"wox/util"
"github.com/tidwall/gjson"
)
var managerInstance *Manager
var managerOnce sync.Once
var logger *util.Log
type Manager struct {
currentLangCode LangCode
@ -30,7 +30,6 @@ func GetI18nManager() *Manager {
}
json, _ := resource.GetLangJson(util.NewTraceContext(), string(LangCodeEnUs))
managerInstance.enUsLangJson = string(json)
logger = util.GetLogger()
})
return managerInstance
}
@ -39,7 +38,7 @@ func (m *Manager) UpdateLang(ctx context.Context, langCode LangCode) error {
if !IsSupportedLangCode(string(langCode)) {
return fmt.Errorf("unsupported lang code: %s", langCode)
}
json, err := m.GetLangJson(ctx, langCode)
if err != nil {
return err
@ -102,13 +101,13 @@ func (m *Manager) TranslatePlugin(ctx context.Context, key string, pluginDirecto
jsonPath := path.Join(pluginDirectory, "lang", fmt.Sprintf("%s.json", m.currentLangCode))
if _, err := os.Stat(jsonPath); os.IsNotExist(err) {
logger.Error(ctx, fmt.Sprintf("lang file not found: %s", jsonPath))
util.GetLogger().Error(ctx, fmt.Sprintf("lang file not found: %s", jsonPath))
return key
}
json, err := os.ReadFile(jsonPath)
if err != nil {
logger.Error(ctx, fmt.Sprintf("error reading lang file(%s): %s", jsonPath, err.Error()))
util.GetLogger().Error(ctx, fmt.Sprintf("error reading lang file(%s): %s", jsonPath, err.Error()))
return key
}

95
Wox/plugin/doctor.go Normal file
View File

@ -0,0 +1,95 @@
package plugin
import (
"context"
"fmt"
"sort"
"wox/updater"
"wox/util"
"wox/util/permission"
)
type DoctorCheckResult struct {
Name string
Status bool
Description string
ActionName string
Action func(ctx context.Context)
}
// RunDoctorChecks runs all doctor checks
func RunDoctorChecks(ctx context.Context) []DoctorCheckResult {
results := []DoctorCheckResult{
checkWoxVersion(ctx),
}
if util.IsMacOS() {
results = append(results, checkAccessibilityPermission(ctx))
}
//sort by status, false first
sort.Slice(results, func(i, j int) bool {
return results[i].Status && !results[j].Status
})
return results
}
func checkWoxVersion(ctx context.Context) DoctorCheckResult {
latestVersion, err := updater.CheckUpdate(ctx)
if err != nil {
if latestVersion.LatestVersion == updater.CURRENT_VERSION {
return DoctorCheckResult{
Name: "Version",
Status: true,
Description: "Already using the latest version",
ActionName: "",
Action: func(ctx context.Context) {
},
}
}
return DoctorCheckResult{
Name: "Version",
Status: true,
Description: err.Error(),
ActionName: "",
Action: func(ctx context.Context) {
},
}
}
return DoctorCheckResult{
Name: "Version",
Status: false,
Description: fmt.Sprintf("New version available: %s", latestVersion),
ActionName: "Check for updates",
Action: func(ctx context.Context) {
//updater.OpenUpdatePage(ctx)
},
}
}
func checkAccessibilityPermission(ctx context.Context) DoctorCheckResult {
hasPermission := permission.HasAccessibilityPermission(ctx)
if !hasPermission {
return DoctorCheckResult{
Name: "Accessibility",
Status: false,
Description: "You need to grant Wox Accessibility permission to use this plugin",
ActionName: "Open Accessibility Settings",
Action: func(ctx context.Context) {
permission.GrantAccessibilityPermission(ctx)
},
}
}
return DoctorCheckResult{
Name: "Accessibility",
Status: hasPermission,
Description: "You have granted Wox Accessibility permission",
ActionName: "",
Action: func(ctx context.Context) {
},
}
}

View File

@ -2,12 +2,7 @@ package system
import (
"context"
"fmt"
"sort"
"wox/plugin"
"wox/updater"
"wox/util"
"wox/util/permission"
)
var doctorIcon = plugin.NewWoxImageSvg(`<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24"><path fill="#06ac11" d="m10.6 16.2l7.05-7.05l-1.4-1.4l-5.65 5.65l-2.85-2.85l-1.4 1.4zM5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21z"/></svg>`)
@ -16,13 +11,6 @@ func init() {
plugin.AllSystemPlugin = append(plugin.AllSystemPlugin, &DoctorPlugin{})
}
type checkResult struct {
Name string
Status bool
Description string
Action func(ctx context.Context)
}
type DoctorPlugin struct {
api plugin.API
}
@ -53,7 +41,7 @@ func (r *DoctorPlugin) Init(ctx context.Context, initParams plugin.InitParams) {
}
func (r *DoctorPlugin) Query(ctx context.Context, query plugin.Query) (results []plugin.QueryResult) {
checkResults := r.runSystemChecks(ctx)
checkResults := plugin.RunDoctorChecks(ctx)
for _, check := range checkResults {
icon := plugin.NewWoxImageSvg(`<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24"><path fill="#f21818" d="M12 17q.425 0 .713-.288T13 16t-.288-.712T12 15t-.712.288T11 16t.288.713T12 17m-1-4h2V7h-2zm1 9q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22"/></svg>`)
@ -67,7 +55,7 @@ func (r *DoctorPlugin) Query(ctx context.Context, query plugin.Query) (results [
Icon: icon,
Actions: []plugin.QueryResultAction{
{
Name: check.Name,
Name: check.ActionName,
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
check.Action(ctx)
},
@ -78,75 +66,3 @@ func (r *DoctorPlugin) Query(ctx context.Context, query plugin.Query) (results [
return results
}
func (r *DoctorPlugin) runSystemChecks(ctx context.Context) []checkResult {
results := []checkResult{
r.checkWoxVersion(ctx),
//check plugin version updates
}
if util.IsMacOS() {
results = append(results, r.checkAccessibilityPermission(ctx))
}
// sort by status, false first
sort.Slice(results, func(i, j int) bool {
return !results[i].Status && results[j].Status
})
return results
}
func (r *DoctorPlugin) checkWoxVersion(ctx context.Context) checkResult {
latestVersion, err := updater.CheckUpdate(ctx)
if err != nil {
if latestVersion.LatestVersion == updater.CURRENT_VERSION {
return checkResult{
Name: "Version",
Status: true,
Description: "Already using the latest version",
Action: func(ctx context.Context) {
},
}
}
return checkResult{
Name: "Version",
Status: true,
Description: err.Error(),
Action: func(ctx context.Context) {
},
}
}
return checkResult{
Name: "Version",
Status: false,
Description: fmt.Sprintf("New version available: %s", latestVersion),
Action: func(ctx context.Context) {
//updater.OpenUpdatePage(ctx)
},
}
}
func (r *DoctorPlugin) checkAccessibilityPermission(ctx context.Context) checkResult {
hasPermission := permission.HasAccessibilityPermission(ctx)
if !hasPermission {
return checkResult{
Name: "Accessibility",
Status: false,
Description: "You need to grant Wox Accessibility permission to use this plugin",
Action: func(ctx context.Context) {
permission.GrantAccessibilityPermission(ctx)
},
}
}
return checkResult{
Name: "Accessibility",
Status: hasPermission,
Description: "You have granted Wox Accessibility permission",
Action: func(ctx context.Context) {
},
}
}

View File

@ -15,7 +15,6 @@ import (
"wox/ui/dto"
"wox/util"
"wox/util/hotkey"
"wox/util/permission"
"github.com/jinzhu/copier"
"github.com/samber/lo"
@ -56,9 +55,8 @@ var routers = map[string]func(w http.ResponseWriter, r *http.Request){
// ai
"/ai/models": handleAIModels,
// permission
"/permission/accessibility": handlePermissionAccessibility,
"/permission/accessibility/grant": handlePermissionAccessibilityGrant,
// doctor
"/doctor/check": handleDoctorCheck,
// others
"/": handleHome,
@ -769,14 +767,15 @@ func handleAIModels(w http.ResponseWriter, r *http.Request) {
writeSuccessResponse(w, results)
}
func handlePermissionAccessibility(w http.ResponseWriter, r *http.Request) {
func handleDoctorCheck(w http.ResponseWriter, r *http.Request) {
ctx := util.NewTraceContext()
hasAccessibilityPermission := permission.HasAccessibilityPermission(ctx)
writeSuccessResponse(w, hasAccessibilityPermission)
}
func handlePermissionAccessibilityGrant(w http.ResponseWriter, r *http.Request) {
ctx := util.NewTraceContext()
permission.GrantAccessibilityPermission(ctx)
writeSuccessResponse(w, "")
results := plugin.RunDoctorChecks(ctx)
allPassed := true
for _, result := range results {
if !result.Status {
allPassed = false
break
}
}
writeSuccessResponse(w, allPassed)
}

View File

@ -12,8 +12,6 @@ import (
const versionManifestUrl = "https://raw.githubusercontent.com/Wox-launcher/Wox/v2/updater.json"
var logger = util.GetLogger()
type VersionManifest struct {
Version string
MacDownloadUrl string
@ -30,26 +28,26 @@ type UpdateInfo struct {
}
func CheckUpdate(ctx context.Context) (info UpdateInfo, err error) {
logger.Info(ctx, "start checking for updates")
util.GetLogger().Info(ctx, "start checking for updates")
latestVersion, err := getLatestVersion(ctx)
if err != nil {
logger.Error(ctx, err.Error())
util.GetLogger().Error(ctx, err.Error())
return UpdateInfo{}, err
}
// compare with current version
existingVersion, existingErr := semver.NewVersion(CURRENT_VERSION)
if existingErr != nil {
logger.Error(ctx, fmt.Sprintf("failed to parse current version: %s", existingErr.Error()))
util.GetLogger().Error(ctx, fmt.Sprintf("failed to parse current version: %s", existingErr.Error()))
return UpdateInfo{}, fmt.Errorf("failed to parse current version: %w", existingErr)
}
newVersion, newErr := semver.NewVersion(latestVersion.Version)
if newErr != nil {
logger.Error(ctx, fmt.Sprintf("failed to parse latest version: %s", newErr.Error()))
util.GetLogger().Error(ctx, fmt.Sprintf("failed to parse latest version: %s", newErr.Error()))
return UpdateInfo{}, fmt.Errorf("failed to parse latest version: %w", newErr)
}
if existingVersion.LessThan(newVersion) || existingVersion.Equal(newVersion) {
logger.Info(ctx, fmt.Sprintf("no new version available, current: %s, latest: %s", existingVersion.String(), newVersion.String()))
util.GetLogger().Info(ctx, fmt.Sprintf("no new version available, current: %s, latest: %s", existingVersion.String(), newVersion.String()))
return UpdateInfo{
CurrentVersion: existingVersion.String(),
LatestVersion: newVersion.String(),
@ -57,7 +55,7 @@ func CheckUpdate(ctx context.Context) (info UpdateInfo, err error) {
}, errors.New("no new version available")
}
logger.Info(ctx, fmt.Sprintf("new version available, current: %s, latest: %s", existingVersion.String(), newVersion.String()))
util.GetLogger().Info(ctx, fmt.Sprintf("new version available, current: %s, latest: %s", existingVersion.String(), newVersion.String()))
var downloadUrl string
if util.IsMacOS() {
@ -70,7 +68,7 @@ func CheckUpdate(ctx context.Context) (info UpdateInfo, err error) {
downloadUrl = latestVersion.LinuxDownloadUrl
}
if downloadUrl == "" {
logger.Error(ctx, "no download url found")
util.GetLogger().Error(ctx, "no download url found")
return UpdateInfo{}, errors.New("no download url found")
}