Skip to content

Commit e9451a6

Browse files
committed
feat: add WMIC availability check and helper modal for Windows users
1 parent c96d5ea commit e9451a6

File tree

8 files changed

+251
-38
lines changed

8 files changed

+251
-38
lines changed

src/main/ipc/dialogs.ts

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,35 @@
1+
import { BrowserWindow, dialog, ipcMain, shell } from 'electron'
12
import _ from 'lodash'
2-
import { v4 as uuid } from 'uuid'
3-
import { ipcMain, shell, dialog, BrowserWindow } from 'electron'
43
import pingLib from 'ping'
4+
import { v4 as uuid } from 'uuid'
55

6-
import { store } from '../store/store'
7-
import { dnsService } from '../config'
8-
import { Server, ServerStore } from '../../shared/interfaces/server.interface'
9-
import { EventsKeys } from '../../shared/constants/eventsKeys.constant'
10-
import { isValidDnsAddress } from '../../shared/validators/dns.validator'
116
import LN from '../../i18n/i18n-node'
127
import { Locales } from '../../i18n/i18n-types'
13-
import { getLoggerPathFile, LogId, userLogger } from '../shared/logger'
8+
import { EventsKeys } from '../../shared/constants/eventsKeys.constant'
9+
import { Server, ServerStore } from '../../shared/interfaces/server.interface'
10+
import { isValidDnsAddress } from '../../shared/validators/dns.validator'
11+
import { dnsService } from '../config'
12+
import { WindowsPlatform } from '../platforms/windows/windows.platform'
1413
import { getOverlayIcon } from '../shared/file'
14+
import { LogId, getLoggerPathFile, userLogger } from '../shared/logger'
1515
import { updateOverlayIcon } from '../shared/overlayIcon'
16+
import { isWindows } from '../shared/platform'
17+
import { store } from '../store/store'
1618

