Skip to content

[pull] master from slgobinath:master #106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
Describe your changes here, and link to related issues if available.

Reminder to run `python validate_po.py --extract` if you have added new translatable strings.

Reminder to run `ruff check`, `ruff format`, and `mypy safeeyes` to ensure coding standards are followed and types are correct.
36 changes: 36 additions & 0 deletions .github/workflows/mypy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Workflow to run mypy
# Adapted from the CPython mypy action
name: mypy

on: [push, pull_request]

permissions:
contents: read

env:
UV_SYSTEM_PYTHON: 1
PIP_DISABLE_PIP_VERSION_CHECK: 1
FORCE_COLOR: 1
TERM: xterm-256color # needed for FORCE_COLOR to work on mypy on Ubuntu, see https://github.com/python/mypy/issues/13817

jobs:
mypy:
name: mypy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@22695119d769bdb6f7032ad67b9bca0ef8c4a174 # v5.4.0
with:
enable-cache: true
cache-dependency-glob: ''
cache-suffix: '3.13'
- name: Setup Python
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:
python-version: '3.13'
- run: sudo apt-get install -y libwayland-dev libcairo2-dev libgirepository-2.0-dev
- run: uv pip install -r pyproject.toml
- run: uv pip install --group types
- run: mypy safeeyes
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ To ensure the new strings are well-formed, you can use `python validate_po.py --

To ensure that the coding and formatting guidelines are followed, install [ruff](https://docs.astral.sh/ruff/) and run `ruff check` and `ruff format --check` to check for issues, as well as `ruff check --fix` and `ruff format` to autofix them.

To ensure that any types are correct, install [mypy](https://github.com/python/mypy) and run `mypy safeeyes`.

The last three checks are also run in CI, so a PR must pass all the tests for it to be mmerged.

## How to Release?

0. Run `update-po.sh` to generate new translation files (which will be eventually updated by translators). Commit and push the changes to the master branch.
Expand Down
32 changes: 32 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,35 @@ build-backend = "setuptools.build_meta"

[tool.setuptools.packages.find]
include=["safeeyes*"]

[dependency-groups]
dev = [
{include-group = "lint"},
{include-group = "scripts"},
{include-group = "types"}
]
lint = [
"ruff==0.11.2"
]
scripts = [
"polib==1.2.0"
]
types = [
"mypy==1.15.0",
"PyGObject-stubs==2.13.0",
"types-croniter==5.0.1.20250322",
"types-psutil==7.0.0.20250218",
"types-python-xlib==0.33.0.20240407"
]

[tool.mypy]
# catch typos in configuration file
warn_unused_configs = true
disallow_any_unimported = true
#check_untyped_defs = true
warn_unused_ignores = true
warn_unreachable = true
enable_error_code = [
"ignore-without-code",
"possibly-undefined"
]
4 changes: 2 additions & 2 deletions safeeyes/plugins/donotdisturb/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
from safeeyes import utility

context = None
skip_break_window_classes = []
take_break_window_classes = []
skip_break_window_classes: list[str] = []
take_break_window_classes: list[str] = []
unfullscreen_allowed = True
dnd_while_on_battery = False

Expand Down
42 changes: 41 additions & 1 deletion safeeyes/plugins/smartpause/ext_idle_notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import threading
import datetime
import os
import select

from pywayland.client import Display
from pywayland.protocol.wayland.wl_seat import WlSeat
Expand All @@ -33,19 +35,39 @@ class ExtIdleNotify:
_notifier_set = False
_running = True
_thread = None
_r_channel = None
_w_channel = None

_idle_since = None

def __init__(self):
# Note that this creates a new connection to the wayland compositor.
# This is not an issue per se, but does mean that the compositor sees this as
# a new, separate client, that just happens to run in the same process as
# the SafeEyes gtk application.
# (This is not a problem, currently. swayidle does the same, it even runs in a
# separate process.)
# If in the future, a compositor decides to lock down ext-idle-notify-v1 to
# clients somehow, and we need to share the connection to wl_display with gtk
# (which might require some hacks, or FFI code), we might run into other issues
# described in this mailing thread:
# https://lists.freedesktop.org/archives/wayland-devel/2019-March/040344.html
# The best thing would be, of course, for gtk to gain native support for
# ext-idle-notify-v1.
self._display = Display()
self._display.connect()
self._r_channel, self._w_channel = os.pipe()

def stop(self):
self._running = False
# write anything, just to wake up the channel
os.write(self._w_channel, b"!")
self._notification.destroy()
self._notification = None
self._seat = None
self._thread.join()
os.close(self._r_channel)
os.close(self._w_channel)

def run(self):
self._thread = threading.Thread(
Expand All @@ -57,8 +79,26 @@ def _run(self):
reg = self._display.get_registry()
reg.dispatcher["global"] = self._global_handler

display_fd = self._display.get_fd()

while self._running:
self._display.dispatch(block=True)
self._display.flush()

# this blocks until either there are new events in self._display
# (retrieved using dispatch())
# or until there are events in self._r_channel - which means that stop()
# was called
# unfortunately, this seems like the best way to make sure that dispatch
# doesn't block potentially forever (up to multiple seconds in my usage)
read, _w, _x = select.select((display_fd, self._r_channel), (), ())

if self._r_channel in read:
# the channel was written to, which means stop() was called
# at this point, self._running should be false as well
break

if display_fd in read:
self._display.dispatch(block=True)

self._display.disconnect()

Expand Down
9 changes: 6 additions & 3 deletions safeeyes/plugins/trayicon/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from safeeyes import utility
import threading
import time
import typing

"""
Safe Eyes tray icon plugin
Expand Down Expand Up @@ -186,8 +187,10 @@ class DBusMenuService(DBusService):

revision = 0

items = []
idToItems = {}
# TODO: replace dict here with more exact typing for item
items: list[dict] = []
# TODO: replace dict here with more exact typing for item
idToItems: dict[str, dict] = {}

def __init__(self, session_bus, context, items):
super().__init__(
Expand Down Expand Up @@ -366,7 +369,7 @@ class StatusNotifierItemService(DBusService):
Status = "Active"
IconName = "io.github.slgobinath.SafeEyes-enabled"
IconThemePath = ""
ToolTip = ("", [], "Safe Eyes", "")
ToolTip: tuple[str, list[typing.Any], str, str] = ("", [], "Safe Eyes", "")
XAyatanaLabel = ""
ItemIsMenu = True
Menu = None
Expand Down
2 changes: 1 addition & 1 deletion safeeyes/ui/break_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from safeeyes import utility
import Xlib
from Xlib.display import Display
from Xlib.display import X
from Xlib import X

gi.require_version("Gtk", "4.0")
from gi.repository import Gdk
Expand Down