CTkDeb 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: CTkDeb
3
+ Version: 0.3.1
4
+ Summary: A tool for easily packaging CustomTkinter projects into DEBs
5
+ Author-email: limafresh <173814432+limafresh@users.noreply.github.com>
6
+ Maintainer-email: limafresh <173814432+limafresh@users.noreply.github.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/limafresh/CTkDeb
9
+ Project-URL: Documentation, https://github.com/limafresh/CTkDeb
10
+ Project-URL: Repository, https://github.com/limafresh/CTkDeb.git
11
+ Project-URL: Issues, https://github.com/limafresh/CTkDeb/issues
12
+ Project-URL: Changelog, https://github.com/limafresh/CTkDeb/blob/main/CHANGELOG.md
13
+ Keywords: gui,customtkinter,deb,linux,package
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Natural Language :: English
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python
19
+ Classifier: Programming Language :: Python :: 3
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: gui
24
+ Requires-Dist: customtkinter; extra == "gui"
25
+ Dynamic: license-file
26
+
27
+ # CTkDeb
28
+
29
+ ![Static Badge](https://img.shields.io/badge/No-PyInstaller-blue?style=for-the-badge)
30
+ ![Static Badge](https://img.shields.io/badge/Simple-GUI-blue?style=for-the-badge)
31
+
32
+ <p align="center">
33
+ <img src="https://raw.githubusercontent.com/limafresh/CTkDeb/main/ctkdeb/icon.png" width="100" height="100">
34
+ </p>
35
+
36
+ The easiest way to create a DEB of your CustomTkinter project! You can create a small DEB without deep packaging experience.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install "CTkDeb[gui] @ git+https://github.com/limafresh/CTkDeb.git"
42
+ ```
43
+
44
+ Packaging requires the `dpkg-deb` tool (it is pre-installed on most Debian-like systems, so you probably won't need to install it separately).
45
+
46
+ ## Run
47
+
48
+ Run this in the project folder (relative to which the paths in the config are specified)
49
+
50
+ ```
51
+ ctkdeb-gui
52
+ ```
53
+
54
+ ## GUI
55
+
56
+ ![Screenshot](https://raw.githubusercontent.com/limafresh/CTkDeb/main/screenshot.png)
57
+
58
+ ## CLI
59
+
60
+ ```
61
+ ctkdeb config.json
62
+ ```
63
+
64
+ View all arguments:
65
+
66
+ ```
67
+ ctkdeb --help
68
+ ```
69
+
70
+ ## Python
71
+
72
+ ```python
73
+ from ctkdeb import Packager
74
+
75
+ packager = Packager("config.json")
76
+ packager.create_deb()
77
+ ```
78
+
79
+ Arguments:
80
+
81
+ | Argument | Type | Description |
82
+ |-|-|-|
83
+ | **json_path** | *str* | Path to the JSON config, required |
84
+ | **project_folder** | *str* | Path to the project folder (if the command is not executed from it) |
85
+ | **desktop_entry** | *str* | Path to the custom desktop entry (or its text) |
86
+ | **output_folder** | *str* | Path to the output folder |
87
+
88
+ ## FAQ
89
+
90
+ - Do I need to specify Tkinter and CustomTkinter as dependencies?
91
+
92
+ No, CTkDeb has already taken care of that; just specify third-party dependencies, if any.
93
+
94
+ ## Other
95
+
96
+ App icon: Claude Sonnet 5.
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ CTkDeb.egg-info/PKG-INFO
5
+ CTkDeb.egg-info/SOURCES.txt
6
+ CTkDeb.egg-info/dependency_links.txt
7
+ CTkDeb.egg-info/entry_points.txt
8
+ CTkDeb.egg-info/requires.txt
9
+ CTkDeb.egg-info/top_level.txt
10
+ ctkdeb/__init__.py
11
+ ctkdeb/gui.py
12
+ ctkdeb/icon.png
13
+ ctkdeb/main.py
14
+ ctkdeb/packager.py
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ ctkdeb = ctkdeb.main:cli
3
+
4
+ [gui_scripts]
5
+ ctkdeb-gui = ctkdeb.main:gui
@@ -0,0 +1,3 @@
1
+
2
+ [gui]
3
+ customtkinter
@@ -0,0 +1 @@
1
+ ctkdeb
ctkdeb-0.3.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 limafresh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
ctkdeb-0.3.1/PKG-INFO ADDED
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: CTkDeb
3
+ Version: 0.3.1
4
+ Summary: A tool for easily packaging CustomTkinter projects into DEBs
5
+ Author-email: limafresh <173814432+limafresh@users.noreply.github.com>
6
+ Maintainer-email: limafresh <173814432+limafresh@users.noreply.github.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/limafresh/CTkDeb
9
+ Project-URL: Documentation, https://github.com/limafresh/CTkDeb
10
+ Project-URL: Repository, https://github.com/limafresh/CTkDeb.git
11
+ Project-URL: Issues, https://github.com/limafresh/CTkDeb/issues
12
+ Project-URL: Changelog, https://github.com/limafresh/CTkDeb/blob/main/CHANGELOG.md
13
+ Keywords: gui,customtkinter,deb,linux,package
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Natural Language :: English
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python
19
+ Classifier: Programming Language :: Python :: 3
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: gui
24
+ Requires-Dist: customtkinter; extra == "gui"
25
+ Dynamic: license-file
26
+
27
+ # CTkDeb
28
+
29
+ ![Static Badge](https://img.shields.io/badge/No-PyInstaller-blue?style=for-the-badge)
30
+ ![Static Badge](https://img.shields.io/badge/Simple-GUI-blue?style=for-the-badge)
31
+
32
+ <p align="center">
33
+ <img src="https://raw.githubusercontent.com/limafresh/CTkDeb/main/ctkdeb/icon.png" width="100" height="100">
34
+ </p>
35
+
36
+ The easiest way to create a DEB of your CustomTkinter project! You can create a small DEB without deep packaging experience.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install "CTkDeb[gui] @ git+https://github.com/limafresh/CTkDeb.git"
42
+ ```
43
+
44
+ Packaging requires the `dpkg-deb` tool (it is pre-installed on most Debian-like systems, so you probably won't need to install it separately).
45
+
46
+ ## Run
47
+
48
+ Run this in the project folder (relative to which the paths in the config are specified)
49
+
50
+ ```
51
+ ctkdeb-gui
52
+ ```
53
+
54
+ ## GUI
55
+
56
+ ![Screenshot](https://raw.githubusercontent.com/limafresh/CTkDeb/main/screenshot.png)
57
+
58
+ ## CLI
59
+
60
+ ```
61
+ ctkdeb config.json
62
+ ```
63
+
64
+ View all arguments:
65
+
66
+ ```
67
+ ctkdeb --help
68
+ ```
69
+
70
+ ## Python
71
+
72
+ ```python
73
+ from ctkdeb import Packager
74
+
75
+ packager = Packager("config.json")
76
+ packager.create_deb()
77
+ ```
78
+
79
+ Arguments:
80
+
81
+ | Argument | Type | Description |
82
+ |-|-|-|
83
+ | **json_path** | *str* | Path to the JSON config, required |
84
+ | **project_folder** | *str* | Path to the project folder (if the command is not executed from it) |
85
+ | **desktop_entry** | *str* | Path to the custom desktop entry (or its text) |
86
+ | **output_folder** | *str* | Path to the output folder |
87
+
88
+ ## FAQ
89
+
90
+ - Do I need to specify Tkinter and CustomTkinter as dependencies?
91
+
92
+ No, CTkDeb has already taken care of that; just specify third-party dependencies, if any.
93
+
94
+ ## Other
95
+
96
+ App icon: Claude Sonnet 5.
ctkdeb-0.3.1/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # CTkDeb
2
+
3
+ ![Static Badge](https://img.shields.io/badge/No-PyInstaller-blue?style=for-the-badge)
4
+ ![Static Badge](https://img.shields.io/badge/Simple-GUI-blue?style=for-the-badge)
5
+
6
+ <p align="center">
7
+ <img src="https://raw.githubusercontent.com/limafresh/CTkDeb/main/ctkdeb/icon.png" width="100" height="100">
8
+ </p>
9
+
10
+ The easiest way to create a DEB of your CustomTkinter project! You can create a small DEB without deep packaging experience.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pip install "CTkDeb[gui] @ git+https://github.com/limafresh/CTkDeb.git"
16
+ ```
17
+
18
+ Packaging requires the `dpkg-deb` tool (it is pre-installed on most Debian-like systems, so you probably won't need to install it separately).
19
+
20
+ ## Run
21
+
22
+ Run this in the project folder (relative to which the paths in the config are specified)
23
+
24
+ ```
25
+ ctkdeb-gui
26
+ ```
27
+
28
+ ## GUI
29
+
30
+ ![Screenshot](https://raw.githubusercontent.com/limafresh/CTkDeb/main/screenshot.png)
31
+
32
+ ## CLI
33
+
34
+ ```
35
+ ctkdeb config.json
36
+ ```
37
+
38
+ View all arguments:
39
+
40
+ ```
41
+ ctkdeb --help
42
+ ```
43
+
44
+ ## Python
45
+
46
+ ```python
47
+ from ctkdeb import Packager
48
+
49
+ packager = Packager("config.json")
50
+ packager.create_deb()
51
+ ```
52
+
53
+ Arguments:
54
+
55
+ | Argument | Type | Description |
56
+ |-|-|-|
57
+ | **json_path** | *str* | Path to the JSON config, required |
58
+ | **project_folder** | *str* | Path to the project folder (if the command is not executed from it) |
59
+ | **desktop_entry** | *str* | Path to the custom desktop entry (or its text) |
60
+ | **output_folder** | *str* | Path to the output folder |
61
+
62
+ ## FAQ
63
+
64
+ - Do I need to specify Tkinter and CustomTkinter as dependencies?
65
+
66
+ No, CTkDeb has already taken care of that; just specify third-party dependencies, if any.
67
+
68
+ ## Other
69
+
70
+ App icon: Claude Sonnet 5.
@@ -0,0 +1 @@
1
+ from .packager import Packager
@@ -0,0 +1,286 @@
1
+ import customtkinter as ctk
2
+ from tkinter import messagebox, filedialog, PhotoImage
3
+ import os
4
+ import sys
5
+ import json
6
+ import tempfile
7
+
8
+ from . import packager
9
+
10
+ PATH = os.path.dirname(os.path.realpath(__file__))
11
+
12
+ class CTkDeb(ctk.CTk):
13
+ def __init__(self):
14
+ super().__init__()
15
+
16
+ self.appearance_mode_var = ctk.StringVar(value="light")
17
+ self.update_appearance_mode()
18
+
19
+ self.title("CTkDeb")
20
+ self.geometry("500x600")
21
+
22
+ self.wm_iconbitmap()
23
+ iconpath = PhotoImage(file=os.path.join(PATH, "icon.png"))
24
+ self.iconphoto(False, iconpath)
25
+
26
+ self.title_font = ctk.CTkFont(size=18)
27
+
28
+ self.fields = {
29
+ "app": {
30
+ "title": "App",
31
+ "items": [
32
+ {"id": "name", "label": "Name", "placeholder": "myapp"},
33
+ {"id": "version", "label": "Version", "placeholder": "0.16.2"},
34
+ {"id": "entryPoint", "label": "Entry point", "placeholder": "main.py"},
35
+ {"id": "icon", "label": "Icon", "placeholder": "assets/icon.png"},
36
+ {"id": "iconSize", "label": "Icon size", "placeholder": "512x512"},
37
+ {"id": "pipDependencies", "label": "Pip dependencies", "placeholder": "somelib potatolib"}
38
+ ]
39
+ },
40
+ "desktopEntry": {
41
+ "title": "Desktop entry",
42
+ "items": [
43
+ {"type": "button", "text": "Add field", "command": self.add_new_desktop_entry_field},
44
+ {"id": "Name", "label": "Name", "placeholder": "MyApp"},
45
+ {"id": "Comment", "label": "Comment", "placeholder": "A simple alarm clock app"}
46
+ ]
47
+ },
48
+ "documentationFiles": {
49
+ "title": "Documentation files (licenses, README.md etc.)",
50
+ "items": [
51
+ {"type": "button", "text": "Add file", "command": self.add_new_doc_file}
52
+ ]
53
+ },
54
+ "deb": {
55
+ "title": "DEB-specific settings",
56
+ "items": [
57
+ {"id": "section", "label": "Section", "placeholder": "graphics"},
58
+ {"id": "depends", "label": "Depends", "placeholder": "somelib, potatolib"},
59
+ {"id": "maintainer", "label": "Maintainer", "placeholder": "Ivan Ivanov <ivan_dev@example.com>"},
60
+ {"id": "description", "label": "Description", "placeholder": "A simple alarm clock app"},
61
+ ]
62
+ }
63
+ }
64
+ self.entries = {}
65
+ self.desktop_entry_entries = {}
66
+ self.doc_entries = []
67
+
68
+ self.build_interface()
69
+
70
+ def update_appearance_mode(self):
71
+ ctk.set_appearance_mode(self.appearance_mode_var.get())
72
+
73
+ def build_interface(self):
74
+ self.create_header()
75
+ self.create_cards()
76
+ self.create_footer()
77
+
78
+ def create_header(self):
79
+ header = ctk.CTkFrame(self)
80
+ header.pack(padx=5, pady=5, side=ctk.TOP, fill=ctk.X)
81
+
82
+ ctk.CTkLabel(header, text="CTkDeb", font=self.title_font).pack(padx=10, pady=10, side=ctk.LEFT)
83
+
84
+ appearance_switch = ctk.CTkSwitch(header, text="Dark", variable=self.appearance_mode_var,
85
+ offvalue="light", onvalue="dark", command=self.update_appearance_mode)
86
+ appearance_switch.pack(padx=10, pady=10, side=ctk.RIGHT)
87
+
88
+ def create_footer(self):
89
+ footer = ctk.CTkFrame(self)
90
+ footer.pack(padx=5, pady=5, side=ctk.BOTTOM, fill=ctk.X)
91
+
92
+ import_config_button = ctk.CTkButton(footer, text="Import config", command=self.import_config)
93
+ import_config_button.grid(row=0, column=0, padx=10, pady=10, sticky=ctk.EW)
94
+
95
+ export_config_button = ctk.CTkButton(footer, text="Export config", command=self.export_config)
96
+ export_config_button.grid(row=0, column=1, padx=10, pady=10, sticky=ctk.EW)
97
+
98
+ create_button = ctk.CTkButton(footer, text="CREATE", command=self.create_deb)
99
+ create_button.grid(row=0, column=2, padx=10, pady=10, sticky=ctk.EW)
100
+
101
+ for i in footer.winfo_children():
102
+ footer.columnconfigure(i, weight=1)
103
+
104
+ def create_cards(self):
105
+ scrollable = ctk.CTkScrollableFrame(self, fg_color="transparent")
106
+ scrollable.pack(padx=5, fill=ctk.BOTH, expand=True)
107
+
108
+ for section, items in self.fields.items():
109
+ self.entries[section] = {}
110
+
111
+ card = ctk.CTkFrame(scrollable)
112
+ card.pack(padx=10, pady=10, fill=ctk.X)
113
+
114
+ card.grid_columnconfigure(1, weight=1)
115
+
116
+ if section == "desktopEntry":
117
+ self.desktop_entry_card = card
118
+ elif section == "documentationFiles":
119
+ self.docs_card = card
120
+
121
+ title = ctk.CTkLabel(card, text=items["title"], font=self.title_font)
122
+ title.grid(row=0, column=0, padx=10, pady=10, columnspan=2, sticky=ctk.W)
123
+
124
+ for row, widget in enumerate(items["items"], start=1):
125
+ self.create_widget(section, card, widget, row)
126
+
127
+ def create_widget(self, section: str, card: ctk.CTkEntry, widget: dict, row: int):
128
+ if widget.get("type") == "button":
129
+ button = ctk.CTkButton(card, text=widget["text"], command=widget["command"])
130
+ button.grid(row=row, column=0, padx=10, pady=(0, 10), columnspan=2, sticky=ctk.EW)
131
+ else:
132
+ if packager.REQUIRED_FIELDS.get(section) and widget["id"] in packager.REQUIRED_FIELDS.get(section):
133
+ characters = "*:"
134
+ else:
135
+ characters = ":"
136
+
137
+ entry = self.add_labeled_entry(card, widget["label"] + characters, widget["placeholder"], row)
138
+ self.entries[section][widget["id"]] = entry
139
+
140
+ def add_labeled_entry(
141
+ self,
142
+ card: ctk.CTkFrame,
143
+ label_text: str,
144
+ placeholder: str,
145
+ row: int,
146
+ entry_type: bool=False
147
+ ):
148
+ if entry_type:
149
+ label = ctk.CTkEntry(card, placeholder_text=label_text)
150
+ else:
151
+ label = ctk.CTkLabel(card, text=label_text)
152
+ label.grid(row=row, column=0, padx=10, pady=(0, 10), sticky=ctk.W)
153
+
154
+ entry = ctk.CTkEntry(card, placeholder_text=placeholder)
155
+ entry.grid(row=row, column=1, padx=10, pady=(0, 10), sticky=ctk.EW)
156
+
157
+ if entry_type:
158
+ return label, entry
159
+ else:
160
+ return entry
161
+
162
+ def add_new_desktop_entry_field(self) -> ctk.CTkEntry:
163
+ next_row = self.desktop_entry_card.grid_size()[1]
164
+
165
+ entry = self.add_labeled_entry(self.desktop_entry_card, "Categories", "Application;Utility;", next_row, True)
166
+
167
+ return entry
168
+
169
+ def add_new_doc_file(self):
170
+ next_row = self.docs_card.grid_size()[1]
171
+
172
+ entry = self.add_labeled_entry(self.docs_card, "File:", "LICENSE", next_row)
173
+ self.doc_entries.append(entry)
174
+
175
+ def import_config(self):
176
+ file_path = filedialog.askopenfilename(
177
+ title="Import config",
178
+ filetypes=([("JSON", "*.json")]),
179
+ defaultextension=".json"
180
+ )
181
+ if not file_path:
182
+ return
183
+
184
+ with open(file_path, "r", encoding="utf-8") as f:
185
+ config = json.load(f)
186
+
187
+ self.destroy_user_widgets()
188
+
189
+ # Regular fields
190
+ for section, entries in self.entries.items():
191
+ if section in config:
192
+ for field_id, entry in entries.items():
193
+ if field_id in config[section]:
194
+ self.entry_set_text(entry, config[section][field_id])
195
+
196
+ # Desktop entry
197
+ for key, value in config["desktopEntry"].items():
198
+ if key in self.entries["desktopEntry"]:
199
+ self.entry_set_text(self.entries["desktopEntry"][key], value)
200
+ else:
201
+ label, entry = self.add_new_desktop_entry_field()
202
+ self.entry_set_text(label, key)
203
+ self.entry_set_text(entry, value)
204
+
205
+ # Documentation files
206
+ for value in config["documentationFiles"]:
207
+ self.add_new_doc_file()
208
+ self.entry_set_text(self.doc_entries[-1], value)
209
+
210
+ messagebox.showinfo("Config imported", "Config successfully imported")
211
+
212
+ def export_config(self, config_file: str=None):
213
+ config = {}
214
+
215
+ # Regular fields
216
+ for section, entries in self.entries.items():
217
+ config[section] = {}
218
+ for field_id, entry in entries.items():
219
+ value = entry.get()
220
+ if value:
221
+ config[section][field_id] = value
222
+
223
+ # Desktop entry
224
+ for child in self.desktop_entry_card.winfo_children():
225
+ info = child.grid_info()
226
+ row, col = info["row"], info["column"]
227
+
228
+ if col == 0 and isinstance(child, ctk.CTkEntry):
229
+ key = child.get()
230
+ if not key:
231
+ continue
232
+
233
+ for other in self.desktop_entry_card.winfo_children():
234
+ other_info = other.grid_info()
235
+ if other_info["row"] == row and other_info["column"] == 1:
236
+ config["desktopEntry"][key] = other.get()
237
+
238
+ config["documentationFiles"] = [entry.get() for entry in self.doc_entries if entry.get()]
239
+
240
+ if config_file:
241
+ with open(config_file, "w", encoding="utf-8") as f:
242
+ json.dump(config, f, ensure_ascii=False)
243
+ return
244
+
245
+ file_path = filedialog.asksaveasfilename(
246
+ title="Export config",
247
+ filetypes=([("JSON", "*.json")]),
248
+ defaultextension=".json",
249
+ initialfile="ctkdeb.json"
250
+ )
251
+ if file_path:
252
+ with open(file_path, "w", encoding="utf-8") as f:
253
+ json.dump(config, f, indent="\t", ensure_ascii=False)
254
+ messagebox.showinfo("Config exported", "Config successfully exported")
255
+
256
+ def entry_set_text(self, entry: ctk.CTkEntry, value: str):
257
+ entry.delete(0, ctk.END)
258
+ entry.insert(0, value)
259
+
260
+ def destroy_user_widgets(self):
261
+ # Desktop entry
262
+ for child in self.desktop_entry_card.winfo_children():
263
+ info = child.grid_info()
264
+ row, col = info["row"], info["column"]
265
+
266
+ if col == 0 and isinstance(child, ctk.CTkEntry):
267
+ for other in self.desktop_entry_card.winfo_children():
268
+ if other.grid_info()["row"] == row:
269
+ other.destroy()
270
+
271
+ # Documentation files
272
+ for entry in self.doc_entries:
273
+ entry.destroy()
274
+ self.doc_entries.clear()
275
+
276
+ def create_deb(self):
277
+ tmp_json = tempfile.NamedTemporaryFile(mode="w+", suffix=".json", delete=False)
278
+ self.export_config(config_file=tmp_json.name)
279
+
280
+ p = packager.Packager(tmp_json.name)
281
+ p.create_deb()
282
+
283
+ tmp_json.close()
284
+ os.remove(tmp_json.name)
285
+
286
+ messagebox.showinfo("DEB created", "DEB successfully created")
Binary file
@@ -0,0 +1,33 @@
1
+ def cli():
2
+ import argparse
3
+ from .packager import Packager
4
+
5
+ parser = argparse.ArgumentParser(
6
+ prog="CTkDeb",
7
+ description="A tool for easily packaging CustomTkinter projects into DEBs"
8
+ )
9
+ parser.add_argument("json", type=str, help="Path to the JSON config")
10
+ parser.add_argument("-pf", "--project-folder", type=str,
11
+ help="Path to the project folder (if the command is not executed from it)")
12
+ parser.add_argument("-d", "--desktop-entry", type=str,
13
+ help="Path to the custom desktop entry (or its text)")
14
+ parser.add_argument("-of", "--output-folder", type=str, help="Path to the output folder")
15
+
16
+ args = parser.parse_args()
17
+
18
+ packager = Packager(
19
+ args.json,
20
+ project_folder=args.project_folder,
21
+ desktop_entry=args.desktop_entry,
22
+ output_folder=args.output_folder
23
+ )
24
+ packager.create_deb()
25
+
26
+ def gui():
27
+ from .gui import CTkDeb
28
+
29
+ app = CTkDeb()
30
+ app.mainloop()
31
+
32
+ if __name__ == "__main__":
33
+ cli()
@@ -0,0 +1,194 @@
1
+ import json
2
+ from pathlib import Path
3
+ import shutil
4
+ import tempfile
5
+ import subprocess
6
+
7
+ REQUIRED_FIELDS = {
8
+ "app": ["name", "version", "entryPoint", "icon", "iconSize"],
9
+ "desktopEntry": ["Name"]
10
+ }
11
+
12
+ def _pip_install_to_dir(target_dir: Path, dependencies: str):
13
+ packages = dependencies.split()
14
+
15
+ cmd = ["pip", "install", "--target", str(target_dir)] + packages
16
+ subprocess.run(cmd)
17
+
18
+ # Clean garbage
19
+ for p in target_dir.rglob("*.py[co]"):
20
+ p.unlink()
21
+ for p in target_dir.rglob("__pycache__"):
22
+ shutil.rmtree(p)
23
+
24
+ class Packager:
25
+ def __init__(
26
+ self,
27
+ json_path: str,
28
+ project_folder: str=None,
29
+ desktop_entry: str=None,
30
+ output_folder: str=None
31
+ ):
32
+ with open(json_path, "r", encoding="utf-8") as f:
33
+ self.json = json.load(f)
34
+
35
+ self._check_required_fields()
36
+
37
+ if project_folder:
38
+ self.project_folder = Path(project_folder)
39
+ else:
40
+ self.project_folder = Path.cwd()
41
+
42
+ if desktop_entry:
43
+ try:
44
+ if Path(desktop_entry).exists():
45
+ with open(desktop_entry, "r", encoding="utf-8") as f:
46
+ self.desktop_entry = f.read()
47
+ else:
48
+ self.desktop_entry = desktop_entry
49
+ except OSError:
50
+ self.desktop_entry = desktop_entry
51
+ else:
52
+ self.desktop_entry = self._create_desktop_entry()
53
+
54
+ if output_folder:
55
+ self.output_folder = output_folder
56
+ else:
57
+ self.output_folder = Path.cwd()
58
+
59
+ def _check_required_fields(self):
60
+ for section, fields in REQUIRED_FIELDS.items():
61
+ if section not in self.json:
62
+ raise ValueError(f"Section '{section}' is missing")
63
+
64
+ for field in fields:
65
+ if not self.json[section].get(field):
66
+ raise ValueError(f"Field '{section}.{field}' is missing or empty")
67
+
68
+ def _create_desktop_entry(self) -> str:
69
+ basic_desktop_entry = f"""[Desktop Entry]
70
+ Type=Application
71
+ Version=1.0
72
+ Name={self.json['desktopEntry']['Name']}
73
+ Exec=python3 /opt/{self.project_folder.name}/{self.json['app']['entryPoint']} %f
74
+ Icon={self.json['app']['name']}
75
+ Terminal=false
76
+ """
77
+
78
+ # Converting basic desktop entry to dict
79
+ lines = basic_desktop_entry.splitlines()
80
+
81
+ entry_dict = {}
82
+ for line in lines:
83
+ if "=" in line:
84
+ key, value = line.split("=", 1)
85
+ entry_dict[key] = value
86
+ else:
87
+ entry_dict[line] = None # for title
88
+
89
+ # Update/add fields from json
90
+ for key, value in self.json["desktopEntry"].items():
91
+ entry_dict[key] = value
92
+
93
+ # Convert back
94
+ result_lines = []
95
+ for key, value in entry_dict.items():
96
+ if value is None:
97
+ result_lines.append(key) # for title
98
+ else:
99
+ result_lines.append(f"{key}={value}")
100
+
101
+ return "\n".join(result_lines)
102
+
103
+ def create_deb(self):
104
+ tmp_dir = tempfile.mkdtemp()
105
+
106
+ package_dir = Path(tmp_dir) / self.json["app"]["name"]
107
+ package_dir.mkdir()
108
+
109
+ share_dir = package_dir / "usr" / "share"
110
+ share_dir.mkdir(parents=True)
111
+
112
+ print("Copying app files...")
113
+
114
+ opt_dir = package_dir / "opt"
115
+ shutil.copytree(self.project_folder, opt_dir / self.project_folder.name)
116
+
117
+ print("Copying the icon...")
118
+
119
+ apps_icons_dir = share_dir / "icons" / "hicolor" / self.json["app"]["iconSize"] / "apps"
120
+ apps_icons_dir.mkdir(parents=True)
121
+
122
+ icon_path = Path(self.json["app"]["icon"])
123
+ new_icon_name = self.json["app"]["name"] + icon_path.suffix
124
+ shutil.copy(self.project_folder / icon_path, apps_icons_dir / new_icon_name)
125
+
126
+ print("Creating the desktop entry...")
127
+
128
+ applications_dir = share_dir / "applications"
129
+ applications_dir.mkdir()
130
+
131
+ with open(applications_dir / (self.json["app"]["name"] + ".desktop"), "w", encoding="utf-8") as f:
132
+ f.write(self.desktop_entry)
133
+
134
+ print("Copying docs...")
135
+
136
+ app_doc_dir = share_dir / "doc" / self.json["app"]["name"]
137
+ app_doc_dir.mkdir(parents=True)
138
+
139
+ for f in self.json["documentationFiles"]:
140
+ shutil.copy(self.project_folder / f, app_doc_dir)
141
+
142
+ print("Install CustomTkinter and pip dependencies...")
143
+
144
+ entry_point = Path(self.json["app"]["entryPoint"])
145
+ if len(entry_point.parts) == 1:
146
+ d = opt_dir / self.project_folder.name
147
+ else:
148
+ d = opt_dir / self.project_folder.name / entry_point.parent
149
+
150
+ dependencies = "customtkinter"
151
+ if self.json["app"].get("pipDependencies"):
152
+ dependencies += " " + self.json["app"]["pipDependencies"]
153
+
154
+ _pip_install_to_dir(d, dependencies)
155
+
156
+ print("Estimating package dir size...")
157
+
158
+ size_bytes = sum(f.stat().st_size for f in package_dir.rglob("*") if f.is_file())
159
+ size_kb = size_bytes // 1024
160
+
161
+ print("Creating control file...")
162
+
163
+ debian_dir = package_dir / "DEBIAN"
164
+ debian_dir.mkdir()
165
+
166
+ depends = "python3, python3-tk"
167
+ if self.json["deb"].get("depends"):
168
+ depends += ", " + self.json["deb"]["depends"]
169
+
170
+ deb_control = f"""Package: {self.json['app']['name']}
171
+ Version: {self.json['app']['version']}
172
+ Section: {self.json['deb'].get('section') or 'unknown'}
173
+ Priority: optional
174
+ Depends: {depends}
175
+ Architecture: all
176
+ Essential: no
177
+ Installed-Size: {size_kb}
178
+ Maintainer: {self.json['deb'].get('maintainer') or 'Unknown <unknown@example.com>'}
179
+ Description: {self.json['deb'].get('description') or 'Packaged via CTkDeb'}
180
+ """
181
+
182
+ with open(debian_dir / "control", "w", encoding="utf-8") as f:
183
+ f.write(deb_control)
184
+
185
+ print("Creating DEB package...")
186
+
187
+ subprocess.run(["dpkg-deb", "--build", package_dir])
188
+
189
+ print("Moving DEB and removing tmp dir...")
190
+
191
+ shutil.copy(Path(tmp_dir) / (self.json["app"]["name"] + ".deb"), self.output_folder)
192
+ shutil.rmtree(tmp_dir)
193
+
194
+ print("DEB successfully created!")
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "CTkDeb"
7
+ version = "0.3.1"
8
+ requires-python = ">= 3.9"
9
+ authors = [
10
+ { name = "limafresh", email = "173814432+limafresh@users.noreply.github.com" }
11
+ ]
12
+ maintainers = [
13
+ { name = "limafresh", email = "173814432+limafresh@users.noreply.github.com" }
14
+ ]
15
+ description = "A tool for easily packaging CustomTkinter projects into DEBs"
16
+ readme = "README.md"
17
+ license = "MIT"
18
+ license-files = ["LICENSE"]
19
+ keywords = ["gui", "customtkinter", "deb", "linux", "package"]
20
+ classifiers = [
21
+ "Development Status :: 4 - Beta",
22
+ "Intended Audience :: Developers",
23
+ "Natural Language :: English",
24
+ "Operating System :: POSIX :: Linux",
25
+ "Programming Language :: Python",
26
+ "Programming Language :: Python :: 3"
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ gui = ["customtkinter"]
31
+
32
+ [project.scripts]
33
+ ctkdeb = "ctkdeb.main:cli"
34
+
35
+ [project.gui-scripts]
36
+ ctkdeb-gui = "ctkdeb.main:gui"
37
+
38
+ [tool.setuptools.package-data]
39
+ ctkdeb = ["icon.png"]
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/limafresh/CTkDeb"
43
+ Documentation = "https://github.com/limafresh/CTkDeb"
44
+ Repository = "https://github.com/limafresh/CTkDeb.git"
45
+ Issues = "https://github.com/limafresh/CTkDeb/issues"
46
+ Changelog = "https://github.com/limafresh/CTkDeb/blob/main/CHANGELOG.md"
ctkdeb-0.3.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+