DEV Community

Cover image for I make a simple desktop app with Python
Victor Chendra
Victor Chendra

Posted on

I make a simple desktop app with Python

Here, I will show my mini project where I built a desktop app with Python customtkinter and pyinstaller.

Introduction:
We often need to retrieve a Wi-Fi password from a device we’ve used in the past, usually because we’ve forgotten it or someone else needs to connect their new device. However, sharing a QR code isn't always an option. For example, the connected device might be nowhere near the router.

Imagine my Device A was previously connected to Wi-Fi 'XYZ' in Indonesia, but I am currently in Japan with that device. My friend in Indonesia wants to connect their Device B to that same 'XYZ' network. Since my device isn't currently within range of the signal, I can't simply generate a 'Share Wi-Fi' QR code. Need a way to view the saved password.

Problem:
See, the troubles start when we need to share the access. Modern smartphones have made sharing Wi-Fi easy via QR codes, BUT the catch is as what have been discussed on the introduction.

Solve:
And that's why I came up with this idea. I want to make it as an app. A one tap solution without entering manual script to cmd.

DISCLAIMER:

Please use this guide as intended for personal use or for helping friends or family to access networks you already have permission to use. And always be cautious when sharing Wi-Fi passwords.

1. First step (getting familiar with batch command)

If you run this command on Windows cmd:

netsh wlan show profiles
Enter fullscreen mode Exit fullscreen mode

This will list all the device's connected Wi-Fi profiles 👇.

Image 1 list all profile wifi

2. Show the passwords

To display the password, simply add this:

netsh wlan show profiles name="<profile_name>" key=clear | findstr Key
Enter fullscreen mode Exit fullscreen mode

Image 2 example show wifi password

3. Automating

Now we know how to retrieve the password, but only for single profile. Easily, we just need to loop the given list of profiles. In Python, we need to use import subprocess library.

import subprocess
from subprocess import CalledProcessError


def get_all_connected_wifi_profile() -> list[str]:
    command = f"netsh wlan show profiles"
    try:
        all_wifis = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT).decode("utf-8")
    except UnicodeDecodeError:
        all_wifis = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT, encoding="latin-1")
    except CalledProcessError as e:
        print(f"Error: {e}")
        print(f"Command output: {e.output.decode('utf-8').strip()}")

    profiles = [
        line.split(":")[1].strip()
        for line in all_wifis.split("\n")
        if "All User Profile" in line
    ]
    return profiles


def get_password(name: str) -> str:
    try:
        command = f'netsh wlan show profiles name="{name}" key=clear | findstr Key'
        try:
            output = (subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT).decode("utf-8").strip())
        except UnicodeDecodeError:
            output = (subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT, encoding="latin-1").strip())

        if "Key Content" in output:
            password = output.split(":")[1].strip()
            return password
        else:
            return None

    except CalledProcessError as e:
        print(f"Error: {e}")
        print(f"Command output: {e.output.decode('utf-8').strip()}")
Enter fullscreen mode Exit fullscreen mode

Then iterate them like this

if __name__ == "__main__":
    all_profiles = get_all_connected_wifi_profile()

    for i, network in enumerate(all_profiles):
        password = get_password(network)
        print(f'{i+1}. {network} --> "{password}"')
Enter fullscreen mode Exit fullscreen mode

Congratulations, now we get all the data we need. Just one thing, enhance it and put is a one tap solutions (as an app .exe).

4. The Tkinter things (UI)

Here, I use customtkinter library, why? Because it looks more modern than normal tkinter.

CustomTkinter is a python desktop UI-library based on Tkinter, which provides modern looking and fully customizable widgets. With CustomTkinter you'll get a consistent look across all desktop platforms (Windows, macOS, Linux).

Source: CustomTkinter Documentation

It's too much to explain the UI. Hopefully, you can get familiar with customtkinter by yourself by reading the documentations.

Let's go back and..., here is the full code:

# main.py

import subprocess
import customtkinter as ctk
from subprocess import CalledProcessError


