Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
07a0827
refactor(gui): create app class
dynobo Sep 28, 2025
76de098
refactor(gui): migrate singleton logic to app
dynobo Sep 28, 2025
a49851c
tests: refactor to work with new qapplication
dynobo Sep 29, 2025
e0b90ae
refactor(gui): migrate settings and cli handling
dynobo Sep 30, 2025
c0d5611
refactor(gui): migrate windows lifecycle
dynobo Sep 30, 2025
a2bb587
refactor(gui): migrate detection and result processing
dynobo Sep 30, 2025
05483a6
refactor(gui): migrate language management and update check
dynobo Sep 30, 2025
467c723
refactor(gui): migrate permissions, introduction and dbus
dynobo Sep 30, 2025
3292517
refactor(gui): adjust notification and menu logic
dynobo Oct 1, 2025
cf53336
refactor(gui): listen directly to setting changes
dynobo Oct 1, 2025
4f121b8
refactor(gui): simplify timer logic
dynobo Oct 1, 2025
e6beb0f
refactor(gui): installed languages handling
dynobo Oct 1, 2025
0c20fc1
refactor: move macos reset permission to permissions module
dynobo Oct 1, 2025
98d498c
feat(screenshot): do not try gnome-screenshot on Gnome 49+
dynobo Oct 1, 2025
49ce935
feat: additional app id for development purposes
dynobo Oct 2, 2025
d1f4ef5
feat: ensure window closes early
dynobo Oct 2, 2025
1b9e544
refactor: simplify update check logic
dynobo Oct 2, 2025
3ebc672
refactor(gui): carve out socket server to own module
dynobo Oct 2, 2025
d65e9fd
refactor(settings): unify date format in settings
dynobo Oct 2, 2025
ffaa460
fix: wrong action label and add test
dynobo Oct 2, 2025
d384e50
feat(clipboard): add qt based clipboard handler for wayland
dynobo Oct 3, 2025
dc816b8
chore(deps): bump pyside6
dynobo Oct 3, 2025
282ac1c
refactor: move app_id out of gui
dynobo Oct 4, 2025
199006c
fix(logger): use correct file names
dynobo Oct 4, 2025
af90a38
feat(dbus): capture exception during dbus service register
dynobo Oct 4, 2025
81989aa
fix(notification): correct module name in log output
dynobo Oct 4, 2025
11a3969
refactor(notification): use own system_info for notifications
dynobo Oct 4, 2025
516d0a1
cicd: remove deprecated macos 13 runner
dynobo Oct 4, 2025
e24a9c9
refactor: detection results and transformers
dynobo Oct 4, 2025
8d7bc0f
feat: improve url in code detection robustness
dynobo Oct 4, 2025
cd9e1c4
refactor: rename prebuild package and fix usage
dynobo Oct 4, 2025
c7d3d92
refactor: replace elif by match
dynobo Oct 4, 2025
5557f2c
refactor: carve out dbus permission dialog into gui
dynobo Oct 4, 2025
c38905e
refactor: dissolve utils into meaningful modules
dynobo Oct 4, 2025
ec0640b
chore(deps): uv sync upgrade
dynobo Oct 4, 2025
5aa62e4
feat(logging): improve format for better clickable pathnames
dynobo Oct 4, 2025
3ee97ee
chore(deps): update pre-commit hooks
dynobo Oct 4, 2025
69f4dd8
chore: improve logging output
dynobo Oct 5, 2025
e320796
refactor: consolidate system_info in own submodule
dynobo Oct 7, 2025
0e3c338
chore: bump pyside version; ignore unfixable pip-audit finding
dynobo Oct 13, 2025
5a2fb65
chore(deps): bump dev dependencies
dynobo Oct 30, 2025
97a8f77
tests: fix failing tests
dynobo Oct 30, 2025
0641dc0
cicd: bump manylinux version to support pyside 6.10.0
dynobo Oct 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/actions/setup-python-uv/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ runs:
steps:

- name: install uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v7
with:
version: "0.4.30"
version: "0.9.5"
enable-cache: true

