77
88import os
99from pathlib import Path
10- from PySide6 .QtCore import Qt , QPoint , Signal , QTimer , QSize
10+ from PySide6 .QtCore import Qt , Signal , QTimer , QSize
1111from PySide6 .QtGui import QColor , QIcon
12- from PySide6 .QtGui import QPixmap , QPainter
12+ from PySide6 .QtGui import QPixmap , QPainter , QFont
1313from PySide6 .QtWidgets import (
1414 QWidget ,
1515 QVBoxLayout ,
2222 QApplication ,
2323 QGraphicsDropShadowEffect ,
2424 QFileDialog ,
25+ QSystemTrayIcon ,
26+ QMenu ,
2527)
26-
2728from kratt .config import (
2829 load_settings ,
2930 save_settings ,
@@ -38,7 +39,8 @@ class MainWindow(QWidget):
3839 Main draggable, frameless chat window.
3940
4041 Manages user input, chat history, worker thread execution,
41- and UI state during message generation.
42+ and UI state during message generation. Supports system tray
43+ integration for minimized state.
4244 """
4345
4446 toggle_signal = Signal ()
@@ -59,6 +61,7 @@ def __init__(self) -> None:
5961 self .current_model_used = ""
6062
6163 self ._setup_ui ()
64+ self ._setup_tray ()
6265 self ._setup_hotkey ()
6366 self .new_chat ()
6467
@@ -96,6 +99,63 @@ def _setup_ui(self) -> None:
9699 self ._setup_input_area ()
97100 self ._center_window ()
98101
102+ def _setup_tray (self ) -> None :
103+ """Set up system tray icon with context menu."""
104+ self .tray_icon = QSystemTrayIcon (self )
105+
106+ # Create tray menu
107+ tray_menu = QMenu ()
108+
109+ action_show = tray_menu .addAction ("Show" )
110+ action_show .triggered .connect (self .show_window )
111+
112+ action_hide = tray_menu .addAction ("Hide" )
113+ action_hide .triggered .connect (self .hide )
114+
115+ tray_menu .addSeparator ()
116+
117+ action_quit = tray_menu .addAction ("Quit" )
118+ action_quit .triggered .connect (QApplication .quit )
119+
120+ self .tray_icon .setContextMenu (tray_menu )
121+
122+ self ._create_tray_icon_pixmap ()
123+
124+ self .tray_icon .activated .connect (self ._on_tray_icon_activated )
125+
126+ self .tray_icon .show ()
127+
128+ def _create_tray_icon_pixmap (self ) -> None :
129+ """Create a simple tray icon pixmap with 'K' letter."""
130+ pixmap = QPixmap (64 , 64 )
131+ pixmap .fill (QColor (230 , 126 , 34 , 255 ))
132+
133+ painter = QPainter (pixmap )
134+ painter .setRenderHint (QPainter .RenderHint .Antialiasing )
135+
136+ font = QFont ("Sans Serif" , 32 , QFont .Weight .Bold )
137+ painter .setFont (font )
138+ painter .setPen (QColor (255 , 255 , 255 ))
139+ painter .drawText (pixmap .rect (), Qt .AlignmentFlag .AlignCenter , "K" )
140+
141+ painter .end ()
142+
143+ self .tray_icon .setIcon (QIcon (pixmap ))
144+
145+ def _on_tray_icon_activated (self , reason ) -> None :
146+ """Handle tray icon activation (click)."""
147+ if reason in (
148+ QSystemTrayIcon .ActivationReason .Trigger ,
149+ QSystemTrayIcon .ActivationReason .DoubleClick ,
150+ ):
151+ self .show_window ()
152+
153+ def show_window (self ) -> None :
154+ """Show the window and bring it to focus."""
155+ self .show ()
156+ self .activateWindow ()
157+ self .txt_input .setFocus ()
158+
99159 def _setup_header (self ) -> None :
100160 """Set up the top bar with title, new chat, settings, and close buttons."""
101161 self .header = QFrame ()
@@ -125,7 +185,7 @@ def _setup_header(self) -> None:
125185 btn_close .setObjectName ("CloseBtn" )
126186 btn_close .setFixedSize (28 , 28 )
127187 btn_close .setCursor (Qt .CursorShape .PointingHandCursor )
128- btn_close .clicked .connect (self .close )
188+ btn_close .clicked .connect (self .toggle_visibility )
129189
130190 self .header_layout .addWidget (title )
131191 self .header_layout .addStretch ()
@@ -146,7 +206,6 @@ def _setup_chat_area(self) -> None:
146206
147207 self .chat_widget = QWidget ()
148208 self .chat_widget .setObjectName ("ChatWidget" )
149- # Important: Allow the chat_widget to be styled via CSS background-color rules
150209 self .chat_widget .setAttribute (Qt .WidgetAttribute .WA_StyledBackground )
151210
152211 self .chat_layout = QVBoxLayout ()
@@ -500,6 +559,4 @@ def toggle_visibility(self) -> None:
500559 if self .isVisible ():
501560 self .hide ()
502561 else :
503- self .show ()
504- self .activateWindow ()
505- self .txt_input .setFocus ()
562+ self .show_window ()
0 commit comments