Skip to content

Commit 08e89a8

Browse files
authored
Merge pull request #15 from d7omdev/feat/pyproject-installer
2 parents 862fb64 + 38d3542 commit 08e89a8

30 files changed

Lines changed: 4234 additions & 3717 deletions

clipse-gui.py

Lines changed: 10 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,18 @@
11
#!/usr/bin/env python3
2-
import argparse
3-
import logging
4-
import os
5-
import sys
6-
import traceback
7-
from logging.handlers import RotatingFileHandler
8-
from pathlib import Path
9-
import gi
10-
gi.require_version("Gtk", "3.0")
11-
gi.require_version("Gdk", "3.0")
12-
gi.require_version("Pango", "1.0")
13-
gi.require_version("GdkPixbuf", "2.0")
14-
gi.require_version("GLib", "2.0")
15-
16-
from clipse_gui import __version__, constants # noqa: E402
17-
18-
PACKAGE_PARENT = ".."
19-
SCRIPT_DIR = os.path.dirname(
20-
os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__)))
21-
)
22-
sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT)))
23-
24-
log = None
25-
26-
27-
class ColorFormatter(logging.Formatter):
28-
COLORS = {
29-
"DEBUG": "\033[1;36m",
30-
"INFO": "\033[1;32m",
31-
"WARNING": "\033[1;33m",
32-
"ERROR": "\033[1;31m",
33-
"CRITICAL": "\033[1;41m",
34-
"RESET": "\033[0m",
35-
}
36-
37-
def format(self, record):
38-
level_color = self.COLORS.get(record.levelname, "")
39-
reset = self.COLORS["RESET"]
40-
record.levelname = f"{level_color}{record.levelname}{reset}"
41-
record.name = f"\033[1;34m{record.name}\033[0m"
42-
record.asctime = f"\033[1;37m{self.formatTime(record, self.datefmt)}\033[0m"
43-
return super().format(record)
44-
45-
46-
def parse_args_from_sys_argv():
47-
parser = argparse.ArgumentParser(
48-
description="Start the Clipse GUI application.",
49-
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
50-
)
51-
parser.add_argument(
52-
"-d", "--debug", action="store_true", help="Enable debug logging"
53-
)
54-
parser.add_argument(
55-
"-v", "--version", action="version", version=f"Clipse GUI v{__version__}"
56-
)
57-
return parser.parse_known_args()
58-
2+
"""Dev entry shim for in-tree runs.
593
60-
def setup_logging(debug=False):
61-
global log
62-
try:
63-
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
64-
date_format = "%Y-%m-%d %H:%M:%S"
4+
The canonical entry point is `clipse_gui.cli:main`, exposed as `clipse-gui`
5+
via `[project.scripts]` once installed from the wheel. This file just lets
6+
`python clipse-gui.py` work from a fresh checkout.
7+
"""
658

66-
stream_handler = logging.StreamHandler(sys.stdout)
67-
stream_handler.setLevel(logging.DEBUG if debug else logging.INFO)
68-
stream_handler.setFormatter(ColorFormatter(log_format, datefmt=date_format))
69-
70-
handlers = [stream_handler]
71-
72-
# File logging only in debug mode — RotatingFileHandler adds ~100ms I/O on every start
73-
if debug:
74-
log_file_path = os.path.join(constants.CONFIG_DIR, "clipse-gui.log")
75-
file_handler = RotatingFileHandler(
76-
filename=log_file_path,
77-
maxBytes=5 * 1024 * 1024,
78-
backupCount=3,
79-
)
80-
file_handler.setLevel(logging.DEBUG)
81-
file_handler.setFormatter(logging.Formatter(log_format, datefmt=date_format))
82-
handlers.append(file_handler)
83-
84-
logging.basicConfig(level=logging.DEBUG, handlers=handlers)
85-
log = logging.getLogger(__name__)
86-
log.info(f"Logging initialized ({'DEBUG + file' if debug else 'INFO'})")
87-
except Exception as e:
88-
print(f"CRITICAL: Failed to set up logging: {e}", file=sys.stderr)
89-
traceback.print_exc()
90-
sys.exit(1)
91-
92-
93-
def main():
94-
args, gtk_args = parse_args_from_sys_argv()
95-
setup_logging(debug=args.debug)
96-
97-
if log is None:
98-
print("CRITICAL: Logging setup failed. Exiting.", file=sys.stderr)
99-
sys.exit(1)
100-
101-
try:
102-
Path(constants.CONFIG_DIR).mkdir(parents=True, exist_ok=True)
103-
except Exception as e:
104-
log.critical(f"Failed to create config directory: {e}", exc_info=True)
105-
sys.exit(1)
106-
107-
try:
108-
from clipse_gui.app import ClipseGuiApplication
9+
import os
10+
import sys
10911