- uses: actions/setup-python@v6
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/cicd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-13, macos-14, macos-15, windows-2022, windows-2025, ubuntu-22.04, ubuntu-24.04]
os: [macos-14, macos-15, windows-2022, windows-2025, ubuntu-22.04, ubuntu-24.04]
steps:
# Setup environment
- uses: actions/checkout@v5.0.0
Expand Down Expand Up @@ -118,7 +118,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-13, macos-15, windows-2025, ubuntu-24.04]
os: [macos-14, macos-15, windows-2025, ubuntu-24.04]
steps:
- uses: actions/checkout@v5.0.0

Expand Down Expand Up @@ -162,7 +162,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-13, macos-15, windows-2025, ubuntu-24.04]
os: [macos-14, macos-15, windows-2025, ubuntu-24.04]
steps:
- uses: actions/checkout@v5.0.0

Expand Down Expand Up @@ -222,14 +222,14 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v5.0.0
- name: Initialize CodeQL
uses: github/codeql-action/init@v3.30.5
uses: github/codeql-action/init@v4.31.0
with:
languages: ${{ matrix.language }}
queries: security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@v3.30.5
uses: github/codeql-action/autobuild@v4.31.0
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3.30.5
uses: github/codeql-action/analyze@v4.31.0

deploy-briefcase:
name: Briefcase build & draft release
Expand All @@ -245,7 +245,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-13, macos-15, windows-2025, ubuntu-24.04]
os: [macos-14, macos-15, windows-2025, ubuntu-24.04]
steps:
- uses: actions/checkout@v5.0.0

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Edit at https://www.gitignore.io/?templates=python

# Own
*.xdot
.virtualenvs/
.vscode/
.ruff_cache/
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ fail_fast: true

repos:
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v2.4.0
rev: v4.3.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: check-ast
#- id: check-yaml
Expand Down
6 changes: 4 additions & 2 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@

# Changelog

## v0.6.1 (upcoming)
## v0.7.0 (upcoming)

**All systems:**
- 🗲 Breaking: Drop support for Python 3.9.
- Fix unresposive window when selecting larger screen regions.

