Skip to content

Commit f8607f6

Browse files
committed
kPAd Release 1.3.0 "Automaticis"
1 parent 554a78e commit f8607f6

File tree

3 files changed

+174
-10
lines changed

3 files changed

+174
-10
lines changed

.github/workflows/build.yml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ jobs:
2525
2626
- name: Build job ARM64
2727
run: |
28-
python -m nuitka --standalone --onefile --macos-create-app-bundle --macos-app-name='kPad ARM64' --macos-app-version='1.2.2' --enable-plugin=tk-inter --assume-yes-for-downloads main.py
28+
python -m nuitka --standalone --onefile --macos-create-app-bundle --macos-app-name='kPad ARM64' --macos-app-version='1.3.0' --enable-plugin=tk-inter --assume-yes-for-downloads main.py
2929
mv main.app kPad-mac_arm64.app
3030
3131
- name: Build job Intel
3232
run: |
33-
arch -x86_64 python -m nuitka --standalone --onefile --macos-create-app-bundle --macos-app-name='kPad Intel' --macos-app-version='1.2.2' --enable-plugin=tk-inter --assume-yes-for-downloads main.py
33+
arch -x86_64 python -m nuitka --standalone --onefile --macos-create-app-bundle --macos-app-name='kPad Intel' --macos-app-version='1.3.0' --enable-plugin=tk-inter --assume-yes-for-downloads main.py
3434
mv main.app kPad-mac_x86_64.app
3535
3636
- name: Create Info.plist for ARM64
@@ -47,11 +47,22 @@ jobs:
4747
<key>CFBundleIdentifier</key>
4848
<string>com.maxhatei2.kPad</string>
4949
<key>CFBundleVersion</key>
50-
<string>1.2.2</string>
50+
<string>1.3.0</string>
5151
<key>CFBundleExecutable</key>
5252
<string>main</string>
5353
<key>CFBundlePackageType</key>
5454
<string>APPL</string>
55+
<key>CFBundleURLTypes</key>
56+
<array>
57+
<dict>
58+
<key>CFBundleURLName</key>
59+
<string>kPad</string>
60+
<key>CFBundleURLSchemes</key>
61+
<array>
62+
<string>kpad</string>
63+
</array>
64+
</dict>
65+
</array>
5566
</dict>
5667
</plist>
5768
EOF
@@ -70,7 +81,7 @@ jobs:
7081
<key>CFBundleIdentifier</key>
7182
<string>com.maxhatei2.kPad</string>
7283
<key>CFBundleVersion</key>
73-
<string>1.2.2</string>
84+
<string>1.3.0</string>
7485
<key>CFBundleExecutable</key>
7586
<string>main</string>
7687
<key>CFBundlePackageType</key>

WPI.reg

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Windows Registry Editor Version 5.00
2+
3+
[HKEY_CURRENT_USER\Software\Classes\kpad]
4+
@="URL:kPad Protocol"
5+
"URL Protocol"=""
6+
7+
[HKEY_CURRENT_USER\Software\Classes\kpad\shell\open\command]
8+
@="\"C:\\Users\\%USERNAME%\\AppData\\Local\\kPad\\kPad-Windows_x86_64.exe\" \"%1\""

main.py

Lines changed: 151 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
11
import customtkinter as ctk
22
from tkinter.filedialog import asksaveasfilename, askopenfilename
33
from tkinter import Menu
4-
from tkinter.messagebox import showinfo, askyesno, askyesnocancel
4+
from tkinter.messagebox import showinfo, askyesno, askyesnocancel, showerror
55
from tkinter import simpledialog
6-
import time, threading
7-
import os, json, platform
8-
import venv, sys
6+
import time, threading, shutil
7+
import os, json, platform, tempfile
8+
import venv, sys, urllib.parse, urllib.request
9+
from io import BytesIO
10+
from zipfile import ZipFile
11+
12+
VERSION = '1.3.67'
13+
ONL_VER_URL = 'https://raw.githubusercontent.com/maxhatei2/kPad/refs/heads/main/.kv'
14+
DOWNLOAD_URLS = {
15+
('Darwin', 'arm64'): 'https://github.com/maxhatei2/kPad/releases/latest/kPad-mac_arm64.zip',
16+
('Darwin', 'x86_64'): 'https://github.com/maxhatei2/kPad/releases/latest/kPad-mac_x86_64.zip',
17+
(('Windows', 'arm64'), ('Windows', 'x86_64')): 'https://github.com/maxhatei2/kPad/releases/latest/kPad-Windows_x86_64.exe.zip'
18+
}
919

