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.
- ctkdeb-0.3.1/CTkDeb.egg-info/PKG-INFO +96 -0
- ctkdeb-0.3.1/CTkDeb.egg-info/SOURCES.txt +14 -0
- ctkdeb-0.3.1/CTkDeb.egg-info/dependency_links.txt +1 -0
- ctkdeb-0.3.1/CTkDeb.egg-info/entry_points.txt +5 -0
- ctkdeb-0.3.1/CTkDeb.egg-info/requires.txt +3 -0
- ctkdeb-0.3.1/CTkDeb.egg-info/top_level.txt +1 -0
- ctkdeb-0.3.1/LICENSE +21 -0
- ctkdeb-0.3.1/PKG-INFO +96 -0
- ctkdeb-0.3.1/README.md +70 -0
- ctkdeb-0.3.1/ctkdeb/__init__.py +1 -0
- ctkdeb-0.3.1/ctkdeb/gui.py +286 -0
- ctkdeb-0.3.1/ctkdeb/icon.png +0 -0
- ctkdeb-0.3.1/ctkdeb/main.py +33 -0
- ctkdeb-0.3.1/ctkdeb/packager.py +194 -0
- ctkdeb-0.3.1/pyproject.toml +46 -0
- ctkdeb-0.3.1/setup.cfg +4 -0
|
@@ -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
|
+

|
|
30
|
+

|
|
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
|
+

|
|
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 @@
|
|
|
1
|
+
|
|
@@ -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
|
+

|
|
30
|
+

|
|
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
|
+

|
|
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
|
+

|
|
4
|
+

|
|
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
|
+

|
|
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