class RetriveConnectedWifiPasswordApp:
    def __init__(self) -> None:
        self.root = ctk.CTk()

        self.app_width = 700
        self.app_height = 680

        self.color1 = "#D2DE32"
        self.color2 = "#FFFFDD"
        self.bgcolor = "#61A3BA"

        # icon_path = "icons8-wifi-96.ico"
        # self.root.iconbitmap(icon_path)
        self.screenwidth = self.root.winfo_screenwidth()
        self.screenheight = self.root.winfo_screenheight()
        self.x = (self.screenwidth // 2) - (self.app_width // 2) + 120
        self.y = (self.screenheight // 2) - (self.app_height // 2) - 38

        self.root.title("Get Connected Wi-Fi Password")
        self.root.geometry(f"{self.app_width}x{self.app_height}+{self.x}+{self.y}")
        self.root.resizable(False, False)
        self.root.configure(bg=self.bgcolor)

        self.header = None
        self.scrollable = None
        self.letsGoButton = None

        self.resultLabel = None
        self.data = {}

    @staticmethod
    def get_password(name: str) -> str:
        try:
            command = f'netsh wlan show profiles name="{name}" key=clear | findstr Key'
            try:
                output = (subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT).decode("utf-8").strip())
            except UnicodeDecodeError:
                output = (subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT, encoding="latin-1").strip())

            if "Key Content" in output:
                password = output.split(":")[1].strip()
                return password
            else:
                return None

        except CalledProcessError as e:
            print(f"Error: {e}")
            print(f"Command output: {e.output.decode('utf-8').strip()}")

    @staticmethod
    def get_all_connected_wifi_profile() -> list[str]:
        command = f"netsh wlan show profiles"
        try:
            all_wifis = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT).decode("utf-8")
        except UnicodeDecodeError:
            all_wifis = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT, encoding="latin-1")
        except CalledProcessError as e:
            print(f"Error: {e}")
            print(f"Command output: {e.output.decode('utf-8').strip()}")

        """
        # all_wifis looks like this

        Profiles on interface Wi-Fi:

        Group policy profiles (read only)
        ---------------------------------
            <None>

        User profiles
        -------------
            All User Profile     : cencored1
            All User Profile     : cencored2
            All User Profile     : cencored3
            All User Profile     : cencored4
            All User Profile     : TP-Link_3E16
            All User Profile     : Redmi Note 9 Pro
            All User Profile     : TP-LINK_C32E7E

        """

        profiles = [
            line.split(":")[1].strip()
            for line in all_wifis.split("\n")
            if "All User Profile" in line
        ]
        return profiles

    def letsGoButtonEvent(self):
        self.letsGoButton.configure(state="disabled")
        all_profiles = self.get_all_connected_wifi_profile()

        labelHeader = ctk.CTkLabel(
            self.scrollable,
            text="Connected Networks",
            font=("Arial", 18, "bold"),
            # bg_color="#555555",
            # text_color="#FFFFFF",
            width=200,
        )
        labelHeader.pack(side="top", pady=(8, 10))

        for i, network in enumerate(all_profiles):
            password = self.get_password(network)

            # write data
            self.data[network] = password

            if password == None:
                password = str(password).lower()
            else:
                password = "[" + password + "]"
            text = network.rjust(40, " ") + " --> " + password.ljust(40, " ")

            container = ctk.CTkFrame(self.scrollable)
            if i + 1 == len(all_profiles):
                container.pack(side="top", pady=(0, 16))
            else:
                container.pack(side="top")

            number = ctk.CTkLabel(
                container,
                text=f"{(str(i+1) + ' ').rjust(5, ' ')}",
                font=("Consolas", 11, "bold"),
                # bg_color="#555555",
            )
            number.pack(side="left", pady=(0, 2), padx=(0, 2))

            label = ctk.CTkLabel(
                container,
                text=text,
                font=("Consolas", 11),
                # bg_color="#555555",
                width=200,
            )
            label.pack(side="left", pady=(0, 2))

            self.root.update()

    def initialize(self):
        self.header = ctk.CTkLabel(
            self.root,
            text="Get Connected Wi-Fi Password",
            font=("Arial", 26, "bold"),
            # bg_color=self.color1,
            # text_color="#000000",
            width=self.screenwidth,
            height=30,
        )
        self.header.pack(pady=(24, 0))

        self.scrollable = ctk.CTkScrollableFrame(
            self.root, width=self.app_width - 100, height=500
        )
        self.scrollable.pack(pady=(24, 0))

        self.letsGoButton = ctk.CTkButton(
            self.root,
            text="Let's go",
            command=self.letsGoButtonEvent,
            font=("Arial", 12, "bold"),
            width=150,
            height=34,
            corner_radius=18,
        )
        self.letsGoButton.pack(side="top", pady=(24, 0))

    def run(self):
        self.initialize()
        self.root.mainloop()


if __name__ == "__main__":
    app = RetriveConnectedWifiPasswordApp()
    app.run()

Enter fullscreen mode Exit fullscreen mode

Run it:

python main.py
Enter fullscreen mode Exit fullscreen mode

You will get something like this:

Image 3 the result

5. Compile it to .exe file

Why we need to compile it to .exe file? Because, not every PC has python environment installed. And it also make things easier for us when we need to get previously connected networks.

  • Install pyinstaller:
pip install pyinstaller
Enter fullscreen mode Exit fullscreen mode
  • Run this command to complie
pyinstaller main.py --onefile --windowed --icon="assets/icons8-wifi-96.ico" --name="Connected Networks"
Enter fullscreen mode Exit fullscreen mode

The --icon="..." tag is optional, you can remove it. It's for the application icon.

After that, it will create new 2 directories dist/ and build/. Locate the Connected Networks.exe, should be in dist/. You can safely delete the build/ folder. There maybe also a Connected Networks.spec file. You can delete it also safely.

  • Check the result

Check if it works fine and as expected by open the compiled file (.exe) located on dist/.

6. Finish

And congrats, we made it. Hopefully this is useful.

Here's the link to the my repository for more details:
GitHub Repository

Top comments (0)