1719
ipcMain.handle(EventsKeys.SET_DNS, async (event, server: Server) => {
1820
try {
21+
if (isWindows()) {
22+
const winPlatform = new WindowsPlatform()
23+
const isAvailableWmic = await winPlatform.isWmicAvailable()
24+
if (!isAvailableWmic) {
25+
return {
26+
server,
27+
success: false,
28+
message: 'wmic_not_available',
29+
}
30+
}
31+
}
32+
1933
await dnsService.setDns(server.servers)
2034
const currentLng = LN[getCurrentLng()]
2135
const win = BrowserWindow.getAllWindows()[0]
@@ -41,6 +55,18 @@ ipcMain.handle(EventsKeys.SET_DNS, async (event, server: Server) => {
4155

4256
ipcMain.handle(EventsKeys.CLEAR_DNS, async (event, server: Server) => {
4357
try {
58+
if (isWindows()) {
59+
const winPlatform = new WindowsPlatform()
60+
const isAvailableWmic = await winPlatform.isWmicAvailable()
61+
if (!isAvailableWmic) {
62+
return {
63+
server,
64+
success: false,
65+
message: 'wmic_not_available',
66+
}
67+
}
68+
}
69+
4470
await dnsService.clearDns()
4571

4672
const currentLng = LN[getCurrentLng()]
@@ -224,7 +250,18 @@ ipcMain.handle(EventsKeys.TOGGLE_PIN, async (event, server: Server) => {
224250
}
225251
})
226252

227-
ipcMain.handle(EventsKeys.GET_NETWORK_INTERFACE_LIST, () => {
253+
ipcMain.handle(EventsKeys.GET_NETWORK_INTERFACE_LIST, async () => {
254+
if (isWindows()) {
255+
const winPlatform = new WindowsPlatform()
256+
const isAvailableWmic = await winPlatform.isWmicAvailable()
257+
if (!isAvailableWmic) {
258+
return {
259+
success: false,
260+
message: 'wmic_not_available',
261+
}
262+
}
263+
}
264+
228265
return dnsService.getInterfacesList()
229266
})
230267

src/main/platforms/windows/windows.platform.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import network from 'network'
22
import sudo from 'sudo-prompt'
33

4+
import { store } from '../../store/store'
45
import { Platform } from '../platform'
56
import { Interface } from './interfaces/interface'
6-
import { store } from '../../store/store'
77

88
export class WindowsPlatform extends Platform {
99
async clearDns(): Promise<void> {
@@ -74,6 +74,24 @@ export class WindowsPlatform extends Platform {
7474
}
7575
}
7676

77+
async isWmicAvailable(): Promise<boolean> {
78+
return new Promise((resolve, reject) => {
79+
sudo.exec(
80+
'wmic',
81+
{
82+
name: 'DnsChanger',
83+
},
84+
(error) => {
85+
if (error) {
86+
resolve(false)
87+
return
88+
}
89+
resolve(true)
90+
},
91+
)
92+
})
93+
}
94+
7795
private async getValidateInterface() {
7896
try {
7997
const interfaces: Interface[] = await this.getInterfacesList()

src/main/shared/platform.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function isWindows() {
2+
return process.platform === 'win32'
3+
}

src/renderer/component/buttons/connect-btn.component.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
import { useContext, useState } from 'react'
12
import { Button } from 'react-daisyui'
3+
import ReactGA from 'react-ga4'
4+
import { AiOutlineLoading } from 'react-icons/ai'
25
import { CiPower } from 'react-icons/ci'
3-
import { useContext, useState } from 'react'
46
import { serversContext } from '../../context/servers.context'
5-
import { AiOutlineLoading } from 'react-icons/ai'
67
import { appNotif } from '../../notifications/appNotif'
7-
import ReactGA from 'react-ga4'
88

99
enum statusStep {
1010
CONNECTED = 0,
@@ -29,7 +29,14 @@ export function ConnectButtonComponent() {
2929
if (response.success) {
3030
serversStateContext.setCurrentActive(null)
3131
window.ipc.notif(response.message)
32-
} else window.ipc.dialogError('Error', response.message)
32+
} else {
33+
if (response.message == 'wmic_not_available') {
34+
const event = new Event('wmic-helper-modal')
35+
window.dispatchEvent(event)
36+
} else {
37+
window.ipc.dialogError('Error', response.message)
38+
}
39+
}
3340
} else if (step == statusStep.DISCONNECT) {
3441
// req connect
3542
const response = await window.ipc.setDns(serversStateContext.selected)
@@ -43,7 +50,12 @@ export function ConnectButtonComponent() {
4350
value: 1,
4451
})
4552
} else {
46-
window.ipc.dialogError('Error', response.message)
53+
if (response.message == 'wmic_not_available') {
54+
const event = new Event('wmic-helper-modal')
55+
window.dispatchEvent(event)
56+
} else {
57+
window.ipc.dialogError('Error', response.message)
58+
}
4759
}
4860
}
4961

src/renderer/component/buttons/interfaces-dialog-btn-component.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { Button, Tooltip } from 'react-daisyui'
21
import { useContext, useState } from 'react'
3-
import { serversContext } from '../../context/servers.context'
2+
import { Button, Tooltip } from 'react-daisyui'
43
import { BsHddNetwork } from 'react-icons/bs'
4+
import { serversContext } from '../../context/servers.context'
55
import { NetworkOptionsModalComponent } from '../modals/network-options.component'
66

77
export function InterfacesDialogButtonComponent() {

src/renderer/component/modals/network-options.component.tsx

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React, { useContext, useEffect, useState } from 'react'
22

3-
import { setState } from '../../interfaces/react.interface'
43
import {
54
Button,
65
Card,
@@ -9,9 +8,10 @@ import {
98
Dialog,
109
} from '@material-tailwind/react'
1110
import { Select } from 'react-daisyui'
12-
import { ServersContext } from '../../interfaces/servers-context.interface'
13-
import { serversContext } from '../../context/servers.context'
1411
import { settingStore } from '../../app'
12+
import { serversContext } from '../../context/servers.context'
13+
import { setState } from '../../interfaces/react.interface'
14+
import { ServersContext } from '../../interfaces/servers-context.interface'
1515

1616
interface Props {
1717
isOpen: boolean
@@ -31,19 +31,28 @@ export function NetworkOptionsModalComponent(props: Props) {
3131
const [networkAdapters, setNetworkAdapters] = useState<string[]>([])
3232

3333
useEffect(() => {
34+
const fetchNetworkInterfaces = async () => {
35+
try {
36+
const interfaces = await window.os.getInterfaces()
37+
if ('success' in interfaces) {
38+
props.setIsOpen(false)
39+
const event = new Event('wmic-helper-modal')
40+
window.dispatchEvent(event)
41+
return
42+
}
43+
44+
const networks = interfaces.map((d) => d.name)
45+
networks.unshift('Auto')
46+
setNetworkAdapters(networks)
47+
} finally {
48+
setLoading(false)
49+
}
50+
}
51+
3452
if (props.isOpen) {
3553
const current = window.storePreload.get('settings').network_interface
36-
3754
setNetwork(current)
38-
39-
window.os
40-
.getInterfaces()
41-
.then((interfaces) => {
42-
const networks = interfaces.map((d) => d.name)
43-
networks.unshift('Auto')
44-
setNetworkAdapters(networks)
45-
})
46-
.finally(() => setLoading(false))
55+
fetchNetworkInterfaces()
4756
}
4857
}, [props.isOpen])
4958

@@ -75,7 +84,7 @@ export function NetworkOptionsModalComponent(props: Props) {
7584
<div className={'grid'}>
7685
<div>
7786
<div className="label">
78-
<span className="label-text text-lg font-normal ">
87+
<span className="text-lg font-normal label-text ">
7988
Network Interface
8089
</span>
8190
</div>
@@ -103,7 +112,7 @@ export function NetworkOptionsModalComponent(props: Props) {
103112
</div>
104113
</div>
105114
</CardBody>
106-
<CardFooter className="pt-0 flex flex-row gap-2">
115+
<CardFooter className="flex flex-row gap-2 pt-0">
107116
<Button
108117
variant="text"
109118
className="normal-case font-[balooTamma] text-xl flex-1"
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import {
2+
Button,
3+
Card,
4+
CardBody,
5+
CardFooter,
6+
Dialog,
7+
} from '@material-tailwind/react'
8+
import React, { useState } from 'react'
9+
import { BiCommentError } from 'react-icons/bi'
10+
import { FaCheck, FaCopy } from 'react-icons/fa'
11+
12+
interface Props {
13+
isOpen: boolean
14+
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>
15+
}
16+
17+
export function WmicHelperModal(props: Props) {
18+
const handleOpen = () => props.setIsOpen((cur) => !cur)
19+
const [copied, setCopied] = useState(false)
20+
21+
const handleCopy = async () => {
22+
await navigator.clipboard.writeText(
23+
'DISM /Online /Add-Capability /CapabilityName:WMIC~~~~',
24+
)
25+
setCopied(true)
26+
setTimeout(() => setCopied(false), 2000)
27+
}
28+
29+
return (
30+
<Dialog
31+
size="md"
32+
open={props.isOpen}
33+
handler={handleOpen}
34+
className="bg-transparent shadow-none"
35+
>
36+
<Card className="mx-auto w-96 dark:bg-[#282828]">
37+
<div className="mb-0 p-2 bg-[#f2f2f2] dark:bg-[#262626] dark:text-gray-400 rounded-t-2xl flex flex-row justify-between">
38+
<div className="ml-3 font-[balooTamma] text-1xl flex flex-row items-center gap-1 text-red-400/80">
39+
<BiCommentError />
40+
WMIC Not Installed
41+
</div>
42+
</div>
43+
44+
<CardBody className="flex flex-col py-2">
45+
<p className="text-[13px] dark:text-gray-400 font-[Inter] bg-[#f2f2f2] dark:bg-[#262626] p-2 rounded-md">
46+
It looks like WMIC is not installed on your system. WMIC is required
47+
for this application to function properly. Please follow the
48+
instructions below to install WMIC.
49+
</p>
50+
<div className="mt-4">
51+
<p className="text-[13px] dark:text-gray-400 font-[Inter]">
52+
1. Open Command Prompt as Administrator.
53+
</p>
54+
<p className="text-[13px] dark:text-gray-400 font-[Inter]">
55+
2. Run the following command:
56+
</p>
57+
<div className="relative mt-2">
58+
<div className="absolute left-0 p-2 top-1">
59+
<Button
60+
variant="text"
61+
className=" rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 normal-case font-[balooTamma] text-sm bg-gray-300/80 dark:bg-gray-700 dark:text-gray-400"
62+
size="sm"
63+
onClick={() => handleCopy()}
64+
>
65+
{copied ? (
66+
<FaCheck className="w-4 h-4 text-green-500" />
67+
) : (
68+
<FaCopy className="w-4 h-4" />
69+
)}
70+
</Button>
71+
</div>
72+
73+
<pre className="pl-16 bg-gray-100 dark:bg-gray-800 p-4 rounded-lg text-[13px] font-mono border border-gray-200 dark:border-gray-700 dark:text-gray-300 overflow-x-auto">
74+
DISM /Online /Add-Capability /CapabilityName:WMIC~~~~​
75+
</pre>
76+
</div>
77+
<p className="text-[13px] dark:text-gray-400 font-[Inter]">
78+
3. Restart your computer.
79+
</p>
80+
</div>
81+
</CardBody>
82+
83+
<CardFooter className="flex flex-row justify-between py-2 pt-0">
84+
<Button
85+
variant="text"
86+
className="normal-case font-[balooTamma] text-xl"
87+
color="red"
88+
size="sm"
89+
onClick={handleOpen}
90+
>
91+
Close
92+
</Button>
93+
<Button
94+
variant="text"
95+
className="normal-case font-[balooTamma] text-xl"
96+
color="blue"
97+
size="sm"
98+
onClick={() =>
99+
window.open(
100+
'https://github.com/DnsChanger/dnsChanger-desktop/issues/74',
101+
'_blank',
102+
)
103+
}
104+
>
105+
Can't Install?
106+
</Button>
107+
</CardFooter>
108+
</Card>
109+
</Dialog>
110+
)
111+
}

0 commit comments

Comments
 (0)