Skip to content

Commit b33ade5

Browse files
committed
feat(auto-start): implement auto-start functionality for macOS
1 parent f4aa404 commit b33ade5

5 files changed

Lines changed: 136 additions & 80 deletions

File tree

build/darwin/Info.dev.plist

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,18 @@
6363
<dict>
6464
<key>NSAllowsLocalNetworking</key>
6565
<true/>
66+
<key>NSExceptionDomains</key>
67+
<dict>
68+
<key>wails.localhost</key>
69+
<dict>
70+
<key>NSIncludesSubdomains</key>
71+
<true/>
72+
<key>NSExceptionAllowsInsecureHTTPLoads</key>
73+
<true/>
74+
<key>NSExceptionRequiresForwardSecrecy</key>
75+
<false/>
76+
</dict>
77+
</dict>
6678
</dict>
6779
</dict>
6880
</plist>

frontend/src/directives/platform.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ export default {
77
const envStore = useEnvStore()
88
const supports = binding.value
99
if (!supports.includes(envStore.env.os)) {
10-
el.remove()
10+
el.style.display = 'none'
1111
}
1212
},
13+
updated(el, binding) {
14+
const envStore = useEnvStore()
15+
const supports = binding.value
16+
el.style.display = supports.includes(envStore.env.os) ? '' : 'none'
17+
},
1318
} as Directive

frontend/src/utils/helper.ts

Lines changed: 97 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ import {
1414
usePluginsStore,
1515
useRulesetsStore,
1616
} from '@/stores'
17-
import { ignoredError, stringifyNoFolding, message, confirm } from '@/utils'
17+
import {
18+
ignoredError,
19+
stringifyNoFolding,
20+
message,
21+
confirm,
22+
APP_TITLE,
23+
getTaskSchXmlString,
24+
} from '@/utils'
1825

1926
// Permissions Helper
2027
export const SwitchPermissions = async (enable: boolean) => {
@@ -38,20 +45,24 @@ export const SwitchPermissions = async (enable: boolean) => {
3845
appPath,
3946
'/f',
4047
]
41-
await Exec('reg', args)
48+
await Exec('reg', args, { Convert: true })
4249
}
4350

