Compare commits

...

3 Commits

Author SHA1 Message Date
xmk23333
197c9d3af8 feat: add Windows 7 compatibility build using win7 target 2026-01-19 13:14:54 +08:00
xmk23333
388581d75e - fix: fix IME composition input causing character duplication
Handle onCompositionStart/End events to prevent repeated characters
  when typing Chinese in proxy group filter
2026-01-19 13:04:34 +08:00
xmk23333
3c148f2c01 fix: prevent profile switch queue from breaking after failed switch 2026-01-19 12:55:21 +08:00
6 changed files with 95 additions and 27 deletions

View File

@ -205,6 +205,8 @@ jobs:
GITHUB_EVENT_NAME: workflow_dispatch
run: node scripts/update-version.mjs
- name: Prepare
env:
LEGACY_BUILD: 'true'
run: pnpm prepare --${{ matrix.arch }}
- name: Build
env:
@ -263,7 +265,7 @@ jobs:
arch:
- x64
- arm64
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v6

View File

@ -332,11 +332,13 @@ function getSysproxyNodeName() {
}
})()
const isWin7Build = process.env.LEGACY_BUILD === 'true'
switch (platform) {
case 'win32':
if (arch === 'x64') return 'sysproxy.win32-x64-msvc.node'
if (arch === 'x64') return isWin7Build ? 'sysproxy.win32-x64-msvc-win7.node' : 'sysproxy.win32-x64-msvc.node'
if (arch === 'arm64') return 'sysproxy.win32-arm64-msvc.node'
if (arch === 'ia32') return 'sysproxy.win32-ia32-msvc.node'
if (arch === 'ia32') return isWin7Build ? 'sysproxy.win32-ia32-msvc-win7.node' : 'sysproxy.win32-ia32-msvc.node'
break
case 'darwin':
if (arch === 'x64') return 'sysproxy.darwin-x64.node'

View File

@ -65,26 +65,33 @@ export async function getProfileItem(id: string | undefined): Promise<IProfileIt
export async function changeCurrentProfile(id: string): Promise<void> {
// 使用队列确保 profile 切换串行执行,避免竞态条件
changeProfileQueue = changeProfileQueue.then(async () => {
const { current } = await getProfileConfig()
if (current === id) return
let taskError: unknown = null
changeProfileQueue = changeProfileQueue
.catch(() => {
})
.then(async () => {
const { current } = await getProfileConfig()
if (current === id) return
try {
await updateProfileConfig((config) => {
config.current = id
return config
})
await restartCore()
} catch (e) {
// 回滚配置
await updateProfileConfig((config) => {
config.current = current
return config
})
throw e
}
})
try {
await updateProfileConfig((config) => {
config.current = id
return config
})
await restartCore()
} catch (e) {
// 回滚配置
await updateProfileConfig((config) => {
config.current = current
return config
})
taskError = e
}
})
await changeProfileQueue
if (taskError) {
throw taskError
}
}
export async function updateProfileItem(item: IProfileItem): Promise<void> {
@ -249,6 +256,14 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
useProxy: true,
timeout: subscriptionTimeout
})
} else if (newItem.substore) {
// SubStore requests (especially collections) need more time as they fetch and merge multiple subscriptions
// Use the full subscriptionTimeout since SubStore is a local server and doesn't need smart fallback
result = await fetchAndValidateSubscription({
...baseOptions,
useProxy: false,
timeout: subscriptionTimeout
})
} else {
const smartTimeout = 5000
try {

View File

@ -1,11 +1,19 @@
const { existsSync } = require('fs')
const { join, dirname } = require('path')
const os = require('os')
const { platform, arch } = process
let nativeBinding = null
let loadError = null
function isWindows7() {
if (platform !== 'win32') return false
const release = os.release()
// Windows 7 is NT 6.1
return release.startsWith('6.1')
}
function isMusl() {
// 优先使用 process.reportNode.js 12+,最可靠)
if (process.report && typeof process.report.getReport === 'function') {
@ -23,10 +31,11 @@ function isMusl() {
}
function getBindingName() {
const win7 = isWindows7()
switch (platform) {
case 'win32':
if (arch === 'x64') return 'sysproxy.win32-x64-msvc.node'
if (arch === 'ia32') return 'sysproxy.win32-ia32-msvc.node'
if (arch === 'x64') return win7 ? 'sysproxy.win32-x64-msvc-win7.node' : 'sysproxy.win32-x64-msvc.node'
if (arch === 'ia32') return win7 ? 'sysproxy.win32-ia32-msvc-win7.node' : 'sysproxy.win32-ia32-msvc.node'
if (arch === 'arm64') return 'sysproxy.win32-arm64-msvc.node'
break
case 'darwin':

View File

@ -1,20 +1,60 @@
import React, { useRef } from 'react'
import React, { useRef, useState, useCallback } from 'react'
import { Input, InputProps } from '@heroui/react'
import { FaSearch } from 'react-icons/fa'
interface CollapseInputProps extends InputProps {
interface CollapseInputProps extends Omit<InputProps, 'onValueChange'> {
title: string
onValueChange?: (value: string) => void
}
const CollapseInput: React.FC<CollapseInputProps> = (props) => {
const { title, ...inputProps } = props
const { title, value, onValueChange, ...inputProps } = props
const inputRef = useRef<HTMLInputElement>(null)
const isComposingRef = useRef(false)
const [localValue, setLocalValue] = useState(value || '')
// 同步外部 value 变化
React.useEffect(() => {
if (!isComposingRef.current) {
setLocalValue(value || '')
}
}, [value])
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value
setLocalValue(newValue)
// 只在非组合输入时触发外部更新
if (!isComposingRef.current) {
onValueChange?.(newValue)
}
},
[onValueChange]
)
const handleCompositionStart = useCallback(() => {
isComposingRef.current = true
}, [])
const handleCompositionEnd = useCallback(
(e: React.CompositionEvent<HTMLInputElement>) => {
isComposingRef.current = false
// 组合输入结束后,触发一次更新
onValueChange?.(e.currentTarget.value)
},
[onValueChange]
)
return (
<div className="flex">
<Input
size="sm"
ref={inputRef}
{...inputProps}
value={localValue as string}
onChange={handleChange}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
style={{ paddingInlineEnd: 0 }}
classNames={{
inputWrapper: 'cursor-pointer bg-transparent p-0 data-[hover=true]:bg-content2',

View File

@ -303,7 +303,7 @@ const drawSvg = async (
if (upload === currentUploadRef.current && download === currentDownloadRef.current) return
currentUploadRef.current = upload
currentDownloadRef.current = download
const svg = `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 140 36"><image height="36" width="36" href="${trayIconBase64}"/><text x="140" y="15" font-size="18" font-family="PingFang SC" font-weight="bold" text-anchor="end">${calcTraffic(upload)}/s</text><text x="140" y="34" font-size="18" font-family="PingFang SC" font-weight="bold" text-anchor="end">${calcTraffic(download)}/s</text></svg>`
const svg = `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 156 36"><image height="36" width="36" href="${trayIconBase64}"/><text x="156" y="15" font-size="18" font-family="PingFang SC" font-weight="bold" text-anchor="end" fill="black">${calcTraffic(upload)}/s</text><text x="156" y="34" font-size="18" font-family="PingFang SC" font-weight="bold" text-anchor="end" fill="black">${calcTraffic(download)}/s</text></svg>`
const image = await loadImage(svg)
window.electron.ipcRenderer.send('trayIconUpdate', image, true)
}