**Windows**:
- Fix crash on `NormCap.exe --help`. ([#783](https://github.com/dynobo/normcap/issues/783))

**Linux**:
- Add notification actions for FlatPak package.
- Add clickable desktop notifications in FlatPak package.
- Add a new clipboard-handler `qt_wayland` to improve reliabilty of writing to clipboard on Wayland systems.

## v0.6.0 (2025-08-31)

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ sudo zypper install python3-devel tesseract-ocr tesseract-ocr-devel wl-clipboard
# Install normcap
pip install normcap

#TODO: [HIGH] Describe service and desktop file setup

# Run
./normcap
```
Expand Down
19 changes: 10 additions & 9 deletions bundle/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@
)
args = parser.parse_args()

if sys.platform == "win32":
WindowsBriefcase().create()
WindowsBriefcaseZip().create()
match sys.platform:
case "win32":
WindowsBriefcase().create()
WindowsBriefcaseZip().create()

elif sys.platform == "darwin":
MacBriefcase().create()
case "darwin":
MacBriefcase().create()

elif sys.platform == "linux":
LinuxBriefcase().create()
case "linux":
LinuxBriefcase().create()

else:
raise RuntimeError(f"Unknown platform '{sys.platform}'.")
case _:
raise RuntimeError(f"Unknown platform '{sys.platform}'.")
9 changes: 9 additions & 0 deletions normcap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@

from __future__ import annotations

import os
from importlib.metadata import PackageNotFoundError, version

try:
__version__ = version("normcap")
except PackageNotFoundError:
__version__ = "unknown"

app_id = "com.github.dynobo.normcap"
if os.environ.get("NORMCAP_DEV"):
# This alternative app id is meant for normcap developers only. It allows to
# setup two different NormCap versions (e.g. a stable one mand one for development)
# with different .desktop laucherns, which both can ask for permission to talk to
# DBus Portal.
app_id += "_dev"
109 changes: 19 additions & 90 deletions normcap/app.py
Original file line number Diff line number Diff line change
@@ -1,121 +1,50 @@
"""Start main application logic."""

import logging
import os
import signal
import sys
from argparse import Namespace
from pathlib import Path
from typing import NoReturn

from PySide6 import QtCore, QtWidgets
from PySide6 import QtWidgets

from normcap import __version__, utils
from normcap.gui import system_info
from normcap.gui.tray import SystemTray
from normcap import argparser, environment
from normcap import logger_config as logger_
from normcap.gui.application import NormcapApp
from normcap.platform import system_info

logger = logging.getLogger(__name__)

def _get_args() -> Namespace:
"""Parse command line arguments.

Exit if NormCap was started with --version flag.
def _init_normcap() -> QtWidgets.QApplication:
"""Prepares the application.

Auto-enable tray for "background mode", which starts NormCap in tray without
immediately opening the select-region window.
"""
args = utils.create_argparser().parse_args()

if args.version:
print(f"NormCap {__version__}") # noqa: T201
sys.exit(0)

if args.background_mode:
args.tray = True

return args


def _prepare_logging(log_level: str, log_file: Path | None = None) -> None:
"""Initialize the logger with the given log level.

This function wraps the QT logger to control the output in the Python logger.
For all log levels except DEBUG, an exception hook is used to improve the stack
trace output for bug reporting on Github.

Args:
log_level: Valid Python log level (debug, warning, error)
log_file: Target for saving log to file
"""
sys.excepthook = utils.hook_exceptions

utils.init_logger(log_level=log_level.upper(), log_file=log_file)
logger = logging.getLogger("normcap")
logger.info("Start NormCap v%s", __version__)

# Wrap QT logging output
QtCore.qInstallMessageHandler(utils.qt_log_wrapper)


def _prepare_envs() -> None:
"""Prepare environment variables depending on setup and system.

Enable exiting via CTRL+C in Terminal.
"""
# Allow closing QT app with CTRL+C in terminal
signal.signal(signal.SIGINT, signal.SIG_DFL)

# Silence opencv warnings
os.environ["OPENCV_LOG_LEVEL"] = "OFF"

if system_info.display_manager_is_wayland():
utils.set_environ_for_wayland()
if system_info.is_flatpak():
utils.set_environ_for_flatpak()
if system_info.is_appimage_package():
utils.set_environ_for_appimage()


def _get_application() -> QtWidgets.QApplication:
"""Get a QApplication instance that doesn't exit on window close."""
app = QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
app.setQuitOnLastWindowClosed(False)
return app


def _prepare() -> tuple[QtWidgets.QApplication, SystemTray]:
"""Prepares the application and system tray without executing.
This does not call app.exec() to simplify testing.

Returns:
A tuple containing the QApplication ready for execution
and the not yet visible SystemTray.
NormcapApp instance.
"""
args = _get_args()
args = argparser.get_args()

_prepare_logging(
logger_.prepare_logging(
log_level=str(getattr(args, "verbosity", "ERROR")),
log_file=getattr(args, "log_file", Path.cwd() / "normcap.log"),
)
_prepare_envs()
environment.prepare()

if system_info.is_prebuilt_package():
if system_info.is_packaged():
tessdata_path = system_info.get_tessdata_path(
config_directory=system_info.config_directory(),
is_briefcase_package=system_info.is_briefcase_package(),
is_flatpak_package=system_info.is_flatpak(),
is_packaged=system_info.is_packaged(),
)
utils.copy_traineddata_files(target_dir=tessdata_path)

app = _get_application()
tray = SystemTray(app, vars(args))
environment.copy_traineddata_files(target_dir=tessdata_path)

return app, tray
logger.debug("System info:\n%s", system_info.to_dict())
return NormcapApp(args=vars(args))


def run() -> NoReturn:
"""Run the main application."""
app, tray = _prepare()
tray.show()
sys.exit(app.exec())
sys.exit(_init_normcap().exec())


if __name__ == "__main__":
Expand Down
Loading