1020
# ██╗░░██╗██████╗░░█████╗░██████╗░
1121
# ██║░██╔╝██╔══██╗██╔══██╗██╔══██╗
1222
# █████═╝░██████╔╝███████║██║░░██║
1323
# ██╔═██╗░██╔═══╝░██╔══██║██║░░██║
1424
# ██║░╚██╗██║░░░░░██║░░██║██████╔╝
1525
# ╚═╝░░╚═╝╚═╝░░░░░╚═╝░░╚═╝╚═════╝░
16-
# Version 1.2.2 [SOURCE CODE]
26+
# Version 1.3.0 [SOURCE CODE]
1727

1828

1929
if platform.system() == 'Darwin':
@@ -52,6 +62,34 @@
5262

5363
_fonts = ['Menlo', 'Monaco', 'Helvetica', 'Arial', 'Times New Roman', 'Georgia', 'Avenir', 'Baskerville', 'Futura', 'Verdana', 'Gill Sans', 'Courier', 'Optima', 'American Typewriter']
5464

65+
try:
66+
PROTOCOL_URL = sys.argv[1]
67+
parsed = urllib.parse.urlparse(PROTOCOL_URL)
68+
if parsed.netloc == 'InstallPlugin':
69+
url = parsed.query.split('=')[1]
70+
try:
71+
response = urllib.request.urlopen(url)
72+
data = response.read()
73+
with ZipFile(BytesIO(data)) as z:
74+
z.extract(plugin_dir)
75+
showinfo('Plugin installed', 'Plugin was installed from the link. If everything is correct, you\'ll find it to run in the \'Plugins\' tab to run.')
76+
except Exception as e: showerror('Error while installing plugin', f'Error while installing the plugin: -> {e} <- Check your Internet connection or the URL in the link.')
77+
except: pass
78+
79+
def AutoUpdate(gimme_your_self):
80+
try:
81+
online_ver = urllib.request.urlopen(ONL_VER_URL).read().decode().strip()
82+
if online_ver != VERSION:
83+
to_update = askyesno('Update available!', f'A new update to kPad {online_ver}, is available to install! Do it now?')
84+
if to_update:
85+
DownloadUpdateWindow(gimme_your_self)
86+
else:
87+
gimme_your_self.stats_line_col.configure(text='User rejected update | Ln: 1 Col: 1 Ch: 0')
88+
else:
89+
gimme_your_self.stats_line_col.configure(text='Up to date | Ln: 1 Col: 1 Ch: 0')
90+
except Exception as e:
91+
gimme_your_self.stats_line_col.configure(text=f'⚠️ Warning! Could not check for updates: {e} | Ln: 1 Col: 1 Ch: 0')
92+
5593
class PluginAPI:
5694
def __init__(self, textbox, appinstance):
5795
self.textbox = textbox
@@ -141,6 +179,8 @@ class App(ctk.CTk):
141179
def __init__(self, title, geometry):
142180
super().__init__()
143181

182+
self.after(1, lambda: threading.Thread(AutoUpdate(gimme_your_self=self)))
183+
144184
import importlib.util
145185

146186
self.textbox = ctk.CTkTextbox(self, undo=CONFIGURATION['undo']['enabled'], autoseparators=CONFIGURATION['undo']['separate_edits_from_undos'], maxundo=CONFIGURATION['undo']['max_undo'])
@@ -482,4 +522,109 @@ def make_separator():
482522
make_separator()
483523
self.main.pack(fill='both', expand='True')
484524