4451
export const CheckPermissions = async () => {
4552
const { appPath } = useEnvStore().env
4653
try {
47-
const out = await Exec('reg', [
48-
'query',
49-
'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers',
50-
'/v',
51-
appPath,
52-
'/t',
53-
'REG_SZ',
54-
])
54+
const out = await Exec(
55+
'reg',
56+
[
57+
'query',
58+
'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers',
59+
'/v',
60+
appPath,
61+
'/t',
62+
'REG_SZ',
63+
],
64+
{ Convert: true },
65+
)
5566
return out.includes('RunAsAdmin')
5667
} catch {
5768
return false
@@ -349,25 +360,33 @@ export const GetSystemProxy = async () => {
349360
const { os } = useEnvStore().env
350361
try {
351362
if (os === 'windows') {
352-
const out1 = await Exec('reg', [
353-
'query',
354-
'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings',
355-
'/v',
356-
'ProxyEnable',
357-
'/t',
358-
'REG_DWORD',
359-
])
363+
const out1 = await Exec(
364+
'reg',
365+
[
366+
'query',
367+
'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings',
368+
'/v',
369+
'ProxyEnable',
370+
'/t',
371+
'REG_DWORD',
372+
],
373+
{ Convert: true },
374+
)
360375

361376
if (/REG_DWORD\s+0x0/.test(out1)) return ''
362377

363-
const out2 = await Exec('reg', [
364-
'query',
365-
'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings',
366-
'/v',
367-
'ProxyServer',
368-
'/t',
369-
'REG_SZ',
370-
])
378+
const out2 = await Exec(
379+
'reg',
380+
[
381+
'query',
382+
'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings',
383+
'/v',
384+
'ProxyServer',
385+
'/t',
386+
'REG_SZ',
387+
],
388+
{ Convert: true },
389+
)
371390

372391
const regex = /ProxyServer\s+REG_SZ\s+(\S+)/
373392
const match = out2.match(regex)
@@ -551,21 +570,62 @@ export const GetSystemOrKernelProxy = async () => {
551570
return proxy_cache.proxyPromise
552571
}
553572

554-
export const QuerySchTask = async (taskName: string) => {
555-
await Exec('Schtasks', ['/Query', '/TN', taskName, '/XML'], { Convert: true })
573+
// Auto-start
574+
export const IsAutoStartEnabled = async () => {
575+
const { os } = useEnvStore().env
576+
let isAutoStart = false
577+
if (os === 'windows') {
578+
isAutoStart = await Exec('Schtasks', ['/Query', '/TN', APP_TITLE, '/XML'], { Convert: true })
579+
.then(() => true)
580+
.catch(() => false)
581+
} else if (os === 'darwin') {
582+
isAutoStart = await Exec('osascript', [
583+
'-e',
584+
`tell application "System Events" to get exists login item "${APP_TITLE}"`,
585+
])
586+
.then((res) => res.includes('true'))
587+
.catch(() => false)
588+
} else if (os === 'linux') {
589+
// TODO
590+
}
591+
return isAutoStart
556592
}
557593

558-
export const CreateSchTask = async (taskName: string, xmlPath: string) => {
559-
const fn = useEnvStore().env.isPrivileged ? Exec : RunWithPowerShell
560-
await fn('SchTasks', ['/Create', '/F', '/TN', taskName, '/XML', xmlPath], {
561-
admin: true,
562-
hidden: true,
563-
})
594+
export const EnableAutoStart = async (delay = 10) => {
595+
const { os, appPath, basePath, isPrivileged } = useEnvStore().env
596+
if (os === 'windows') {
597+
const xmlPath = await AbsolutePath('data/.cache/tasksch.xml')
598+
const xmlContent = getTaskSchXmlString(appPath, delay)
599+
await WriteFile(xmlPath, xmlContent)
600+
const fn = isPrivileged ? Exec : RunWithPowerShell
601+
await fn('SchTasks', ['/Create', '/F', '/TN', APP_TITLE, '/XML', xmlPath], {
602+
admin: true,
603+
hidden: true,
604+
})
605+
} else if (os === 'darwin') {
606+
const path = basePath.replace('/Contents/MacOS', '')
607+
await Exec('osascript', [
608+
'-e',
609+
`tell application "System Events" to make login item at end with properties {name:"${APP_TITLE}", path:"${path}"}`,
610+
])
611+
} else if (os === 'linux') {
612+
// TODO
613+
}
564614
}
565615

566-
export const DeleteSchTask = async (taskName: string) => {
567-
const fn = useEnvStore().env.isPrivileged ? Exec : RunWithPowerShell
568-
await fn('SchTasks', ['/Delete', '/F', '/TN', taskName], { admin: true, hidden: true })
616+
export const DisableAutoStart = async () => {
617+
const { os, isPrivileged } = useEnvStore().env
618+
if (os === 'windows') {
619+
const fn = isPrivileged ? Exec : RunWithPowerShell
620+
await fn('SchTasks', ['/Delete', '/F', '/TN', APP_TITLE], { admin: true, hidden: true })
621+
} else if (os === 'darwin') {
622+
await Exec('osascript', [
623+
'-e',
624+
`tell application "System Events" to delete login item "${APP_TITLE}"`,
625+
])
626+
} else if (os === 'linux') {
627+
// TODO
628+
}
569629
}
570630

571631
// Others

frontend/src/utils/others.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,7 @@ export const getGitHubApiAuthorization = () => {
187187
}
188188

189189
// System ScheduledTask Helper
190-
export const getTaskSchXmlString = async (delay = 30) => {
191-
const { appPath } = useEnvStore().env
192-
190+
export const getTaskSchXmlString = (appPath: string, delay = 30) => {
193191
const xml = /*xml*/ `<?xml version="1.0" encoding="UTF-16"?>
194192
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
195193
<RegistrationInfo>
@@ -235,7 +233,6 @@ export const getTaskSchXmlString = async (delay = 30) => {
235233
</Actions>
236234
</Task>
237235
`
238-
239236
return xml
240237
}
241238

frontend/src/views/SettingsView/components/components/BehaviorSettings.vue

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
11
<script lang="ts" setup>
22
import { ref } from 'vue'
33
4-
import { WriteFile, RemoveFile, AbsolutePath, ExitApp } from '@/bridge'
4+
import { ExitApp } from '@/bridge'
55
import { WebviewGpuPolicyOptions, WindowStateOptions } from '@/constant/app'
66
import { useAppSettingsStore, useEnvStore } from '@/stores'
77
import {
8-
APP_TITLE,
9-
getTaskSchXmlString,
108
confirm,
119
message,
12-
QuerySchTask,
13-
CreateSchTask,
14-
DeleteSchTask,
1510
CheckPermissions,
1611
SwitchPermissions,
1712
RunWithPowerShell,
13+
IsAutoStartEnabled,
14+
EnableAutoStart,
15+
DisableAutoStart,
1816
} from '@/utils'
1917
2018
const appSettings = useAppSettingsStore()
2119
const envStore = useEnvStore()
2220
2321
const isAdmin = ref(false)
24-
const isTaskScheduled = ref(false)
22+
const isAutoStart = ref(false)
2523
2624
const restartApp = async (admin = false) => {
2725
if (admin) {
@@ -46,14 +44,11 @@ const onPermChange = async (v: boolean) => {
4644
}
4745
4846
const onTaskSchChange = async (v: boolean) => {
49-
isTaskScheduled.value = !v
47+
isAutoStart.value = !v
48+
5049
try {
51-
if (v) {
52-
await createSchTask(appSettings.app.startupDelay)
53-
} else {
54-
await DeleteSchTask(APP_TITLE)
55-
}
56-
isTaskScheduled.value = v
50+
await (v ? EnableAutoStart(appSettings.app.startupDelay) : DisableAutoStart())
51+
isAutoStart.value = v
5752
} catch (error: any) {
5853
console.error(error)
5954
message.error(error)
@@ -63,7 +58,7 @@ const onTaskSchChange = async (v: boolean) => {
6358
const onStartupDelayChange = async (delay: number) => {
6459
if (appSettings.app.startupDelay !== delay) {
6560
try {
66-
await createSchTask(delay)
61+
await EnableAutoStart(delay)
6762
appSettings.app.startupDelay = delay
6863
} catch (error: any) {
6964
console.error(error)
@@ -72,26 +67,11 @@ const onStartupDelayChange = async (delay: number) => {
7267
}
7368
}
7469
75-
const checkSchtask = async () => {
76-
try {
77-
await QuerySchTask(APP_TITLE)
78-
isTaskScheduled.value = true
79-
} catch {
80-
isTaskScheduled.value = false
81-
}
82-
}
83-
84-
const createSchTask = async (delay = 30) => {
85-
const xmlPath = 'data/.cache/tasksch.xml'
86-
const xmlContent = await getTaskSchXmlString(delay)
87-
await WriteFile(xmlPath, xmlContent)
88-
await CreateSchTask(APP_TITLE, await AbsolutePath(xmlPath))
89-
await RemoveFile(xmlPath)
90-
}
70+
IsAutoStartEnabled().then((res) => {
71+
isAutoStart.value = res
72+
})
9173
9274
if (envStore.env.os === 'windows') {
93-
checkSchtask()
94-
9575
CheckPermissions().then((admin) => {
9676
isAdmin.value = admin
9777
})
@@ -119,23 +99,25 @@ if (envStore.env.os === 'windows') {
11999
<Switch v-model="isAdmin" @change="onPermChange" />
120100
</div>
121101
</div>
122-
<div v-platform="['windows']" class="px-8 py-12 flex items-center justify-between">
102+
<div v-platform="['windows', 'darwin']" class="px-8 py-12 flex items-center justify-between">
123103
<div class="text-16 font-bold">
124104
{{ $t('settings.startup.name') }}
125-
<span class="font-normal text-12">({{ $t('settings.needAdmin') }})</span>
105+
<span v-platform="['windows']" class="font-normal text-12">
106+
({{ $t('settings.needAdmin') }})
107+
</span>
126108
</div>
127109
<div class="flex items-center">
128110
<Radio
129-
v-if="isTaskScheduled"
111+
v-if="isAutoStart"
130112
v-model="appSettings.app.windowStartState"
131113
:options="WindowStateOptions"
132114
type="number"
133115
/>
134-
<Switch v-model="isTaskScheduled" class="ml-16" @change="onTaskSchChange" />
116+
<Switch v-model="isAutoStart" class="ml-16" @change="onTaskSchChange" />
135117
</div>
136118
</div>
137119
<div
138-
v-if="isTaskScheduled"
120+
v-if="isAutoStart"
139121
v-platform="['windows']"
140122
class="px-8 py-12 flex items-center justify-between"
141123
>

0 commit comments

Comments
 (0)