110-
app = ClipseGuiApplication()
111-
exit_status = app.run(gtk_args)
112-
log.info(f"Application exited with status {exit_status}.")
113-
sys.exit(exit_status)
114-
except Exception as e:
115-
log.critical(f"Unhandled exception in main: {e}", exc_info=True)
116-
sys.exit(1)
12+
# Make the package importable when running from source without installing.
13+
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
11714

15+
from clipse_gui.cli import main # noqa: E402
11816

11917
if __name__ == "__main__":
12018
main()

clipse_gui/app.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import signal
23
from . import constants
34
from .constants import (
45
APP_NAME,
@@ -30,14 +31,41 @@ def do_startup(self):
3031
"""Called once when the application starts."""
3132
Gtk.Application.do_startup(self)
3233
log.debug(f"Application {APPLICATION_ID} starting up.")
34+
self._install_signal_handlers()
35+
36+
def _install_signal_handlers(self):
37+
"""Wire SIGINT (Ctrl+C) and SIGTERM into the GLib main loop for graceful exit."""
38+
def _on_signal(sig_name):
39+
log.info(f"{sig_name} received — quitting application gracefully.")
40+
self.quit()
41+
return GLib.SOURCE_REMOVE
42+
43+
GLib.unix_signal_add(
44+
GLib.PRIORITY_DEFAULT, signal.SIGINT, _on_signal, "SIGINT"
45+
)
46+
GLib.unix_signal_add(
47+
GLib.PRIORITY_DEFAULT, signal.SIGTERM, _on_signal, "SIGTERM"
48+
)
3349

3450
def do_activate(self):
3551
"""Called when the application is launched. Creates the main window and controller."""
3652
if not self.window:
3753
log.debug("Activating application - creating main window.")
3854

3955
self.window = Gtk.ApplicationWindow(application=self, title=APP_NAME)
40-
self.window.set_default_size(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT)
56+
57+
# Pick initial window size based on compact mode setting from config.
58+
# This must happen BEFORE first show so the WM honors it.
59+
compact_mode_on = constants.config.getboolean(
60+
"General", "compact_mode", fallback=False
61+
)
62+
if compact_mode_on:
63+
init_w = int(DEFAULT_WINDOW_WIDTH * 0.6)
64+
init_h = int(DEFAULT_WINDOW_HEIGHT * 0.6)
65+
else:
66+
init_w = DEFAULT_WINDOW_WIDTH
67+
init_h = DEFAULT_WINDOW_HEIGHT
68+
self.window.set_default_size(init_w, init_h)
4169
try:
4270
self.window.set_icon_name("edit-copy")
4371
except GLib.Error as e:

clipse_gui/cli.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""Console entry point for clipse-gui.
2+
3+
Referenced by `[project.scripts]` in pyproject.toml as `clipse_gui.cli:main`.
4+
The root `clipse-gui.py` is a thin shim that calls into here for in-tree development.
5+
"""
6+
7+
import argparse
8+
import logging
9+
import os
10+
import sys
11+
import traceback
12+
from logging.handlers import RotatingFileHandler
13+
from pathlib import Path
14+
15+
import gi
16+
17+
gi.require_version("Gtk", "3.0")
18+
gi.require_version("Gdk", "3.0")
19+
gi.require_version("Pango", "1.0")
20+
gi.require_version("GdkPixbuf", "2.0")
21+
gi.require_version("GLib", "2.0")
22+
23+
from . import __version__, constants # noqa: E402
24+
25+
log: logging.Logger | None = None
26+
27+
28+
class ColorFormatter(logging.Formatter):
29+
COLORS = {
30+
"DEBUG": "\033[1;36m",
31+
"INFO": "\033[1;32m",
32+
"WARNING": "\033[1;33m",
33+
"ERROR": "\033[1;31m",
34+
"CRITICAL": "\033[1;41m",
35+
"RESET": "\033[0m",
36+
}
37+
38+
def format(self, record):
39+
level_color = self.COLORS.get(record.levelname, "")
40+
reset = self.COLORS["RESET"]
41+
record.levelname = f"{level_color}{record.levelname}{reset}"
42+
record.name = f"\033[1;34m{record.name}\033[0m"
43+
record.asctime = f"\033[1;37m{self.formatTime(record, self.datefmt)}\033[0m"
44+
return super().format(record)
45+
46+
47+
def parse_args_from_sys_argv():
48+
parser = argparse.ArgumentParser(
49+
description="Start the Clipse GUI application.",
50+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
51+
)
52+
parser.add_argument(
53+
"-d", "--debug", action="store_true", help="Enable debug logging"
54+
)
55+
parser.add_argument(
56+
"-v", "--version", action="version", version=f"Clipse GUI v{__version__}"
57+
)
58+
return parser.parse_known_args()
59+
60+
61+
def setup_logging(debug=False):
62+
global log
63+
try:
64+
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
65+
date_format = "%Y-%m-%d %H:%M:%S"
66+
67+
stream_handler = logging.StreamHandler(sys.stdout)
68+
stream_handler.setLevel(logging.DEBUG if debug else logging.INFO)
69+
stream_handler.setFormatter(ColorFormatter(log_format, datefmt=date_format))
70+
71+
handlers = [stream_handler]
72+
73+
# File logging only in debug mode — RotatingFileHandler adds ~100ms I/O on every start
74+
if debug:
75+
log_file_path = os.path.join(constants.CONFIG_DIR, "clipse-gui.log")
76+
file_handler = RotatingFileHandler(
77+
filename=log_file_path,
78+
maxBytes=5 * 1024 * 1024,
79+
backupCount=3,
80+
)
81+
file_handler.setLevel(logging.DEBUG)
82+
file_handler.setFormatter(logging.Formatter(log_format, datefmt=date_format))
83+
handlers.append(file_handler)
84+
85+
logging.basicConfig(level=logging.DEBUG, handlers=handlers)
86+
log = logging.getLogger(__name__)
87+
log.info(f"Logging initialized ({'DEBUG + file' if debug else 'INFO'})")
88+
except Exception as e:
89+
print(f"CRITICAL: Failed to set up logging: {e}", file=sys.stderr)
90+
traceback.print_exc()
91+
sys.exit(1)
92+
93+
94+
def main():
95+
args, gtk_args = parse_args_from_sys_argv()
96+
setup_logging(debug=args.debug)
97+
98+
if log is None:
99+
print("CRITICAL: Logging setup failed. Exiting.", file=sys.stderr)
100+
sys.exit(1)
101+
102+
try:
103+
Path(constants.CONFIG_DIR).mkdir(parents=True, exist_ok=True)
104+
except Exception as e:
105+
log.critical(f"Failed to create config directory: {e}", exc_info=True)
106+
sys.exit(1)
107+
108+
try:
109+
from clipse_gui.app import ClipseGuiApplication
110+
111+
app = ClipseGuiApplication()
112+
exit_status = app.run(gtk_args)
113+
log.info(f"Application exited with status {exit_status}.")
114+
sys.exit(exit_status)
115+
except Exception as e:
116+
log.critical(f"Unhandled exception in main: {e}", exc_info=True)
117+
sys.exit(1)
118+
119+
120+
if __name__ == "__main__":
121+
main()

0 commit comments

Comments
 (0)