485-
App('kPad - Untitled', CONFIGURATION['window_geometry']).mainloop()
525+
class DownloadUpdateWindow(ctk.CTkToplevel):
526+
def __init__(self, parent):
527+
super().__init__(parent)
528+
529+
syst = platform.system()
530+
arch = platform.machine().lower()
531+
if arch in ("x86_64", "amd64"):
532+
arch = "x86_64"
533+
elif arch in ("arm64", "aarch64"):
534+
arch = "arm64"
535+
536+
537+
def create_start_menu_shortcut(exe_path, name="kPad"):
538+
start_menu = os.path.join(os.environ["APPDATA"], r"Microsoft\Windows\Start Menu\Programs")
539+
shortcut_path = os.path.join(start_menu, f"{name}.lnk")
540+
ps_cmd = f'''
541+
$WshShell = New-Object -ComObject WScript.Shell;
542+
$Shortcut = $WshShell.CreateShortcut("{shortcut_path}");
543+
$Shortcut.TargetPath = "{exe_path}";
544+
$Shortcut.WorkingDirectory = "{os.path.dirname(exe_path)}";
545+
$Shortcut.Save();
546+
'''
547+
os.system(f'powershell -Command "{ps_cmd}"')
548+
549+
def Download():
550+
syst = platform.system()
551+
arch = platform.machine().lower()
552+
if arch in ("x86_64", "amd64"):
553+
arch = "x86_64"
554+
elif arch in ("arm64", "aarch64"):
555+
arch = "arm64"
556+
requiredUrl = DOWNLOAD_URLS.get((syst, arch))
557+
if not requiredUrl:
558+
showerror("Error", f"No download available for {syst} {arch}")
559+
return
560+
if syst == 'Darwin':
561+
try:
562+
data = urllib.request.urlopen(requiredUrl).read()
563+
with tempfile.TemporaryDirectory() as tmp_outer:
564+
outer_zip_path = os.path.join(tmp_outer, os.path.basename(requiredUrl))
565+
with open(outer_zip_path, "wb") as f:
566+
f.write(data)
567+
with ZipFile(outer_zip_path) as outer_zip:
568+
outer_zip.extractall(tmp_outer)
569+
inner_zip_path = None
570+
for root, _, files in os.walk(tmp_outer):
571+
for file in files:
572+
if file.endswith(".zip"):
573+
inner_zip_path = os.path.join(root, file)
574+
break
575+
if inner_zip_path:
576+
break
577+
if not inner_zip_path:
578+
showerror("Error", "Could not find inner zip containing the .app")
579+
self.destroy()
580+
return
581+
with tempfile.TemporaryDirectory() as tmp_inner:
582+
with ZipFile(inner_zip_path) as inner_zip:
583+
inner_zip.extractall(tmp_inner)
584+
app_folder = None
585+
for item in os.listdir(tmp_inner):
586+
if item.endswith(".app"):
587+
app_folder = os.path.join(tmp_inner, item)
588+
break
589+
if not app_folder:
590+
showerror("Error", "Could not find .app inside the inner zip")
591+
self.destroy()
592+
return
593+
dest = "/Applications/kPad.app"
594+
if os.path.exists(dest):
595+
shutil.rmtree(dest)
596+
shutil.move(app_folder, dest)
597+
showinfo("Update Installed", f"kPad installed to {dest}")
598+
except Exception as e:
599+
showerror("Error", f"[Errno EXCEPTION]: Failed to install update: {e}")
600+
self.destroy()
601+
return
602+
elif syst == 'Windows':
603+
dest_folder = os.path.join(os.getenv("LOCALAPPDATA"), "kPad")
604+
os.makedirs(dest_folder, exist_ok=True)
605+
dest_file = os.path.join(dest_folder, "kPad.exe")
606+
try:
607+
data = urllib.request.urlopen(requiredUrl).read()
608+
with tempfile.TemporaryDirectory() as tmp:
609+
with ZipFile(BytesIO(data)) as zip:
610+
zip.extract(tmp)
611+
shutil.move(f'{tmp}/kPad-Windows_x86_64/kPad-Windows_x86_64.exe', dest_file)
612+
create_start_menu_shortcut(dest_file, f'kPad')
613+
except Exception as e:
614+
showerror('Failed!', f'[Errno EXCEPTION]: {e}')
615+
self.destroy()
616+
return
617+
618+
downloading_new_version = ctk.CTkLabel(self, text='Downloading update...')
619+
downloading_new_version.pack()
620+
this_might_etc = ctk.CTkLabel(self, text='This might take a bit, depending on your Internet connection. When done, the app will close.')
621+
this_might_etc.pack()
622+
load_pbar = ctk.CTkProgressBar(self, mode='indeterminate')
623+
load_pbar.pack()
624+
load_pbar.start()
625+
626+
self.after(1, Download)
627+
628+
629+
app = App('kPad - Untitled', CONFIGURATION['window_geometry'])
630+
app.mainloop()

0 commit comments

Comments
 (0)