diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..4d8e536 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: bkbilly # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: ['https://www.paypal.me/bkbillybk'] diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..bdaab28 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,39 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..adcf604 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,29 @@ +# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. +# +# You can adjust the behavior by modifying this file. +# For more information, see: +# https://github.com/actions/stale +name: Mark stale issues and pull requests + +on: + schedule: + - cron: '20 20 * * *' + +jobs: + stale: + + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'Stale issue message' + stale-pr-message: 'Stale pull request message' + stale-issue-label: 'no-issue-activity' + stale-pr-label: 'no-pr-activity' + days-before-stale: 60 + days-before-close: 7 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index b887d85..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,18 +0,0 @@ -repos: - - repo: local - hooks: - - id: black - name: black - language: system - entry: poetry run black - types: [python] - - id: isort - name: isort - language: system - entry: poetry run isort - types: [python] - - id: flake8 - name: flake8 - language: system - entry: poetry run flake8 - types: [python] diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7bacbfd..0000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -dist: xenial -sudo: required - -language: python -python: - - 3.6 - - 3.7 -node_js: '8' - -install: - - pip install -U tox-travis - - pip install python-coveralls - - npm install @commitlint/travis-cli - -script: - - node_modules/.bin/commitlint-travis - - tox -r - -after_success: - - coveralls - -notifications: - email: false diff --git a/README.md b/README.md index 37bae86..8f8392f 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,38 @@ -# idle-time +# dbus-idle -[![pypi](https://img.shields.io/pypi/v/idle-time.svg)](https://pypi.python.org/pypi/idle-time) [![Build Status](https://travis-ci.org/escaped/idle-time.png?branch=master)](http://travis-ci.org/escaped/idle-time) [![Coverage](https://coveralls.io/repos/escaped/idle-time/badge.png?branch=master)](https://coveralls.io/r/escaped/idle-time) ![python version](https://img.shields.io/pypi/pyversions/idle-time.svg) ![Project status](https://img.shields.io/pypi/status/idle-time.svg) ![license](https://img.shields.io/pypi/l/idle-time.svg) +[![pypi](https://img.shields.io/pypi/v/dbus-idle.svg)](https://pypi.python.org/pypi/dbus-idle) +![python version](https://img.shields.io/pypi/pyversions/dbus-idle.svg) +![license](https://img.shields.io/pypi/l/dbus-idle.svg) -Detect user idle time or inactivity on Linux and Windows. - -**WARNING** This project is in an alpha status! Though there is already some code to support Windows, it has only been tested on Wayland/Gnome. +Python library to detect user idle time in milliseconds or inactivity on Linux and Windows. ## Requirements -* Python 3.6 or later +* Python 3.7 or later ## Installation -Install using `pip install idle-time` +Install using: +``` +sudo apt install meson libdbus-glib-1-dev patchelf +pip install dbus-idle +``` ## Usage You can use this module from the command line - - python -m idle-time - +```bash +dbus-idle +``` or access the current idle time from within your python program +```python +from dbus_idle import IdleMonitor +milliseconds = IdleMonitor().get_dbus_idle() +``` - from idle_time import IdleMonitor - - monitor = IdleMonitor.get_monitor() - monitor.get_idle_time() +## Contribution +This is based on the work by [Alexander Frenzel](https://github.com/escaped/dbus_idle) diff --git a/dbus_idle/__init__.py b/dbus_idle/__init__.py new file mode 100644 index 0000000..3ba6f20 --- /dev/null +++ b/dbus_idle/__init__.py @@ -0,0 +1,229 @@ +import time +import ctypes +import ctypes.util +import logging +from typing import Any, List, Type +import subprocess + + +logger = logging.getLogger("dbus_idle") +logging.basicConfig(level=logging.ERROR) + +class IdleMonitor: + subclasses: List[Type["IdleMonitor"]] = [] + + def __init__(self, *, idle_threshold: int = 120, debug: bool=False) -> None: + self.idle_threshold = idle_threshold + self.class_used = None + if debug: + logger.setLevel(logging.DEBUG) + + def __init_subclass__(self) -> None: + super().__init_subclass__() + self.subclasses.append(self) + + @classmethod + def get_monitor(self, **kwargs) -> "IdleMonitor": + """ + Return the first available idle monitor. + """ + for monitor_class in self.subclasses: + try: + return monitor_class(**kwargs) + except Exception: + logger.warning("Could not load %s", monitor_class, exc_info=True) + raise RuntimeError("Could not find a working monitor.") + + def get_dbus_idle(self) -> float: + """ + Return idle time in milliseconds. + """ + if self.class_used is None: + for monitor_class in self.subclasses: + try: + self.class_used = monitor_class() + logger.debug("Using: %s", monitor_class.__name__) + return self.class_used.get_dbus_idle() + except Exception: + logger.info("Could not load %s", monitor_class.__name__, exc_info=False) + logger.warning("Could not find any working monitor to get idle time.", exc_info=True) + return None + else: + try: + return self.class_used.get_dbus_idle() + except Exception as e: + logger.warning("Can't run the working monitor enymore.", exc_info=False) + return None + + def is_idle(self) -> bool: + """ + Return whether the user is idling. + """ + return self.get_dbus_idle() > self.idle_threshold + + +class DBusIdleMonitor(IdleMonitor): + """ + Idle monitor for gnome running on wayland. + + Based on + * https://unix.stackexchange.com/a/492328 + """ + + def __init__(self, **kwargs) -> None: + from jeepney import DBusAddress, new_method_call + from jeepney.io.blocking import open_dbus_connection + + self.connection = open_dbus_connection(bus="SESSION") + dbus_addr = DBusAddress( + object_path="/org/freedesktop/DBus", + bus_name="org.freedesktop.DBus", + interface="org.freedesktop.DBus", + ) + + msg = new_method_call(remote_obj=dbus_addr, method="ListNames") + reply = self.connection.send_and_get_reply(msg) + self.idle_msg = None + for service in reply.body[0]: + if "IdleMonitor" in service: + service_path = f"/{service.replace('.', '/')}/Core" + idle_addr = DBusAddress(service_path, bus_name=service, interface=service) + self.idle_msg = new_method_call(remote_obj=idle_addr, method="GetIdletime") + break + if self.idle_msg is None: + raise AttributeError() + + def get_dbus_idle(self) -> float: + idle_reply = self.connection.send_and_get_reply(self.idle_msg) + return int(idle_reply.body[0]) + + +class XprintidleIdleMonitor(IdleMonitor): + """Idle monitor using xprintidle command.""" + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + command = subprocess.run( + ["which", "xprintidle"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + if command.returncode != 0: + raise AttributeError() + + def get_dbus_idle(self) -> float: + stdout = subprocess.run( + 'xprintidle', + stdout=subprocess.PIPE, + stderr=subprocess.PIPE).stdout.decode("UTF-8") + + idle_sec = int(stdout.strip()) + + return idle_sec + + +class X11IdleMonitor(IdleMonitor): + """ + Idle monitor for systems running X11. + + Based on + * http://tperl.blogspot.com/2007/09/x11-idle-time-and-focused-window-in.html + * https://stackoverflow.com/a/55966565/7774036 + """ + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + + class XScreenSaverInfo(ctypes.Structure): + _fields_ = [ + ("window", ctypes.c_ulong), # screen saver window + ("state", ctypes.c_int), # off, on, disabled + ("kind", ctypes.c_int), # blanked, internal, external + ("since", ctypes.c_ulong), # milliseconds + ("idle", ctypes.c_ulong), # milliseconds + ("event_mask", ctypes.c_ulong), + ] # events + + lib_x11 = self._load_lib("X11") + # specify required types + lib_x11.XOpenDisplay.argtypes = [ctypes.c_char_p] + lib_x11.XOpenDisplay.restype = ctypes.c_void_p + lib_x11.XDefaultRootWindow.argtypes = [ctypes.c_void_p] + lib_x11.XDefaultRootWindow.restype = ctypes.c_uint32 + # fetch current settings + self.display = lib_x11.XOpenDisplay(None) + if self.display is None: + raise AttributeError() + self.root_window = lib_x11.XDefaultRootWindow(self.display) + + self.lib_xss = self._load_lib("Xss") + # specify required types + self.lib_xss.XScreenSaverQueryInfo.argtypes = [ + ctypes.c_void_p, + ctypes.c_uint32, + ctypes.POINTER(XScreenSaverInfo), + ] + self.lib_xss.XScreenSaverQueryInfo.restype = ctypes.c_int + self.lib_xss.XScreenSaverAllocInfo.restype = ctypes.POINTER(XScreenSaverInfo) + # allocate memory for idle information + self.xss_info = self.lib_xss.XScreenSaverAllocInfo() + + status = self.lib_xss.XScreenSaverQueryInfo(self.display, self.root_window, self.xss_info) + if status == 0: + raise RuntimeError("Not Supported...") + + def get_dbus_idle(self) -> float: + self.lib_xss.XScreenSaverQueryInfo(self.display, self.root_window, self.xss_info) + return self.xss_info.contents.idle + + def _load_lib(self, name: str) -> Any: + path = ctypes.util.find_library(name) + if path is None: + raise OSError(f"Could not find library `{name}`") + return ctypes.cdll.LoadLibrary(path) + + +class SwayIdleMonitor(IdleMonitor): + """Idle monitor using swayidle command for Wayland environments.""" + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + self.output_file = "/tmp/idletime.txt" + command = subprocess.run( + ["swayidle"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + if command.returncode != 0: + raise AttributeError() + with open(self.output_file, "w") as file: + file.write("0") + subprocess.run( + f'swayidle -w timeout 1 "echo -n \\$(date +%s) > {self.output_file}" resume "echo -n 0 > {self.output_file}" &', + shell=True, + ) + + def get_dbus_idle(self) -> float: + with open(self.output_file, "r") as file: + idle_time = int(file.read()) + if idle_time != 0: + idle_time = time.time() - idle_time + + return idle_time * 1000 + + +class WindowsIdleMonitor(IdleMonitor): + """ + Idle monitor for Windows. + + Based on + * https://stackoverflow.com/q/911856 + """ + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + import win32api + self.win32api = win32api + + def get_dbus_idle(self) -> float: + current_tick = self.win32api.GetTickCount() + last_tick = self.win32api.GetLastInputInfo() + return current_tick - last_tick diff --git a/dbus_idle/__main__.py b/dbus_idle/__main__.py new file mode 100644 index 0000000..1c7fb3f --- /dev/null +++ b/dbus_idle/__main__.py @@ -0,0 +1,14 @@ +from . import IdleMonitor +import argparse + + +def main(): + parser = argparse.ArgumentParser( + prog="dbus-idle", + description="Get idle time in seconds from DBus") + parser.add_argument("-d", "--debug", action="store_true", help="Show debug messeges") + args = parser.parse_args() + + idle_monitor = IdleMonitor(debug=args.debug) + milliseconds = idle_monitor.get_dbus_idle() + print(milliseconds) diff --git a/idle_time/__init__.py b/idle_time/__init__.py deleted file mode 100644 index 30b62a0..0000000 --- a/idle_time/__init__.py +++ /dev/null @@ -1,144 +0,0 @@ -import ctypes -import ctypes.util -import logging -from typing import Any, List, Type - -from jeepney import DBusAddress, new_method_call -from jeepney.integrate.blocking import connect_and_authenticate - -logger = logging.getLogger(name="idle_time") - - -class IdleMonitor: - subclasses: List[Type["IdleMonitor"]] = [] - - def __init__(self, *, idle_threshold: int = 120) -> None: - self.idle_threshold = idle_threshold - - def __init_subclass__(cls) -> None: - super().__init_subclass__() - cls.subclasses.append(cls) - - @classmethod - def get_monitor(cls, **kwargs) -> "IdleMonitor": - """ - Return the first available idle monitor. - - Raises `RuntimeError` if no usable monitor could be found. - """ - for monitor_class in cls.subclasses: - try: - return monitor_class(**kwargs) - except Exception: - logger.warning("Could not load %s", monitor_class, exc_info=True) - raise RuntimeError("Could not find a working monitor.") - - def get_idle_time(self) -> float: - """ - Return idle time in seconds. - """ - raise NotImplementedError() - - def is_idle(self) -> bool: - """ - Return whether the user is idling. - """ - return self.get_idle_time() > self.idle_threshold - - -try: - import win32api -except ImportError: - pass -else: - - class WindowsIdleMonitor(IdleMonitor): - """ - Idle monitor for Windows. - - Based on - * https://stackoverflow.com/q/911856 - """ - - def get_idle_time(self) -> float: - return (win32api.GetTickCount() - win32api.GetLastInputInfo()) / 1000 - - -class GnomeWaylandIdleMonitor(IdleMonitor): - """ - Idle monitor for gnome running on wayland. - - Based on - * https://unix.stackexchange.com/a/492328 - """ - - def __init__(self, **kwargs) -> None: - super().__init__(**kwargs) - - address = DBusAddress( - "/org/gnome/Mutter/IdleMonitor/Core", - bus_name="org.gnome.Mutter.IdleMonitor", - interface="org.gnome.Mutter.IdleMonitor", - ) - self.connection = connect_and_authenticate(bus="SESSION") - self.message = new_method_call(address, "GetIdletime") - - def get_idle_time(self) -> float: - reply = self.connection.send_and_get_reply(self.message) - idle_time = reply[0] - return idle_time / 1000 - - -class X11IdleMonitor(IdleMonitor): - """ - Idle monitor for systems running X11. - - Based on - * http://tperl.blogspot.com/2007/09/x11-idle-time-and-focused-window-in.html - * https://stackoverflow.com/a/55966565/7774036 - """ - - def __init__(self, **kwargs) -> None: - super().__init__(**kwargs) - - class XScreenSaverInfo(ctypes.Structure): - _fields_ = [ - ("window", ctypes.c_ulong), # screen saver window - ("state", ctypes.c_int), # off, on, disabled - ("kind", ctypes.c_int), # blanked, internal, external - ("since", ctypes.c_ulong), # milliseconds - ("idle", ctypes.c_ulong), # milliseconds - ("event_mask", ctypes.c_ulong), - ] # events - - lib_x11 = self._load_lib("X11") - # specify required types - lib_x11.XOpenDisplay.argtypes = [ctypes.c_char_p] - lib_x11.XOpenDisplay.restype = ctypes.c_void_p - lib_x11.XDefaultRootWindow.argtypes = [ctypes.c_void_p] - lib_x11.XDefaultRootWindow.restype = ctypes.c_uint32 - # fetch current settings - self.display = lib_x11.XOpenDisplay(None) - self.root_window = lib_x11.XDefaultRootWindow(self.display) - - self.lib_xss = self._load_lib("Xss") - # specify required types - self.lib_xss.XScreenSaverQueryInfo.argtypes = [ - ctypes.c_void_p, - ctypes.c_uint32, - ctypes.POINTER(XScreenSaverInfo), - ] - self.lib_xss.XScreenSaverQueryInfo.restype = ctypes.c_int - self.lib_xss.XScreenSaverAllocInfo.restype = ctypes.POINTER(XScreenSaverInfo) - # allocate memory for idle information - self.xss_info = self.lib_xss.XScreenSaverAllocInfo() - - def get_idle_time(self) -> float: - self.lib_xss.XScreenSaverQueryInfo(self.display, self.root_window, self.xss_info) - return self.xss_info.contents.idle / 1000 - - def _load_lib(self, name: str) -> Any: - path = ctypes.util.find_library(name) - if path is None: - raise OSError(f"Could not find library `{name}`") - return ctypes.cdll.LoadLibrary(path) diff --git a/idle_time/__main__.py b/idle_time/__main__.py deleted file mode 100644 index 9af2a1d..0000000 --- a/idle_time/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -from . import IdleMonitor - -monitor = IdleMonitor.get_monitor() -print(f"Idle time: {monitor.get_idle_time()}s") diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index dcb44e3..0000000 --- a/poetry.lock +++ /dev/null @@ -1,301 +0,0 @@ -[[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -name = "appdirs" -optional = false -python-versions = "*" -version = "1.4.3" - -[[package]] -category = "dev" -description = "Atomic file writes." -name = "atomicwrites" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.3.0" - -[[package]] -category = "dev" -description = "Classes Without Boilerplate" -name = "attrs" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.1.0" - -[[package]] -category = "dev" -description = "The uncompromising code formatter." -name = "black" -optional = false -python-versions = ">=3.6" -version = "19.3b0" - -[package.dependencies] -appdirs = "*" -attrs = ">=18.1.0" -click = ">=6.5" -toml = ">=0.9.4" - -[[package]] -category = "dev" -description = "Composable command line interface toolkit" -name = "click" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "7.0" - -[[package]] -category = "dev" -description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\"" -name = "colorama" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.4.1" - -[[package]] -category = "dev" -description = "Discover and load entry points from installed packages." -name = "entrypoints" -optional = false -python-versions = ">=2.7" -version = "0.3" - -[[package]] -category = "dev" -description = "the modular source code checker: pep8, pyflakes and co" -name = "flake8" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.7.7" - -[package.dependencies] -entrypoints = ">=0.3.0,<0.4.0" -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.5.0,<2.6.0" -pyflakes = ">=2.1.0,<2.2.0" - -[[package]] -category = "dev" -description = "flake8 plugin that integrates isort ." -name = "flake8-isort" -optional = false -python-versions = "*" -version = "2.7.0" - -[package.dependencies] -flake8 = ">=3.2.1" -isort = ">=4.3.0" -testfixtures = "*" - -[[package]] -category = "dev" -description = "A plugin for flake8 integrating mypy." -name = "flake8-mypy" -optional = false -python-versions = "*" -version = "17.8.0" - -[package.dependencies] -attrs = "*" -flake8 = ">=3.0.0" -mypy = "*" - -[[package]] -category = "dev" -description = "Read metadata from Python packages" -name = "importlib-metadata" -optional = false -python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3" -version = "0.18" - -[package.dependencies] -zipp = ">=0.5" - -[[package]] -category = "dev" -description = "A Python utility / library to sort Python imports." -name = "isort" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "4.3.20" - -[[package]] -category = "main" -description = "Low-level, pure Python DBus protocol wrapper." -name = "jeepney" -optional = false -python-versions = ">=3.5" -version = "0.4" - -[[package]] -category = "dev" -description = "McCabe checker, plugin for flake8" -name = "mccabe" -optional = false -python-versions = "*" -version = "0.6.1" - -[[package]] -category = "dev" -description = "More routines for operating on iterables, beyond itertools" -name = "more-itertools" -optional = false -python-versions = ">=3.4" -version = "7.0.0" - -[[package]] -category = "dev" -description = "Optional static typing for Python (mypyc-compiled version)" -name = "mypy" -optional = false -python-versions = "*" -version = "0.701" - -[package.dependencies] -mypy-extensions = ">=0.4.0,<0.5.0" -typed-ast = ">=1.3.1,<1.4.0" - -[[package]] -category = "dev" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -name = "mypy-extensions" -optional = false -python-versions = "*" -version = "0.4.1" - -[[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" -name = "pluggy" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.12.0" - -[package.dependencies] -importlib-metadata = ">=0.12" - -[[package]] -category = "dev" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -name = "py" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.8.0" - -[[package]] -category = "dev" -description = "Python style guide checker" -name = "pycodestyle" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.5.0" - -[[package]] -category = "dev" -description = "passive checker of Python programs" -name = "pyflakes" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.1.1" - -[[package]] -category = "dev" -description = "pytest: simple powerful testing with Python" -name = "pytest" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.10.1" - -[package.dependencies] -atomicwrites = ">=1.0" -attrs = ">=17.4.0" -colorama = "*" -more-itertools = ">=4.0.0" -pluggy = ">=0.7" -py = ">=1.5.0" -setuptools = "*" -six = ">=1.10.0" - -[[package]] -category = "main" -description = "Python for Window Extensions" -marker = "sys_platform == \"windows\"" -name = "pywin32" -optional = false -python-versions = "*" -version = "224" - -[[package]] -category = "dev" -description = "Python 2 and 3 compatibility utilities" -name = "six" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "1.12.0" - -[[package]] -category = "dev" -description = "A collection of helpers and mock objects for unit tests and doc tests." -name = "testfixtures" -optional = false -python-versions = "*" -version = "6.9.0" - -[[package]] -category = "dev" -description = "Python Library for Tom's Obvious, Minimal Language" -name = "toml" -optional = false -python-versions = "*" -version = "0.10.0" - -[[package]] -category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" -name = "typed-ast" -optional = false -python-versions = "*" -version = "1.3.5" - -[[package]] -category = "dev" -description = "Backport of pathlib-compatible object wrapper for zip files" -name = "zipp" -optional = false -python-versions = ">=2.7" -version = "0.5.1" - -[metadata] -content-hash = "4528b728f67b1eb2395ef73377f80fecbe380764494ae5d054b4ccea38723651" -python-versions = "^3.7" - -[metadata.hashes] -appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"] -atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"] -attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"] -black = ["09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf", "68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"] -click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] -colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"] -entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"] -flake8 = ["859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661", "a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8"] -flake8-isort = ["1e67b6b90a9b980ac3ff73782087752d406ce0a729ed928b92797f9fa188917e", "81a8495eefed3f2f63f26cd2d766c7b1191e923a15b9106e6233724056572c68"] -flake8-mypy = ["47120db63aff631ee1f84bac6fe8e64731dc66da3efc1c51f85e15ade4a3ba18", "cff009f4250e8391bf48990093cff85802778c345c8449d6498b62efefeebcbc"] -importlib-metadata = ["6dfd58dfe281e8d240937776065dd3624ad5469c835248219bd16cf2e12dbeb7", "cb6ee23b46173539939964df59d3d72c3e0c1b5d54b84f1d8a7e912fe43612db"] -isort = ["c40744b6bc5162bbb39c1257fe298b7a393861d50978b565f3ccd9cb9de0182a", "f57abacd059dc3bd666258d1efb0377510a89777fda3e3274e3c01f7c03ae22d"] -jeepney = ["6089412a5de162c04747f0220f6b2223b8ba660acd041e52a76426ca550e3c70", "f6f8b1428403b4afad04b6b82f9ab9fc426c253d7504c9031c41712a2c01dc74"] -mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] -more-itertools = ["2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7", "c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a"] -mypy = ["2afe51527b1f6cdc4a5f34fc90473109b22bf7f21086ba3e9451857cf11489e6", "56a16df3e0abb145d8accd5dbb70eba6c4bd26e2f89042b491faa78c9635d1e2", "5764f10d27b2e93c84f70af5778941b8f4aa1379b2430f85c827e0f5464e8714", "5bbc86374f04a3aa817622f98e40375ccb28c4836f36b66706cf3c6ccce86eda", "6a9343089f6377e71e20ca734cd8e7ac25d36478a9df580efabfe9059819bf82", "6c9851bc4a23dc1d854d3f5dfd5f20a016f8da86bcdbb42687879bb5f86434b0", "b8e85956af3fcf043d6f87c91cbe8705073fc67029ba6e22d3468bfee42c4823", "b9a0af8fae490306bc112229000aa0c2ccc837b49d29a5c42e088c132a2334dd", "bbf643528e2a55df2c1587008d6e3bda5c0445f1240dfa85129af22ae16d7a9a", "c46ab3438bd21511db0f2c612d89d8344154c0c9494afc7fbc932de514cf8d15", "f7a83d6bd805855ef83ec605eb01ab4fa42bcef254b13631e451cbb44914a9b0"] -mypy-extensions = ["37e0e956f41369209a3d5f34580150bcacfabaa57b33a15c0b25f4b5725e0812", "b16cabe759f55e3409a7d231ebd2841378fb0c27a5d1994719e340e4f429ac3e"] -pluggy = ["0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", "b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"] -py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"] -pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"] -pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"] -pytest = ["3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec", "e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"] -pywin32 = ["22e218832a54ed206452c8f3ca9eff07ef327f8e597569a4c2828be5eaa09a77", "32b37abafbfeddb0fe718008d6aada5a71efa2874f068bee1f9e703983dcc49a", "35451edb44162d2f603b5b18bd427bc88fcbc74849eaa7a7e7cfe0f507e5c0c8", "4eda2e1e50faa706ff8226195b84fbcbd542b08c842a9b15e303589f85bfb41c", "5f265d72588806e134c8e1ede8561739071626ea4cc25c12d526aa7b82416ae5", "6852ceac5fdd7a146b570655c37d9eacd520ed1eaeec051ff41c6fc94243d8bf", "6dbc4219fe45ece6a0cc6baafe0105604fdee551b5e876dc475d3955b77190ec", "9bd07746ce7f2198021a9fa187fa80df7b221ec5e4c234ab6f00ea355a3baf99"] -six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] -testfixtures = ["1fec9b69328777c7ca4b0f645325ce71de5368551e897b957d514d0f9bb10d3e", "772c9e3a2743782c9e432de4334da71d2896a94a35748d60dbcdd30271010beb"] -toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] -typed-ast = ["132eae51d6ef3ff4a8c47c393a4ef5ebf0d1aecc96880eb5d6c8ceab7017cc9b", "18141c1484ab8784006c839be8b985cfc82a2e9725837b0ecfa0203f71c4e39d", "2baf617f5bbbfe73fd8846463f5aeafc912b5ee247f410700245d68525ec584a", "3d90063f2cbbe39177e9b4d888e45777012652d6110156845b828908c51ae462", "4304b2218b842d610aa1a1d87e1dc9559597969acc62ce717ee4dfeaa44d7eee", "4983ede548ffc3541bae49a82675996497348e55bafd1554dc4e4a5d6eda541a", "5315f4509c1476718a4825f45a203b82d7fdf2a6f5f0c8f166435975b1c9f7d4", "6cdfb1b49d5345f7c2b90d638822d16ba62dc82f7616e9b4caa10b72f3f16649", "7b325f12635598c604690efd7a0197d0b94b7d7778498e76e0710cd582fd1c7a", "8d3b0e3b8626615826f9a626548057c5275a9733512b137984a68ba1598d3d2f", "8f8631160c79f53081bd23446525db0bc4c5616f78d04021e6e434b286493fd7", "912de10965f3dc89da23936f1cc4ed60764f712e5fa603a09dd904f88c996760", "b010c07b975fe853c65d7bbe9d4ac62f1c69086750a574f6292597763781ba18", "c908c10505904c48081a5415a1e295d8403e353e0c14c42b6d67f8f97fae6616", "c94dd3807c0c0610f7c76f078119f4ea48235a953512752b9175f9f98f5ae2bd", "ce65dee7594a84c466e79d7fb7d3303e7295d16a83c22c7c4037071b059e2c21", "eaa9cfcb221a8a4c2889be6f93da141ac777eb8819f077e1d09fb12d00a09a93", "f3376bc31bad66d46d44b4e6522c5c21976bf9bca4ef5987bb2bf727f4506cbb", "f9202fa138544e13a4ec1a6792c35834250a85958fde1251b6a22e07d1260ae7"] -zipp = ["8c1019c6aad13642199fbe458275ad6a84907634cc9f0989877ccc4a2840139d", "ca943a7e809cc12257001ccfb99e3563da9af99d52f261725e96dfe0f9275bc3"] diff --git a/pyproject.toml b/pyproject.toml index d745a16..a70a41e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,59 +1,34 @@ -[tool.poetry] -name = "idle-time" -version = "0.1.0" -description = "Detect user idle time" -authors = ["Alexander Frenzel "] - -readme = "README.md" -license = "BSD-3-Clause" - -homepage = "https://github.com/escaped/idle_time" -repository = "https://github.com/escaped/idle_time" -documentation = "https://github.com/escaped/idle_time" +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" +[project] +name = "dbus_idle" +version = "2026.7.0" +description = "System idle time using DBus" +readme = "README.md" +keywords = ["dbus_idle", "dbus-idle"] +requires-python = ">=3.7.0" +authors = [ + {name="Alexander Frenzel", email="alex@relatedworks.com"}, + {name="bkbilly", email="bkbilly@hotmail.com"} +] classifiers = [ - "Development Status :: 2 - Pre-Alpha", - "Intended Audience :: Developers", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Topic :: Software Development :: Libraries :: Python Modules", + "Programming Language :: Python :: 3", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", +] +dependencies = [ + "jeepney>=0.9.0 ; platform_system != 'Windows'", + "pywin32>=221 ; platform_system == 'Windows'", ] -[tool.poetry.dependencies] -python = "^3.7" -Jeepney = "^0.4.0" -pywin32 = {version = "^224.0",platform = "windows"} -[tool.poetry.dev-dependencies] -pytest = "^3.0" -flake8 = "^3.7" -flake8-isort = "^2.7" -black = "=19.3b0" -mypy = "^0.701.0" -flake8-mypy = "^17.8" +[project.urls] +"Source Code" = "https://github.com/bkbilly/dbus_idle" -[tool.black] -line-length = 90 -target_version = ['py36'] -include = '\.pyi?$' -exclude = ''' -( - /( - \.eggs # exclude a few common directories in the - | \.git # root of the project - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist - )/ -) -''' +[tool.setuptools.packages.find] +include = ["dbus_idle*"] -[build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +[project.scripts] +dbus-idle = "dbus_idle.__main__:main" diff --git a/setup.cfg b/setup.cfg index fb39cf4..86bca59 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,60 +1,23 @@ -[bdist_wheel] -universal = 1 - +# Setuptools v62.3 doesn't support editable installs with just 'pyproject.toml' (PEP 660). +# Keep this file until it does! [metadata] -description-file = - README.md - - -[isort] -line_length = 90 -known_project = idle_time - +url = https://github.com/bkbilly/dbus_idle [flake8] -exclude = - .git, - __pycache__, - dist - -max-line-length = 90 -max-complexity = 9 - - -[coverage:run] -branch = True -include = idle_time/* -omit = - */tests/* - -[coverage:report] -show_missing = True -exclude_lines = - pragma: no cover - - # Don't complain about missing debug-only code: - def __unicode__ - def __repr__ - def __str__ - - # Don't complain if tests don't hit defensive assertion code: - raise AssertionError - raise NotImplementedError - - # Don't complain if non-runnable code isn't run: - if __name__ == .__main__.: - - -[tool:pytest] -addopts = --flake8 --mypy --durations=10 --cov=idle_time --cov-report term -norecursedirs = build dist -testpaths = - idle_time - tests - - -[mypy] -python_version=3.7 - -ignore_missing_imports = True +exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build +max-complexity = 25 +doctests = True +# To work with Black +# E501: line too long +# W503: Line break occurred before a binary operator +# E203: Whitespace before ':' +# D202 No blank lines allowed after function docstring +# W504 line break after binary operator +ignore = + E501, + W503, + E203, + D202, + W504 +noqa-require-code = True diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tox.ini b/tox.ini deleted file mode 100644 index bed78ec..0000000 --- a/tox.ini +++ /dev/null @@ -1,17 +0,0 @@ -[tox] -skipsdist = True -envlist = - py36 - py37 - - -[testenv] -skip_install = true -deps = - poetry -commands = - poetry install - poetry run black --check idle_time tests - poetry run mypy idle_time tests - poetry run pytest --cov-append - coverage report