feat: application icon for Windows taskbar + exe
- scripts/gen_icon.py: generates 256x256 SSH client icon (dark terminal window with >_ prompt and SSH connection nodes), outputs multi-size ICO (16-256px) + PNG - resources/icon.ico + icon.png: generated icon files - main.py: _get_icon_path() searches _MEIPASS (onefile), exe dir, source dir; sets app + window icon - build.spec: bundles icon files via datas=, sets exe icon=icon.ico - Windows taskbar will now show the custom icon instead of default PyInstaller icon
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
"""
|
||||
生成 SSHClient 应用图标
|
||||
- 深色圆角方块背景
|
||||
- 终端窗口 + >_ 提示符 + SSH 连接线
|
||||
输出: resources/icon.ico (多尺寸) + resources/icon.png (256x256)
|
||||
"""
|
||||
import os
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
OUT_DIR = os.path.join(os.path.dirname(__file__), "resources")
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
|
||||
SIZE = 256
|
||||
|
||||
def draw_icon(size: int) -> Image.Image:
|
||||
"""绘制指定尺寸的图标"""
|
||||
img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(img)
|
||||
|
||||
pad = int(size * 0.06)
|
||||
rect = [pad, pad, size - pad, size - pad]
|
||||
radius = int(size * 0.18)
|
||||
|
||||
# 背景渐变(模拟,用两层半透明)
|
||||
d.rounded_rectangle(rect, radius=radius, fill=(30, 30, 46, 255))
|
||||
|
||||
# 上半部分亮色渐变
|
||||
inner = [pad + 4, pad + 4, size - pad - 4, size // 2]
|
||||
d.rounded_rectangle(inner, radius=radius - 2, fill=(40, 40, 60, 255))
|
||||
|
||||
# 终端窗口
|
||||
tw_margin = int(size * 0.16)
|
||||
tw_top = int(size * 0.20)
|
||||
tw_bottom = int(size * 0.78)
|
||||
tw_left = tw_margin
|
||||
tw_right = size - tw_margin
|
||||
tw_radius = int(size * 0.04)
|
||||
|
||||
# 终端窗口阴影
|
||||
shadow_offset = max(2, size // 64)
|
||||
d.rounded_rectangle(
|
||||
[tw_left + shadow_offset, tw_top + shadow_offset,
|
||||
tw_right + shadow_offset, tw_bottom + shadow_offset],
|
||||
radius=tw_radius, fill=(0, 0, 0, 60)
|
||||
)
|
||||
|
||||
# 终端窗口背景
|
||||
d.rounded_rectangle(
|
||||
[tw_left, tw_top, tw_right, tw_bottom],
|
||||
radius=tw_radius, fill=(12, 12, 20, 255)
|
||||
)
|
||||
|
||||
# 终端标题栏
|
||||
tb_height = int(size * 0.055)
|
||||
d.rounded_rectangle(
|
||||
[tw_left, tw_top, tw_right, tw_top + tb_height],
|
||||
radius=tw_radius, fill=(50, 50, 70, 255)
|
||||
)
|
||||
# 底部直角覆盖(标题栏只有上面圆角)
|
||||
d.rectangle(
|
||||
[tw_left, tw_top + tb_height - tw_radius, tw_right, tw_top + tb_height],
|
||||
fill=(50, 50, 70, 255)
|
||||
)
|
||||
|
||||
# 三个小圆点(macOS 风格窗口按钮)
|
||||
dot_r = max(2, int(size * 0.016))
|
||||
dot_y = tw_top + tb_height // 2
|
||||
dot_start = tw_left + int(size * 0.04)
|
||||
dot_gap = int(size * 0.045)
|
||||
for i, color in enumerate([(239, 83, 80), (255, 167, 38), (102, 187, 106)]):
|
||||
cx = dot_start + i * dot_gap
|
||||
d.ellipse([cx - dot_r, dot_y - dot_r, cx + dot_r, dot_y + dot_r], fill=color)
|
||||
|
||||
# >_ 提示符
|
||||
cx = tw_left + int(size * 0.06)
|
||||
cy = tw_top + tb_height + int(size * 0.10)
|
||||
line_w = max(2, int(size * 0.022))
|
||||
arrow_len = int(size * 0.08)
|
||||
gap = int(size * 0.02)
|
||||
|
||||
# > 符号
|
||||
d.line([cx, cy, cx + arrow_len, cy + arrow_len // 2],
|
||||
fill=(102, 187, 106), width=line_w)
|
||||
d.line([cx, cy + arrow_len, cx + arrow_len, cy + arrow_len // 2],
|
||||
fill=(102, 187, 106), width=line_w)
|
||||
|
||||
# _ 光标
|
||||
cursor_x = cx + arrow_len + gap
|
||||
cursor_w = int(size * 0.06)
|
||||
cursor_h = max(2, int(size * 0.018))
|
||||
d.rounded_rectangle(
|
||||
[cursor_x, cy + arrow_len // 2 - cursor_h // 2,
|
||||
cursor_x + cursor_w, cy + arrow_len // 2 + cursor_h // 2],
|
||||
radius=max(1, cursor_h // 3), fill=(137, 180, 250)
|
||||
)
|
||||
|
||||
# SSH 连接线(底部装饰)
|
||||
line_y = tw_bottom + int(size * 0.05)
|
||||
if line_y < size - pad:
|
||||
# 连接节点
|
||||
node_r = max(2, int(size * 0.012))
|
||||
n1 = (tw_left + int(size * 0.04), line_y)
|
||||
n2 = (tw_right - int(size * 0.04), line_y)
|
||||
d.ellipse([n1[0]-node_r, n1[1]-node_r, n1[0]+node_r, n1[1]+node_r],
|
||||
fill=(137, 180, 250))
|
||||
d.ellipse([n2[0]-node_r, n2[1]-node_r, n2[0]+node_r, n2[1]+node_r],
|
||||
fill=(137, 180, 250))
|
||||
d.line([n1[0]+node_r, n1[1], n2[0]-node_r, n2[1]],
|
||||
fill=(137, 180, 250, 160), width=max(1, line_w // 2))
|
||||
|
||||
return img
|
||||
|
||||
|
||||
def main():
|
||||
# 生成 256x256 主图
|
||||
icon = draw_icon(SIZE)
|
||||
png_path = os.path.join(OUT_DIR, "icon.png")
|
||||
icon.save(png_path)
|
||||
print(f"Saved {png_path} ({SIZE}x{SIZE})")
|
||||
|
||||
# 生成多尺寸 ICO(Pillow 自动从 256x256 缩放到各尺寸)
|
||||
sizes = [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)]
|
||||
ico_path = os.path.join(OUT_DIR, "icon.ico")
|
||||
icon.save(ico_path, format="ICO", sizes=sizes)
|
||||
print(f"Saved {ico_path} (sizes: {[s[0] for s in sizes]})")
|
||||
print("Done!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user