From 488176adae6e6a207bf81c926518eea1649d1b56 Mon Sep 17 00:00:00 2001 From: Gleb Popov <6yearold@gmail.com> Date: Thu, 30 Mar 2023 21:55:11 +0300 Subject: [PATCH 01/29] Put all path variables into a separate module. This makes it easier for downstream packagers to customize where howdy installs its files. --- howdy/src/cli/add.py | 22 ++++++++++------------ howdy/src/cli/clear.py | 9 ++++----- howdy/src/cli/config.py | 3 ++- howdy/src/cli/disable.py | 3 ++- howdy/src/cli/list.py | 7 +++---- howdy/src/cli/remove.py | 9 ++++----- howdy/src/cli/set.py | 3 ++- howdy/src/cli/snap.py | 5 ++--- howdy/src/cli/test.py | 11 ++++++----- howdy/src/compare.py | 18 ++++++++---------- howdy/src/paths.py | 12 ++++++++++++ howdy/src/snapshot.py | 15 ++++++--------- 12 files changed, 61 insertions(+), 56 deletions(-) create mode 100644 howdy/src/paths.py diff --git a/howdy/src/cli/add.py b/howdy/src/cli/add.py index 7a6d9eca..5a63bdfe 100644 --- a/howdy/src/cli/add.py +++ b/howdy/src/cli/add.py @@ -8,6 +8,7 @@ import configparser import builtins import numpy as np +import paths from recorders.video_capture import VideoCapture from i18n import _ @@ -26,39 +27,36 @@ # OpenCV needs to be imported after dlib import cv2 -# Define the absolute path to the config directory -config_path = "/etc/howdy" - # Test if at lest 1 of the data files is there and abort if it's not -if not os.path.isfile(config_path + "/dlib-data/shape_predictor_5_face_landmarks.dat"): +if not os.path.isfile(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat"): print(_("Data files have not been downloaded, please run the following commands:")) - print("\n\tcd " + config_path + "/dlib-data") + print("\n\tcd " + paths.dlib_data_dir) print("\tsudo ./install.sh\n") sys.exit(1) # Read config from disk config = configparser.ConfigParser() -config.read(config_path + "/config.ini") +config.read(paths.config_dir + "/config.ini") use_cnn = config.getboolean("core", "use_cnn", fallback=False) if use_cnn: - face_detector = dlib.cnn_face_detection_model_v1(config_path + "/dlib-data/mmod_human_face_detector.dat") + face_detector = dlib.cnn_face_detection_model_v1(paths.dlib_data_dir + "mmod_human_face_detector.dat") else: face_detector = dlib.get_frontal_face_detector() -pose_predictor = dlib.shape_predictor(config_path + "/dlib-data/shape_predictor_5_face_landmarks.dat") -face_encoder = dlib.face_recognition_model_v1(config_path + "/dlib-data/dlib_face_recognition_resnet_model_v1.dat") +pose_predictor = dlib.shape_predictor(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat") +face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir + "dlib_face_recognition_resnet_model_v1.dat") user = builtins.howdy_user # The permanent file to store the encoded model in -enc_file = config_path + "/models/" + user + ".dat" +enc_file = paths.user_models_dir + user + ".dat" # Known encodings encodings = [] # Make the ./models folder if it doesn't already exist -if not os.path.exists(config_path + "/models"): +if not os.path.exists(paths.user_models_dir): print(_("No face model folder found, creating one")) - os.makedirs(config_path + "/models") + os.makedirs(paths.user_models_dir) # To try read a premade encodings file if it exists try: diff --git a/howdy/src/cli/clear.py b/howdy/src/cli/clear.py index 6fa5f3ef..aa43e152 100644 --- a/howdy/src/cli/clear.py +++ b/howdy/src/cli/clear.py @@ -4,21 +4,20 @@ import os import sys import builtins +import paths from i18n import _ -# Get the full path to this file -path = "/etc/howdy/models" # Get the passed user user = builtins.howdy_user # Check if the models folder is there -if not os.path.exists(path): +if not os.path.exists(paths.user_models_dir): print(_("No models created yet, can't clear them if they don't exist")) sys.exit(1) # Check if the user has a models file to delete -if not os.path.isfile(path + "/" + user + ".dat"): +if not os.path.isfile(paths.user_models_dir + user + ".dat"): print(_("{} has no models or they have been cleared already").format(user)) sys.exit(1) @@ -34,5 +33,5 @@ sys.exit(1) # Delete otherwise -os.remove(path + "/" + user + ".dat") +os.remove(paths.user_models_dir + user + ".dat") print(_("\nModels cleared")) diff --git a/howdy/src/cli/config.py b/howdy/src/cli/config.py index 71064839..04c51798 100644 --- a/howdy/src/cli/config.py +++ b/howdy/src/cli/config.py @@ -3,6 +3,7 @@ # Import required modules import os import subprocess +import paths from i18n import _ @@ -19,4 +20,4 @@ editor = "/etc/alternatives/editor" # Open the editor as a subprocess and fork it -subprocess.call([editor, "/etc/howdy/config.ini"]) +subprocess.call([editor, paths.config_dir + "config.ini"]) diff --git a/howdy/src/cli/disable.py b/howdy/src/cli/disable.py index be78c97f..1f655412 100644 --- a/howdy/src/cli/disable.py +++ b/howdy/src/cli/disable.py @@ -6,11 +6,12 @@ import builtins import fileinput import configparser +import paths from i18n import _ # Get the absolute filepath -config_path = os.path.dirname("/etc/howdy") + "/config.ini" +config_path = os.path.dirname(paths.config_dir) + "/config.ini" # Read config from disk config = configparser.ConfigParser() diff --git a/howdy/src/cli/list.py b/howdy/src/cli/list.py index 3532e9f8..7539837d 100644 --- a/howdy/src/cli/list.py +++ b/howdy/src/cli/list.py @@ -6,21 +6,20 @@ import json import time import builtins +import paths from i18n import _ -# Get the absolute path and the username -path = "/etc/howdy" user = builtins.howdy_user # Check if the models file has been created yet -if not os.path.exists(path + "/models"): +if not os.path.exists(paths.user_models_dir): print(_("Face models have not been initialized yet, please run:")) print("\n\tsudo howdy -U " + user + " add\n") sys.exit(1) # Path to the models file -enc_file = path + "/models/" + user + ".dat" +enc_file = paths.user_models_dir + user + ".dat" # Try to load the models file and abort if the user does not have it yet try: diff --git a/howdy/src/cli/remove.py b/howdy/src/cli/remove.py index 6321e0b5..37894422 100644 --- a/howdy/src/cli/remove.py +++ b/howdy/src/cli/remove.py @@ -5,11 +5,10 @@ import os import json import builtins +import paths from i18n import _ -# Get the absolute path and the username -path = "/etc/howdy" user = builtins.howdy_user # Check if enough arguments have been passed @@ -22,13 +21,13 @@ sys.exit(1) # Check if the models file has been created yet -if not os.path.exists(path + "/models"): +if not os.path.exists(paths.user_models_dir): print(_("Face models have not been initialized yet, please run:")) print("\n\thowdy add\n") sys.exit(1) # Path to the models file -enc_file = path + "/models/" + user + ".dat" +enc_file = paths.user_models_dir + user + ".dat" # Try to load the models file and abort if the user does not have it yet try: @@ -72,7 +71,7 @@ # Remove the entire file if this encoding is the only one if len(encodings) == 1: - os.remove(path + "/models/" + user + ".dat") + os.remove(paths.user_models_dir + user + ".dat") print(_("Removed last model, howdy disabled for user")) else: # A place holder to contain the encodings that will remain diff --git a/howdy/src/cli/set.py b/howdy/src/cli/set.py index 14d15c20..efbbee5b 100644 --- a/howdy/src/cli/set.py +++ b/howdy/src/cli/set.py @@ -5,11 +5,12 @@ import os import builtins import fileinput +import paths from i18n import _ # Get the absolute filepath -config_path = os.path.dirname("/etc/howdy") + "/config.ini" +config_path = os.path.dirname(paths.config_dir) + "/config.ini" # Check if enough arguments have been passed if len(builtins.howdy_args.arguments) < 2: diff --git a/howdy/src/cli/snap.py b/howdy/src/cli/snap.py index cbcae501..2c625d3b 100644 --- a/howdy/src/cli/snap.py +++ b/howdy/src/cli/snap.py @@ -5,15 +5,14 @@ import configparser import datetime import snapshot +import paths from recorders.video_capture import VideoCapture from i18n import _ -path = "/etc/howdy" - # Read the config config = configparser.ConfigParser() -config.read(path + "/config.ini") +config.read(paths.config_dir + "config.ini") # Start video capture video_capture = VideoCapture(config) diff --git a/howdy/src/cli/test.py b/howdy/src/cli/test.py index 3a6e4d19..563be19b 100644 --- a/howdy/src/cli/test.py +++ b/howdy/src/cli/test.py @@ -10,6 +10,7 @@ import dlib import cv2 import numpy as np +import paths from i18n import _ from recorders.video_capture import VideoCapture @@ -19,7 +20,7 @@ # Read config from disk config = configparser.ConfigParser() -config.read(path + "/config.ini") +config.read(paths.config_dir + "config.ini") if config.get("video", "recording_plugin", fallback="opencv") != "opencv": print(_("Howdy has been configured to use a recorder which doesn't support the test command yet, aborting")) @@ -59,20 +60,20 @@ def print_text(line_number, text): if use_cnn: face_detector = dlib.cnn_face_detection_model_v1( - path + "/dlib-data/mmod_human_face_detector.dat" + paths.dlib_data_dir + "mmod_human_face_detector.dat" ) else: face_detector = dlib.get_frontal_face_detector() -pose_predictor = dlib.shape_predictor(path + "/dlib-data/shape_predictor_5_face_landmarks.dat") -face_encoder = dlib.face_recognition_model_v1(path + "/dlib-data/dlib_face_recognition_resnet_model_v1.dat") +pose_predictor = dlib.shape_predictor(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat") +face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir + "dlib_face_recognition_resnet_model_v1.dat") encodings = [] models = None try: user = builtins.howdy_user - models = json.load(open(path + "/models/" + user + ".dat")) + models = json.load(open(paths.user_models_dir + user + ".dat")) for model in models: encodings += model["data"] diff --git a/howdy/src/compare.py b/howdy/src/compare.py index 99f5285b..f81fe386 100644 --- a/howdy/src/compare.py +++ b/howdy/src/compare.py @@ -23,6 +23,7 @@ import snapshot import numpy as np import _thread as thread +import paths # Allow imports from the local howdy folder sys.path.append('/lib/security/howdy') @@ -48,22 +49,22 @@ def init_detector(lock): global face_detector, pose_predictor, face_encoder # Test if at lest 1 of the data files is there and abort if it's not - if not os.path.isfile(PATH + "/dlib-data/shape_predictor_5_face_landmarks.dat"): + if not os.path.isfile(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat"): print(_("Data files have not been downloaded, please run the following commands:")) - print("\n\tcd " + PATH + "/dlib-data") + print("\n\tcd " + paths.dlib_data_dir) print("\tsudo ./install.sh\n") lock.release() exit(1) # Use the CNN detector if enabled if use_cnn: - face_detector = dlib.cnn_face_detection_model_v1(PATH + "/dlib-data/mmod_human_face_detector.dat") + face_detector = dlib.cnn_face_detection_model_v1(paths.dlib_data_dir + "mmod_human_face_detector.dat") else: face_detector = dlib.get_frontal_face_detector() # Start the others regardless - pose_predictor = dlib.shape_predictor(PATH + "/dlib-data/shape_predictor_5_face_landmarks.dat") - face_encoder = dlib.face_recognition_model_v1(PATH + "/dlib-data/dlib_face_recognition_resnet_model_v1.dat") + pose_predictor = dlib.shape_predictor(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat") + face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir + "dlib_face_recognition_resnet_model_v1.dat") # Note the time it took to initialize detectors timings["ll"] = time.time() - timings["ll"] @@ -103,9 +104,6 @@ def send_to_ui(type, message): if len(sys.argv) < 2: exit(12) -# Get the absolute path to the config directory -PATH = "/etc/howdy" - # The username of the user being authenticated user = sys.argv[1] # The model file contents @@ -129,7 +127,7 @@ def send_to_ui(type, message): # Try to load the face model from the models folder try: - models = json.load(open(PATH + "/models/" + user + ".dat")) + models = json.load(open(paths.user_models_dir + user + ".dat")) for model in models: encodings += model["data"] @@ -142,7 +140,7 @@ def send_to_ui(type, message): # Read config from disk config = configparser.ConfigParser() -config.read(PATH + "/config.ini") +config.read(paths.config_dir + "config.ini") # Get all config values needed use_cnn = config.getboolean("core", "use_cnn", fallback=False) diff --git a/howdy/src/paths.py b/howdy/src/paths.py new file mode 100644 index 00000000..22825405 --- /dev/null +++ b/howdy/src/paths.py @@ -0,0 +1,12 @@ + +# Define the absolute path to the config directory +config_dir = "/etc/howdy/" + +# Define the absolute path to the DLib models data directory +dlib_data_dir = config_dir + "/dlib-data/" + +# Define the absolute path to the Howdy user models directory +user_models_dir = config_dir + "/models/" + +# Define path to any howdy logs +log_path = "/var/log/howdy" diff --git a/howdy/src/snapshot.py b/howdy/src/snapshot.py index 324b5789..9f2f563c 100644 --- a/howdy/src/snapshot.py +++ b/howdy/src/snapshot.py @@ -49,19 +49,16 @@ def generate(frames, text_lines): line_number += 1 - # Define path to any howdy logs - log_path = "/var/log/howdy" - # Made sure a snapshot folder exist - if not os.path.exists(log_path): - os.makedirs(log_path) - if not os.path.exists(log_path + "/snapshots"): - os.makedirs(log_path + "/snapshots") + if not os.path.exists(paths.log_path): + os.makedirs(paths.log_path) + if not os.path.exists(paths.log_path + "/snapshots"): + os.makedirs(paths.log_path + "/snapshots") # Generate a filename based on the current time filename = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%S.jpg") # Write the image to that file - cv2.imwrite(log_path + "/snapshots/" + filename, snap) + cv2.imwrite(paths.log_path + "/snapshots/" + filename, snap) # Return the saved file location - return log_path + "/snapshots/" + filename + return paths.log_path + "/snapshots/" + filename From 53a6ea5eb1279dca62683dd61e28a2b37010bf30 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Mon, 19 Jun 2023 22:54:40 +0200 Subject: [PATCH 02/29] chore: move man page --- howdy/debian/howdy.manpages | 2 +- howdy/{debian => }/howdy.1 | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename howdy/{debian => }/howdy.1 (100%) diff --git a/howdy/debian/howdy.manpages b/howdy/debian/howdy.manpages index 2578acdb..a27b8e74 100644 --- a/howdy/debian/howdy.manpages +++ b/howdy/debian/howdy.manpages @@ -1 +1 @@ -debian/howdy.1 +howdy.1 diff --git a/howdy/debian/howdy.1 b/howdy/howdy.1 similarity index 100% rename from howdy/debian/howdy.1 rename to howdy/howdy.1 From 2d2480054d9ff5aff07a41e01706816d0df121da Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Mon, 19 Jun 2023 23:05:30 +0200 Subject: [PATCH 03/29] build: add meson build system Add Meson as a build system for the whole howdy package, which allow better flexibility for the paths configuration. Generate a python module, which contains the paths used by the other modules and the header containing the paths for the PAM module. --- howdy/src/bin/howdy.in | 3 + howdy/src/cli/add.py | 12 +-- howdy/src/cli/clear.py | 4 +- howdy/src/cli/config.py | 2 +- howdy/src/cli/disable.py | 2 +- howdy/src/cli/list.py | 2 +- howdy/src/cli/remove.py | 4 +- howdy/src/cli/set.py | 2 +- howdy/src/cli/snap.py | 2 +- howdy/src/cli/test.py | 10 +-- howdy/src/compare.py | 12 +-- howdy/src/meson.build | 129 ++++++++++++++++++++++++++++ howdy/src/meson.options | 2 + howdy/src/pam/main.cc | 8 +- howdy/src/pam/meson.build | 17 +++- howdy/src/pam/paths.hh.in | 1 + howdy/src/{paths.py => paths.py.in} | 9 +- howdy/src/snapshot.py | 8 +- 18 files changed, 189 insertions(+), 40 deletions(-) create mode 100644 howdy/src/bin/howdy.in create mode 100644 howdy/src/meson.build create mode 100644 howdy/src/meson.options create mode 100644 howdy/src/pam/paths.hh.in rename howdy/src/{paths.py => paths.py.in} (52%) diff --git a/howdy/src/bin/howdy.in b/howdy/src/bin/howdy.in new file mode 100644 index 00000000..e9f49531 --- /dev/null +++ b/howdy/src/bin/howdy.in @@ -0,0 +1,3 @@ +#!/usr/bin/env sh + +/usr/bin/env python3 "@script_path@" "$@" \ No newline at end of file diff --git a/howdy/src/cli/add.py b/howdy/src/cli/add.py index 5a63bdfe..19e8b8cd 100644 --- a/howdy/src/cli/add.py +++ b/howdy/src/cli/add.py @@ -28,7 +28,7 @@ import cv2 # Test if at lest 1 of the data files is there and abort if it's not -if not os.path.isfile(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat"): +if not os.path.isfile(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat"): print(_("Data files have not been downloaded, please run the following commands:")) print("\n\tcd " + paths.dlib_data_dir) print("\tsudo ./install.sh\n") @@ -36,20 +36,20 @@ # Read config from disk config = configparser.ConfigParser() -config.read(paths.config_dir + "/config.ini") +config.read(paths.config_dir / "config.ini") use_cnn = config.getboolean("core", "use_cnn", fallback=False) if use_cnn: - face_detector = dlib.cnn_face_detection_model_v1(paths.dlib_data_dir + "mmod_human_face_detector.dat") + face_detector = dlib.cnn_face_detection_model_v1(paths.dlib_data_dir / "mmod_human_face_detector.dat") else: face_detector = dlib.get_frontal_face_detector() -pose_predictor = dlib.shape_predictor(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat") -face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir + "dlib_face_recognition_resnet_model_v1.dat") +pose_predictor = dlib.shape_predictor(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat") +face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir / "dlib_face_recognition_resnet_model_v1.dat") user = builtins.howdy_user # The permanent file to store the encoded model in -enc_file = paths.user_models_dir + user + ".dat" +enc_file = paths.user_models_dir / f"{user}.dat" # Known encodings encodings = [] diff --git a/howdy/src/cli/clear.py b/howdy/src/cli/clear.py index aa43e152..c5a48b63 100644 --- a/howdy/src/cli/clear.py +++ b/howdy/src/cli/clear.py @@ -17,7 +17,7 @@ sys.exit(1) # Check if the user has a models file to delete -if not os.path.isfile(paths.user_models_dir + user + ".dat"): +if not os.path.isfile(paths.user_models_dir / f"{user}.dat"): print(_("{} has no models or they have been cleared already").format(user)) sys.exit(1) @@ -33,5 +33,5 @@ sys.exit(1) # Delete otherwise -os.remove(paths.user_models_dir + user + ".dat") +os.remove(paths.user_models_dir / f"{user}.dat") print(_("\nModels cleared")) diff --git a/howdy/src/cli/config.py b/howdy/src/cli/config.py index 04c51798..c2980999 100644 --- a/howdy/src/cli/config.py +++ b/howdy/src/cli/config.py @@ -20,4 +20,4 @@ editor = "/etc/alternatives/editor" # Open the editor as a subprocess and fork it -subprocess.call([editor, paths.config_dir + "config.ini"]) +subprocess.call([editor, paths.config_dir / "config.ini"]) diff --git a/howdy/src/cli/disable.py b/howdy/src/cli/disable.py index 1f655412..ac98ea0c 100644 --- a/howdy/src/cli/disable.py +++ b/howdy/src/cli/disable.py @@ -11,7 +11,7 @@ from i18n import _ # Get the absolute filepath -config_path = os.path.dirname(paths.config_dir) + "/config.ini" +config_path = paths.config_dir.parent / "config.ini" # Read config from disk config = configparser.ConfigParser() diff --git a/howdy/src/cli/list.py b/howdy/src/cli/list.py index 7539837d..2e5073af 100644 --- a/howdy/src/cli/list.py +++ b/howdy/src/cli/list.py @@ -19,7 +19,7 @@ sys.exit(1) # Path to the models file -enc_file = paths.user_models_dir + user + ".dat" +enc_file = paths.user_models_dir / f"{user}.dat" # Try to load the models file and abort if the user does not have it yet try: diff --git a/howdy/src/cli/remove.py b/howdy/src/cli/remove.py index 37894422..d4df673b 100644 --- a/howdy/src/cli/remove.py +++ b/howdy/src/cli/remove.py @@ -27,7 +27,7 @@ sys.exit(1) # Path to the models file -enc_file = paths.user_models_dir + user + ".dat" +enc_file = paths.user_models_dir / f"{user}.dat" # Try to load the models file and abort if the user does not have it yet try: @@ -71,7 +71,7 @@ # Remove the entire file if this encoding is the only one if len(encodings) == 1: - os.remove(paths.user_models_dir + user + ".dat") + os.remove(paths.user_models_dir / f"{user}.dat") print(_("Removed last model, howdy disabled for user")) else: # A place holder to contain the encodings that will remain diff --git a/howdy/src/cli/set.py b/howdy/src/cli/set.py index efbbee5b..a4184b1c 100644 --- a/howdy/src/cli/set.py +++ b/howdy/src/cli/set.py @@ -10,7 +10,7 @@ from i18n import _ # Get the absolute filepath -config_path = os.path.dirname(paths.config_dir) + "/config.ini" +config_path = paths.config_dir / "/config.ini" # Check if enough arguments have been passed if len(builtins.howdy_args.arguments) < 2: diff --git a/howdy/src/cli/snap.py b/howdy/src/cli/snap.py index 2c625d3b..e16187e4 100644 --- a/howdy/src/cli/snap.py +++ b/howdy/src/cli/snap.py @@ -12,7 +12,7 @@ # Read the config config = configparser.ConfigParser() -config.read(paths.config_dir + "config.ini") +config.read(paths.config_dir / "config.ini") # Start video capture video_capture = VideoCapture(config) diff --git a/howdy/src/cli/test.py b/howdy/src/cli/test.py index 563be19b..68477ed3 100644 --- a/howdy/src/cli/test.py +++ b/howdy/src/cli/test.py @@ -20,7 +20,7 @@ # Read config from disk config = configparser.ConfigParser() -config.read(paths.config_dir + "config.ini") +config.read(paths.config_dir / "config.ini") if config.get("video", "recording_plugin", fallback="opencv") != "opencv": print(_("Howdy has been configured to use a recorder which doesn't support the test command yet, aborting")) @@ -60,20 +60,20 @@ def print_text(line_number, text): if use_cnn: face_detector = dlib.cnn_face_detection_model_v1( - paths.dlib_data_dir + "mmod_human_face_detector.dat" + paths.dlib_data_dir / "mmod_human_face_detector.dat" ) else: face_detector = dlib.get_frontal_face_detector() -pose_predictor = dlib.shape_predictor(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat") -face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir + "dlib_face_recognition_resnet_model_v1.dat") +pose_predictor = dlib.shape_predictor(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat") +face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir / "dlib_face_recognition_resnet_model_v1.dat") encodings = [] models = None try: user = builtins.howdy_user - models = json.load(open(paths.user_models_dir + user + ".dat")) + models = json.load(open(paths.user_models_dir / f"{user}.dat")) for model in models: encodings += model["data"] diff --git a/howdy/src/compare.py b/howdy/src/compare.py index f81fe386..48d3e9f4 100644 --- a/howdy/src/compare.py +++ b/howdy/src/compare.py @@ -49,7 +49,7 @@ def init_detector(lock): global face_detector, pose_predictor, face_encoder # Test if at lest 1 of the data files is there and abort if it's not - if not os.path.isfile(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat"): + if not os.path.isfile(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat"): print(_("Data files have not been downloaded, please run the following commands:")) print("\n\tcd " + paths.dlib_data_dir) print("\tsudo ./install.sh\n") @@ -58,13 +58,13 @@ def init_detector(lock): # Use the CNN detector if enabled if use_cnn: - face_detector = dlib.cnn_face_detection_model_v1(paths.dlib_data_dir + "mmod_human_face_detector.dat") + face_detector = dlib.cnn_face_detection_model_v1(paths.dlib_data_dir / "mmod_human_face_detector.dat") else: face_detector = dlib.get_frontal_face_detector() # Start the others regardless - pose_predictor = dlib.shape_predictor(paths.dlib_data_dir + "shape_predictor_5_face_landmarks.dat") - face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir + "dlib_face_recognition_resnet_model_v1.dat") + pose_predictor = dlib.shape_predictor(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat") + face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir / "dlib_face_recognition_resnet_model_v1.dat") # Note the time it took to initialize detectors timings["ll"] = time.time() - timings["ll"] @@ -127,7 +127,7 @@ def send_to_ui(type, message): # Try to load the face model from the models folder try: - models = json.load(open(paths.user_models_dir + user + ".dat")) + models = json.load(open(paths.user_models_dir / f"{user}.dat")) for model in models: encodings += model["data"] @@ -140,7 +140,7 @@ def send_to_ui(type, message): # Read config from disk config = configparser.ConfigParser() -config.read(paths.config_dir + "config.ini") +config.read(paths.config_dir / "config.ini") # Get all config values needed use_cnn = config.getboolean("core", "use_cnn", fallback=False) diff --git a/howdy/src/meson.build b/howdy/src/meson.build new file mode 100644 index 00000000..bb127a53 --- /dev/null +++ b/howdy/src/meson.build @@ -0,0 +1,129 @@ +project('howdy', 'cpp', license: 'MIT', license_files: '../LICENSE', version: 'beta', meson_version: '>= 1.1.0') + +py = import('python').find_installation() +py.dependency() + +confdir = join_paths(get_option('sysconfdir'), 'howdy') +dlibdatadir = join_paths(confdir, 'dlib-data') +usermodelsdir = join_paths(confdir, 'models') +logpath = '/var/log/howdy' + +py_conf = configuration_data({ + 'config_dir': confdir, + 'dlib_data_dir': dlibdatadir, + 'user_models_dir': usermodelsdir, + 'log_path': logpath, +}) + +py_paths = configure_file( + input: 'paths.py.in', + output: 'paths.py', + configuration: py_conf, +) + +py_sources = [ + 'cli/__init__.py', + 'cli/add.py', + 'cli/clear.py', + 'cli/config.py', + 'cli/disable.py', + 'cli/list.py', + 'cli/remove.py', + 'cli/set.py', + 'cli/snap.py', + 'cli/test.py', + 'cli.py', + 'compare.py', + 'i18n.py', + 'recorders/__init__.py', + 'recorders/ffmpeg_reader.py', + 'recorders/pyv4l2_reader.py', + 'recorders/v4l2.py', + 'recorders/video_capture.py', + 'rubberstamps/__init__.py', + 'rubberstamps/hotkey.py', + 'rubberstamps/nod.py', + 'snapshot.py', + py_paths, +] + +# Include PAM module +compare_script_path = join_paths(py.get_install_dir(), 'howdy', 'compare.py') +subdir('pam') + +py.install_sources( + py_sources, + subdir: 'howdy', + preserve_path: true, +) + +install_data('logo.png', install_tag: 'meta') + +install_data('config.ini', install_dir: confdir, install_mode: 'rwxr--r--', install_tag: 'config') + +install_data('dlib-data/install.sh', install_dir: dlibdatadir, install_mode: 'rwxr--r--') + +install_data('dlib-data/Readme.md', install_dir: dlibdatadir, install_mode: 'r--r--r--', install_tag: 'docs') +install_man('../howdy.1') + +# if get_option('fetch_dlib_data') +# downloader = find_program('wget') +# bunzip2 = find_program('bunzip2') + +# links = [ +# 'https://github.com/davisking/dlib-models/raw/master/dlib_face_recognition_resnet_model_v1.dat.bz2', +# 'https://github.com/davisking/dlib-models/raw/master/mmod_human_face_detector.dat.bz2', +# 'https://github.com/davisking/dlib-models/raw/master/shape_predictor_5_face_landmarks.dat.bz2' +# ] + +# archived_model_files = [ +# 'dlib_face_recognition_resnet_model_v1.dat.bz2', +# 'shape_predictor_5_face_landmarks.dat.bz2', +# 'mmod_human_face_detector.dat.bz2' +# ] + +# download = run_command( +# 'download', +# links, +# output: archived_model_files, +# command: [downloader, '-O', '@OUTPUT@', '@INPUT@'] +# ) + +# model_files = [ +# 'dlib_face_recognition_resnet_model_v1.dat', +# 'shape_predictor_5_face_landmarks.dat', +# 'mmod_human_face_detector.dat' +# ] + +# models = custom_target( +# 'models', +# input: archived_model_files, +# output: model_files, +# command: [bunzip2, '-k', '@INPUT@'], +# ) + +# install_data( +# model_files, +# install_dir: join_paths(get_option('prefix'), get_option('libdir'), 'dlib_models'), +# ) + +# endif + +py_path = py.get_install_dir() +cli_path = join_paths(py_path, 'howdy', 'cli.py') + +conf_data = configuration_data({ 'script_path': cli_path }) + +bin_name = 'howdy' + +bin = configure_file( + input: 'bin/howdy.in', + output: bin_name, + configuration: conf_data +) + +install_data( + bin, + install_mode: 'rwxr-xr-x', + install_dir: get_option('bindir'), +) \ No newline at end of file diff --git a/howdy/src/meson.options b/howdy/src/meson.options new file mode 100644 index 00000000..05860542 --- /dev/null +++ b/howdy/src/meson.options @@ -0,0 +1,2 @@ +option('pam_dir', type: 'string', value: '/lib/security', description: 'Set the pam_howdy destination directory') +#option('fetch_dlib_data', type: 'boolean', value: false, description: 'Download dlib data files') \ No newline at end of file diff --git a/howdy/src/pam/main.cc b/howdy/src/pam/main.cc index 290a549f..2f7a996c 100644 --- a/howdy/src/pam/main.cc +++ b/howdy/src/pam/main.cc @@ -41,12 +41,12 @@ #include "enter_device.hh" #include "main.hh" #include "optional_task.hh" +#include "paths.hh" const auto DEFAULT_TIMEOUT = std::chrono::duration(100); const auto MAX_RETRIES = 5; const auto PYTHON_EXECUTABLE = "python3"; -const auto COMPARE_PROCESS_PATH = "/lib/security/howdy/compare.py"; #define S(msg) gettext(msg) @@ -80,7 +80,8 @@ auto howdy_error(int status, syslog(LOG_ERR, "Failure, image too dark"); break; case CompareError::INVALID_DEVICE: - syslog(LOG_ERR, "Failure, not possible to open camera at configured path"); + syslog(LOG_ERR, + "Failure, not possible to open camera at configured path"); break; default: conv_function(PAM_ERROR_MSG, @@ -321,7 +322,8 @@ auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv, // Wait for the end either of the child or the password input { std::unique_lock lock(mutx); - convar.wait(lock, [&] { return confirmation_type != ConfirmationType::Unset; }); + convar.wait(lock, + [&] { return confirmation_type != ConfirmationType::Unset; }); } // The password has been entered or an error has occurred diff --git a/howdy/src/pam/meson.build b/howdy/src/pam/meson.build index be93919a..3cb01613 100644 --- a/howdy/src/pam/meson.build +++ b/howdy/src/pam/meson.build @@ -1,5 +1,3 @@ -project('pam_howdy', 'cpp', version: '0.1.0', default_options: ['cpp_std=c++14']) - inih_cpp = dependency('INIReader', fallback: ['inih', 'INIReader_dep']) libevdev = dependency('libevdev') libpam = meson.get_compiler('cpp').find_library('pam') @@ -8,6 +6,16 @@ threads = dependency('threads') # Translations subdir('po') +# Paths +paths = { 'compare_script_path': compare_script_path } + +paths_h = configure_file( + input: 'paths.hh.in', + output: 'paths.hh', + configuration: paths, + install_dir: get_option('pam_dir') +) + shared_library( 'pam_howdy', 'main.cc', @@ -18,7 +26,10 @@ shared_library( threads, libevdev, ], + link_depends: [ + paths_h, + ], install: true, - install_dir: '/lib/security', + install_dir: get_option('pam_dir'), name_prefix: '' ) diff --git a/howdy/src/pam/paths.hh.in b/howdy/src/pam/paths.hh.in new file mode 100644 index 00000000..df21928d --- /dev/null +++ b/howdy/src/pam/paths.hh.in @@ -0,0 +1 @@ +const auto COMPARE_PROCESS_PATH = "@compare_script_path@"; \ No newline at end of file diff --git a/howdy/src/paths.py b/howdy/src/paths.py.in similarity index 52% rename from howdy/src/paths.py rename to howdy/src/paths.py.in index 22825405..e51d738e 100644 --- a/howdy/src/paths.py +++ b/howdy/src/paths.py.in @@ -1,12 +1,13 @@ +from pathlib import PurePath # Define the absolute path to the config directory -config_dir = "/etc/howdy/" +config_dir = PurePath("@config_dir@") # Define the absolute path to the DLib models data directory -dlib_data_dir = config_dir + "/dlib-data/" +dlib_data_dir = PurePath("@dlib_data_dir@") # Define the absolute path to the Howdy user models directory -user_models_dir = config_dir + "/models/" +user_models_dir = PurePath("@user_models_dir@") # Define path to any howdy logs -log_path = "/var/log/howdy" +log_path = PurePath("@log_path@") diff --git a/howdy/src/snapshot.py b/howdy/src/snapshot.py index 9f2f563c..34e34289 100644 --- a/howdy/src/snapshot.py +++ b/howdy/src/snapshot.py @@ -52,13 +52,13 @@ def generate(frames, text_lines): # Made sure a snapshot folder exist if not os.path.exists(paths.log_path): os.makedirs(paths.log_path) - if not os.path.exists(paths.log_path + "/snapshots"): - os.makedirs(paths.log_path + "/snapshots") + if not os.path.exists(paths.log_path / "snapshots"): + os.makedirs(paths.log_path / "snapshots") # Generate a filename based on the current time filename = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%S.jpg") # Write the image to that file - cv2.imwrite(paths.log_path + "/snapshots/" + filename, snap) + cv2.imwrite(paths.log_path / "snapshots" / filename, snap) # Return the saved file location - return paths.log_path + "/snapshots/" + filename + return paths.log_path / "/snapshots/" / filename From 452dac8708846183baa47673cc0cb3a92f5c13e8 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Mon, 19 Jun 2023 23:10:24 +0200 Subject: [PATCH 04/29] ci: fix workflow --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 1843f5d0..ba897b79 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -17,7 +17,7 @@ jobs: - name: Build run: | - meson setup build howdy/src/pam + meson setup build howdy/src ninja -C build - name: Check source code From d95f5e24513d3cb1919c1e017d8bfdc2c053b1f9 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 11:18:09 +0200 Subject: [PATCH 05/29] fix: replace deprecated function --- howdy/src/pam/po/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/howdy/src/pam/po/meson.build b/howdy/src/pam/po/meson.build index 762f7a47..58e47a42 100644 --- a/howdy/src/pam/po/meson.build +++ b/howdy/src/pam/po/meson.build @@ -6,5 +6,5 @@ localedir = '-DLOCALEDIR="@0@"'.format(get_option('prefix') / get_option('locale add_project_arguments(gettext_package, localedir, language: 'cpp') i18n.gettext(meson.project_name(), - args: [ '--directory=' + meson.source_root(), '--keyword=S:1' ] + args: [ '--directory=' + meson.current_source_dir(), '--keyword=S:1' ] ) \ No newline at end of file From 87017faf86e6fcc51ddfcb51022ce2bfa91bdd0c Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 11:21:01 +0200 Subject: [PATCH 06/29] fix: use build options to configure paths --- howdy/src/meson.build | 55 ++++++++++++++++++++++++++------------- howdy/src/meson.options | 10 +++++-- howdy/src/pam/meson.build | 5 +++- 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/howdy/src/meson.build b/howdy/src/meson.build index bb127a53..f99137a9 100644 --- a/howdy/src/meson.build +++ b/howdy/src/meson.build @@ -1,15 +1,15 @@ -project('howdy', 'cpp', license: 'MIT', license_files: '../LICENSE', version: 'beta', meson_version: '>= 1.1.0') +project('howdy', 'cpp', license: 'MIT', license_files: '../../LICENSE', version: 'beta', meson_version: '>= 1.1.0') py = import('python').find_installation() py.dependency() -confdir = join_paths(get_option('sysconfdir'), 'howdy') -dlibdatadir = join_paths(confdir, 'dlib-data') -usermodelsdir = join_paths(confdir, 'models') -logpath = '/var/log/howdy' +confdir = get_option('config_dir') != '' ? get_option('config_dir') : join_paths(get_option('sysconfdir'), 'howdy') +dlibdatadir = get_option('dlib_data_dir') != '' ? get_option('dlib_data_dir') : join_paths(confdir, 'dlib-data') +usermodelsdir = get_option('user_models_dir') != '' ? get_option('user_models_dir') : join_paths(confdir, 'models') +logpath = get_option('log_path') py_conf = configuration_data({ - 'config_dir': confdir, + 'config_dir': confdir, 'dlib_data_dir': dlibdatadir, 'user_models_dir': usermodelsdir, 'log_path': logpath, @@ -48,18 +48,40 @@ py_sources = [ ] # Include PAM module -compare_script_path = join_paths(py.get_install_dir(), 'howdy', 'compare.py') +if get_option('install_in_site_packages') + pysourcesinstalldir = join_paths(py.get_install_dir(), 'howdy') +else + pysourcesinstalldir = get_option('py_sources_dir') != '' ? get_option('py_sources_dir') : join_paths(get_option('prefix'), get_option('libdir'), 'howdy') +endif + +compare_script_path = join_paths(pysourcesinstalldir, 'compare.py') subdir('pam') -py.install_sources( - py_sources, - subdir: 'howdy', - preserve_path: true, -) +if get_option('install_in_site_packages') + py.install_sources( + py_sources, + subdir: 'howdy', + preserve_path: true, + install_mode: 'r--r--r--', + install_tag: 'py_sources', + ) +else + install_data( + py_sources, + preserve_path: true, + install_dir: pysourcesinstalldir, + install_mode: 'r--r--r--', + install_tag: 'py_sources', + ) +endif install_data('logo.png', install_tag: 'meta') -install_data('config.ini', install_dir: confdir, install_mode: 'rwxr--r--', install_tag: 'config') +fs = import('fs') +config_path = join_paths(confdir, 'config.ini') +if not fs.exists(config_path) + install_data('config.ini', install_dir: confdir, install_mode: 'rwxr--r--', install_tag: 'config') +endif install_data('dlib-data/install.sh', install_dir: dlibdatadir, install_mode: 'rwxr--r--') @@ -109,21 +131,18 @@ install_man('../howdy.1') # endif -py_path = py.get_install_dir() -cli_path = join_paths(py_path, 'howdy', 'cli.py') - +cli_path = join_paths(pysourcesinstalldir, 'cli.py') conf_data = configuration_data({ 'script_path': cli_path }) bin_name = 'howdy' - bin = configure_file( input: 'bin/howdy.in', output: bin_name, configuration: conf_data ) - install_data( bin, install_mode: 'rwxr-xr-x', install_dir: get_option('bindir'), + install_tag: 'bin', ) \ No newline at end of file diff --git a/howdy/src/meson.options b/howdy/src/meson.options index 05860542..bd4318a7 100644 --- a/howdy/src/meson.options +++ b/howdy/src/meson.options @@ -1,2 +1,8 @@ -option('pam_dir', type: 'string', value: '/lib/security', description: 'Set the pam_howdy destination directory') -#option('fetch_dlib_data', type: 'boolean', value: false, description: 'Download dlib data files') \ No newline at end of file +option('pam_dir', type: 'string', value: '', description: 'Set the pam_howdy destination directory') +#option('fetch_dlib_data', type: 'boolean', value: false, description: 'Download dlib data files') +option('config_dir', type: 'string', value: '', description: 'Set the howdy config directory') +option('dlib_data_dir', type: 'string', value: '', description: 'Set the dlib data directory') +option('user_models_dir', type: 'string', value: '', description: 'Set the user models directory') +option('log_path', type: 'string', value: '/var/log/howdy', description: 'Set the log file path') +option('install_in_site_packages', type: 'boolean', value: false, description: 'Install howdy python files in site packages') +option('py_sources_dir', type: 'string', value: '', description: 'Set the python sources directory') \ No newline at end of file diff --git a/howdy/src/pam/meson.build b/howdy/src/pam/meson.build index 3cb01613..39959dc6 100644 --- a/howdy/src/pam/meson.build +++ b/howdy/src/pam/meson.build @@ -16,6 +16,8 @@ paths_h = configure_file( install_dir: get_option('pam_dir') ) +pamdir = get_option('pam_dir') != '' ? get_option('pam_dir') : join_paths(get_option('libdir'), 'security') + shared_library( 'pam_howdy', 'main.cc', @@ -30,6 +32,7 @@ shared_library( paths_h, ], install: true, - install_dir: get_option('pam_dir'), + install_dir: pamdir, + install_tag: 'pam_module', name_prefix: '' ) From c12dce0956b941ab151c8f122d350ad075a9a76e Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 11:57:13 +0200 Subject: [PATCH 07/29] refactor: add path factory to not recreate paths --- howdy/src/cli/add.py | 13 +++++++------ howdy/src/cli/clear.py | 5 +++-- howdy/src/cli/config.py | 3 ++- howdy/src/cli/disable.py | 3 ++- howdy/src/cli/list.py | 3 ++- howdy/src/cli/remove.py | 3 ++- howdy/src/cli/set.py | 3 ++- howdy/src/cli/snap.py | 3 ++- howdy/src/cli/test.py | 14 ++++++-------- howdy/src/compare.py | 13 +++++++------ howdy/src/paths_factory.py | 36 ++++++++++++++++++++++++++++++++++++ howdy/src/snapshot.py | 11 +++++------ 12 files changed, 76 insertions(+), 34 deletions(-) create mode 100644 howdy/src/paths_factory.py diff --git a/howdy/src/cli/add.py b/howdy/src/cli/add.py index 19e8b8cd..0c64f2c8 100644 --- a/howdy/src/cli/add.py +++ b/howdy/src/cli/add.py @@ -9,6 +9,7 @@ import builtins import numpy as np import paths +import paths_factory from recorders.video_capture import VideoCapture from i18n import _ @@ -28,7 +29,7 @@ import cv2 # Test if at lest 1 of the data files is there and abort if it's not -if not os.path.isfile(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat"): +if not os.path.isfile(paths_factory.shape_predictor_5_face_landmarks_path()): print(_("Data files have not been downloaded, please run the following commands:")) print("\n\tcd " + paths.dlib_data_dir) print("\tsudo ./install.sh\n") @@ -36,20 +37,20 @@ # Read config from disk config = configparser.ConfigParser() -config.read(paths.config_dir / "config.ini") +config.read(paths_factory.config_file_path()) use_cnn = config.getboolean("core", "use_cnn", fallback=False) if use_cnn: - face_detector = dlib.cnn_face_detection_model_v1(paths.dlib_data_dir / "mmod_human_face_detector.dat") + face_detector = dlib.cnn_face_detection_model_v1(str(paths_factory.mmod_human_face_detector_path())) else: face_detector = dlib.get_frontal_face_detector() -pose_predictor = dlib.shape_predictor(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat") -face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir / "dlib_face_recognition_resnet_model_v1.dat") +pose_predictor = dlib.shape_predictor(str(paths_factory.shape_predictor_5_face_landmarks_path())) +face_encoder = dlib.face_recognition_model_v1(str(paths_factory.dlib_face_recognition_resnet_model_v1_path())) user = builtins.howdy_user # The permanent file to store the encoded model in -enc_file = paths.user_models_dir / f"{user}.dat" +enc_file = paths_factory.user_model_path(user) # Known encodings encodings = [] diff --git a/howdy/src/cli/clear.py b/howdy/src/cli/clear.py index c5a48b63..2949eef3 100644 --- a/howdy/src/cli/clear.py +++ b/howdy/src/cli/clear.py @@ -5,6 +5,7 @@ import sys import builtins import paths +import paths_factory from i18n import _ @@ -17,7 +18,7 @@ sys.exit(1) # Check if the user has a models file to delete -if not os.path.isfile(paths.user_models_dir / f"{user}.dat"): +if not os.path.isfile(paths_factory.user_model_path(user)): print(_("{} has no models or they have been cleared already").format(user)) sys.exit(1) @@ -33,5 +34,5 @@ sys.exit(1) # Delete otherwise -os.remove(paths.user_models_dir / f"{user}.dat") +os.remove(paths_factory.user_model_path(user)) print(_("\nModels cleared")) diff --git a/howdy/src/cli/config.py b/howdy/src/cli/config.py index c2980999..34860e83 100644 --- a/howdy/src/cli/config.py +++ b/howdy/src/cli/config.py @@ -4,6 +4,7 @@ import os import subprocess import paths +import paths_factory from i18n import _ @@ -20,4 +21,4 @@ editor = "/etc/alternatives/editor" # Open the editor as a subprocess and fork it -subprocess.call([editor, paths.config_dir / "config.ini"]) +subprocess.call([editor, paths_factory.config_file_path()]) diff --git a/howdy/src/cli/disable.py b/howdy/src/cli/disable.py index ac98ea0c..42b2d3ca 100644 --- a/howdy/src/cli/disable.py +++ b/howdy/src/cli/disable.py @@ -7,11 +7,12 @@ import fileinput import configparser import paths +import paths_factory from i18n import _ # Get the absolute filepath -config_path = paths.config_dir.parent / "config.ini" +config_path = paths_factory.config_file_path() # Read config from disk config = configparser.ConfigParser() diff --git a/howdy/src/cli/list.py b/howdy/src/cli/list.py index 2e5073af..6ed00f31 100644 --- a/howdy/src/cli/list.py +++ b/howdy/src/cli/list.py @@ -7,6 +7,7 @@ import time import builtins import paths +import paths_factory from i18n import _ @@ -19,7 +20,7 @@ sys.exit(1) # Path to the models file -enc_file = paths.user_models_dir / f"{user}.dat" +enc_file = paths_factory.user_model_path(user) # Try to load the models file and abort if the user does not have it yet try: diff --git a/howdy/src/cli/remove.py b/howdy/src/cli/remove.py index d4df673b..799b629c 100644 --- a/howdy/src/cli/remove.py +++ b/howdy/src/cli/remove.py @@ -6,6 +6,7 @@ import json import builtins import paths +import paths_factory from i18n import _ @@ -27,7 +28,7 @@ sys.exit(1) # Path to the models file -enc_file = paths.user_models_dir / f"{user}.dat" +enc_file = paths_factory.user_model_path(user) # Try to load the models file and abort if the user does not have it yet try: diff --git a/howdy/src/cli/set.py b/howdy/src/cli/set.py index a4184b1c..eddad43b 100644 --- a/howdy/src/cli/set.py +++ b/howdy/src/cli/set.py @@ -6,11 +6,12 @@ import builtins import fileinput import paths +import paths_factory from i18n import _ # Get the absolute filepath -config_path = paths.config_dir / "/config.ini" +config_path = paths_factory.config_file_path() # Check if enough arguments have been passed if len(builtins.howdy_args.arguments) < 2: diff --git a/howdy/src/cli/snap.py b/howdy/src/cli/snap.py index e16187e4..ff5dfa41 100644 --- a/howdy/src/cli/snap.py +++ b/howdy/src/cli/snap.py @@ -6,13 +6,14 @@ import datetime import snapshot import paths +import paths_factory from recorders.video_capture import VideoCapture from i18n import _ # Read the config config = configparser.ConfigParser() -config.read(paths.config_dir / "config.ini") +config.read(paths_factory.config_file_path()) # Start video capture video_capture = VideoCapture(config) diff --git a/howdy/src/cli/test.py b/howdy/src/cli/test.py index 68477ed3..b7c02a93 100644 --- a/howdy/src/cli/test.py +++ b/howdy/src/cli/test.py @@ -11,16 +11,14 @@ import cv2 import numpy as np import paths +import paths_factory from i18n import _ from recorders.video_capture import VideoCapture -# The absolute path to the config directory -path = "/etc/howdy" - # Read config from disk config = configparser.ConfigParser() -config.read(paths.config_dir / "config.ini") +config.read(paths_factory.config_file_path()) if config.get("video", "recording_plugin", fallback="opencv") != "opencv": print(_("Howdy has been configured to use a recorder which doesn't support the test command yet, aborting")) @@ -60,20 +58,20 @@ def print_text(line_number, text): if use_cnn: face_detector = dlib.cnn_face_detection_model_v1( - paths.dlib_data_dir / "mmod_human_face_detector.dat" + paths_factory.mmod_human_face_detector_path() ) else: face_detector = dlib.get_frontal_face_detector() -pose_predictor = dlib.shape_predictor(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat") -face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir / "dlib_face_recognition_resnet_model_v1.dat") +pose_predictor = dlib.shape_predictor(paths_factory.shape_predictor_5_face_landmarks_path()) +face_encoder = dlib.face_recognition_model_v1(paths_factory.dlib_face_recognition_resnet_model_v1_path()) encodings = [] models = None try: user = builtins.howdy_user - models = json.load(open(paths.user_models_dir / f"{user}.dat")) + models = json.load(open(paths_factory.user_model_path(user))) for model in models: encodings += model["data"] diff --git a/howdy/src/compare.py b/howdy/src/compare.py index 48d3e9f4..0878ed8a 100644 --- a/howdy/src/compare.py +++ b/howdy/src/compare.py @@ -24,6 +24,7 @@ import numpy as np import _thread as thread import paths +import paths_factory # Allow imports from the local howdy folder sys.path.append('/lib/security/howdy') @@ -49,7 +50,7 @@ def init_detector(lock): global face_detector, pose_predictor, face_encoder # Test if at lest 1 of the data files is there and abort if it's not - if not os.path.isfile(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat"): + if not os.path.isfile(str(paths_factory.shape_predictor_5_face_landmarks_path())): print(_("Data files have not been downloaded, please run the following commands:")) print("\n\tcd " + paths.dlib_data_dir) print("\tsudo ./install.sh\n") @@ -58,13 +59,13 @@ def init_detector(lock): # Use the CNN detector if enabled if use_cnn: - face_detector = dlib.cnn_face_detection_model_v1(paths.dlib_data_dir / "mmod_human_face_detector.dat") + face_detector = dlib.cnn_face_detection_model_v1(str(paths_factory.mmod_human_face_detector_path())) else: face_detector = dlib.get_frontal_face_detector() # Start the others regardless - pose_predictor = dlib.shape_predictor(paths.dlib_data_dir / "shape_predictor_5_face_landmarks.dat") - face_encoder = dlib.face_recognition_model_v1(paths.dlib_data_dir / "dlib_face_recognition_resnet_model_v1.dat") + pose_predictor = dlib.shape_predictor(str(paths_factory.shape_predictor_5_face_landmarks_path())) + face_encoder = dlib.face_recognition_model_v1(str(paths_factory.dlib_face_recognition_resnet_model_v1_path())) # Note the time it took to initialize detectors timings["ll"] = time.time() - timings["ll"] @@ -127,7 +128,7 @@ def send_to_ui(type, message): # Try to load the face model from the models folder try: - models = json.load(open(paths.user_models_dir / f"{user}.dat")) + models = json.load(open(paths_factory.user_model_path(user))) for model in models: encodings += model["data"] @@ -140,7 +141,7 @@ def send_to_ui(type, message): # Read config from disk config = configparser.ConfigParser() -config.read(paths.config_dir / "config.ini") +config.read(paths_factory.config_file_path()) # Get all config values needed use_cnn = config.getboolean("core", "use_cnn", fallback=False) diff --git a/howdy/src/paths_factory.py b/howdy/src/paths_factory.py new file mode 100644 index 00000000..7a0d1bac --- /dev/null +++ b/howdy/src/paths_factory.py @@ -0,0 +1,36 @@ +from pathlib import PurePath +import paths + +models = [ + "shape_predictor_5_face_landmarks.dat", + "mmod_human_face_detector.dat", + "dlib_face_recognition_resnet_model_v1.dat", +] + + +def shape_predictor_5_face_landmarks_path() -> PurePath: + return paths.dlib_data_dir / models[0] + + +def mmod_human_face_detector_path() -> PurePath: + return paths.dlib_data_dir / models[1] + + +def dlib_face_recognition_resnet_model_v1_path() -> PurePath: + return paths.dlib_data_dir / models[2] + + +def user_model_path(user: str) -> PurePath: + return paths.user_models_dir / f"{user}.dat" + + +def config_file_path() -> PurePath: + return paths.config_dir / "config.ini" + + +def snapshots_dir_path() -> PurePath: + return paths.log_path / "snapshots" + + +def snapshot_path(snapshot: str) -> PurePath: + return snapshots_dir_path() / snapshot diff --git a/howdy/src/snapshot.py b/howdy/src/snapshot.py index 34e34289..53dd7fac 100644 --- a/howdy/src/snapshot.py +++ b/howdy/src/snapshot.py @@ -5,6 +5,7 @@ import os import datetime import numpy as np +import paths_factory def generate(frames, text_lines): @@ -50,15 +51,13 @@ def generate(frames, text_lines): line_number += 1 # Made sure a snapshot folder exist - if not os.path.exists(paths.log_path): - os.makedirs(paths.log_path) - if not os.path.exists(paths.log_path / "snapshots"): - os.makedirs(paths.log_path / "snapshots") + if not os.path.exists(paths_factory.snapshots_dir_path()): + os.makedirs(paths_factory.snapshots_dir_path()) # Generate a filename based on the current time filename = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%S.jpg") # Write the image to that file - cv2.imwrite(paths.log_path / "snapshots" / filename, snap) + cv2.imwrite(paths_factory.snapshot_path(filename), snap) # Return the saved file location - return paths.log_path / "/snapshots/" / filename + return paths_factory.snapshot_path(filename) From a30fbd3d42a0b66de130d305fa2fc24f763434d3 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 12:02:00 +0200 Subject: [PATCH 08/29] build: remove license_files --- howdy/src/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/howdy/src/meson.build b/howdy/src/meson.build index f99137a9..153d4bea 100644 --- a/howdy/src/meson.build +++ b/howdy/src/meson.build @@ -1,4 +1,4 @@ -project('howdy', 'cpp', license: 'MIT', license_files: '../../LICENSE', version: 'beta', meson_version: '>= 1.1.0') +project('howdy', 'cpp', license: 'MIT', version: 'beta', meson_version: '>= 1.1.0') py = import('python').find_installation() py.dependency() From 5d3e69d3adce44c725a05f366370889ca0087f46 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 14:14:02 +0200 Subject: [PATCH 09/29] build: add meson_options.txt for compatibility --- howdy/src/meson_options.txt | 1 + 1 file changed, 1 insertion(+) create mode 120000 howdy/src/meson_options.txt diff --git a/howdy/src/meson_options.txt b/howdy/src/meson_options.txt new file mode 120000 index 00000000..7b28df27 --- /dev/null +++ b/howdy/src/meson_options.txt @@ -0,0 +1 @@ +meson.options \ No newline at end of file From c97164f9f50edf3825464d5e1e1baccc5e5e1895 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 14:16:47 +0200 Subject: [PATCH 10/29] ci: use latest meson version --- .github/workflows/check.yml | 5 ++++- howdy/src/meson.build | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index ba897b79..5b319d67 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -8,10 +8,13 @@ jobs: - name: Install required libraries run: > sudo apt-get update && sudo apt-get install -y - python3 python3-pip python3-setuptools python3-wheel ninja-build meson + python3 python3-pip python3-setuptools python3-wheel cmake make build-essential clang-tidy libpam0g-dev libinih-dev libevdev-dev python3-dev libopencv-dev + + - name: Install meson + run: sudo python3 -m pip install meson ninja - uses: actions/checkout@v2 diff --git a/howdy/src/meson.build b/howdy/src/meson.build index 153d4bea..36ae832c 100644 --- a/howdy/src/meson.build +++ b/howdy/src/meson.build @@ -1,4 +1,4 @@ -project('howdy', 'cpp', license: 'MIT', version: 'beta', meson_version: '>= 1.1.0') +project('howdy', 'cpp', license: 'MIT', version: 'beta', meson_version: '>= 0.64.0') py = import('python').find_installation() py.dependency() From 73adb06f453d33b35b3d7e136ce94648c6590b80 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 14:17:19 +0200 Subject: [PATCH 11/29] build: add paths_factory file --- howdy/src/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/howdy/src/meson.build b/howdy/src/meson.build index 36ae832c..9d2835a5 100644 --- a/howdy/src/meson.build +++ b/howdy/src/meson.build @@ -35,6 +35,7 @@ py_sources = [ 'cli.py', 'compare.py', 'i18n.py', + 'paths_factory.py', 'recorders/__init__.py', 'recorders/ffmpeg_reader.py', 'recorders/pyv4l2_reader.py', @@ -83,9 +84,8 @@ if not fs.exists(config_path) install_data('config.ini', install_dir: confdir, install_mode: 'rwxr--r--', install_tag: 'config') endif -install_data('dlib-data/install.sh', install_dir: dlibdatadir, install_mode: 'rwxr--r--') +install_data('dlib-data/install.sh', 'dlib-data/Readme.md', install_dir: dlibdatadir, install_mode: 'rwxr--r--') -install_data('dlib-data/Readme.md', install_dir: dlibdatadir, install_mode: 'r--r--r--', install_tag: 'docs') install_man('../howdy.1') # if get_option('fetch_dlib_data') From c9e24e37d9df7bb99cee636fb2abe1e56a6ee43e Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 14:17:39 +0200 Subject: [PATCH 12/29] fix: convert paths to string --- howdy/src/cli/test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/howdy/src/cli/test.py b/howdy/src/cli/test.py index b7c02a93..fff2e917 100644 --- a/howdy/src/cli/test.py +++ b/howdy/src/cli/test.py @@ -58,13 +58,13 @@ def print_text(line_number, text): if use_cnn: face_detector = dlib.cnn_face_detection_model_v1( - paths_factory.mmod_human_face_detector_path() + str(paths_factory.mmod_human_face_detector_path()) ) else: face_detector = dlib.get_frontal_face_detector() -pose_predictor = dlib.shape_predictor(paths_factory.shape_predictor_5_face_landmarks_path()) -face_encoder = dlib.face_recognition_model_v1(paths_factory.dlib_face_recognition_resnet_model_v1_path()) +pose_predictor = dlib.shape_predictor(str(paths_factory.shape_predictor_5_face_landmarks_path())) +face_encoder = dlib.face_recognition_model_v1(str(paths_factory.dlib_face_recognition_resnet_model_v1_path())) encodings = [] models = None From 81db5c3bf139f20ceb80abc36abfa19bb750c456 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 14:18:12 +0200 Subject: [PATCH 13/29] fix: use correct shebang --- howdy/src/bin/howdy.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/howdy/src/bin/howdy.in b/howdy/src/bin/howdy.in index e9f49531..1ea89f70 100644 --- a/howdy/src/bin/howdy.in +++ b/howdy/src/bin/howdy.in @@ -1,3 +1,3 @@ -#!/usr/bin/env sh +#!/bin/sh -/usr/bin/env python3 "@script_path@" "$@" \ No newline at end of file +env python3 "@script_path@" "$@" \ No newline at end of file From 61222197c37c36511717795b8b5d32805ea50bca Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 14:32:31 +0200 Subject: [PATCH 14/29] build: add .clang-tidy to root src folder --- howdy/src/.clang-tidy | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 howdy/src/.clang-tidy diff --git a/howdy/src/.clang-tidy b/howdy/src/.clang-tidy new file mode 100644 index 00000000..887f4c31 --- /dev/null +++ b/howdy/src/.clang-tidy @@ -0,0 +1,7 @@ +Checks: 'clang-diagnostic-*,clang-analyser-*,-clang-diagnostic-unused-command-line-argument,google-*,bugprone-*,modernize-*,performance-*,portability-*,readability-*,-bugprone-easily-swappable-*,-readability-magic-numbers,-google-readability-todo' +WarningsAsErrors: 'clang-diagnostic-*,clang-analyser-*,-clang-diagnostic-unused-command-line-argument,google-*,bugprone-*,modernize-*,performance-*,portability-*,readability-*,-bugprone-easily-swappable-*,-readability-magic-numbers,-google-readability-todo' +CheckOptions: + - key: readability-function-cognitive-complexity.Threshold + value: '50' + +# vim:syntax=yaml From 8bc95efda6b837471291d08cec184dc7bbd6306e Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 16:55:26 +0200 Subject: [PATCH 15/29] fix: convert to string when necessary --- howdy/src/cli/add.py | 4 ++-- howdy/src/cli/config.py | 1 - howdy/src/cli/disable.py | 1 - howdy/src/cli/remove.py | 2 +- howdy/src/cli/set.py | 1 - howdy/src/cli/snap.py | 1 - howdy/src/cli/test.py | 1 - howdy/src/compare.py | 4 ---- howdy/src/snapshot.py | 7 +++---- 9 files changed, 6 insertions(+), 16 deletions(-) diff --git a/howdy/src/cli/add.py b/howdy/src/cli/add.py index 0c64f2c8..ab58cc76 100644 --- a/howdy/src/cli/add.py +++ b/howdy/src/cli/add.py @@ -31,7 +31,7 @@ # Test if at lest 1 of the data files is there and abort if it's not if not os.path.isfile(paths_factory.shape_predictor_5_face_landmarks_path()): print(_("Data files have not been downloaded, please run the following commands:")) - print("\n\tcd " + paths.dlib_data_dir) + print("\n\tcd " + str(paths.dlib_data_dir)) print("\tsudo ./install.sh\n") sys.exit(1) @@ -50,7 +50,7 @@ user = builtins.howdy_user # The permanent file to store the encoded model in -enc_file = paths_factory.user_model_path(user) +enc_file = str(paths_factory.user_model_path(user)) # Known encodings encodings = [] diff --git a/howdy/src/cli/config.py b/howdy/src/cli/config.py index 34860e83..fae7b081 100644 --- a/howdy/src/cli/config.py +++ b/howdy/src/cli/config.py @@ -3,7 +3,6 @@ # Import required modules import os import subprocess -import paths import paths_factory from i18n import _ diff --git a/howdy/src/cli/disable.py b/howdy/src/cli/disable.py index 42b2d3ca..dc87e232 100644 --- a/howdy/src/cli/disable.py +++ b/howdy/src/cli/disable.py @@ -6,7 +6,6 @@ import builtins import fileinput import configparser -import paths import paths_factory from i18n import _ diff --git a/howdy/src/cli/remove.py b/howdy/src/cli/remove.py index 799b629c..595dfc67 100644 --- a/howdy/src/cli/remove.py +++ b/howdy/src/cli/remove.py @@ -72,7 +72,7 @@ # Remove the entire file if this encoding is the only one if len(encodings) == 1: - os.remove(paths.user_models_dir / f"{user}.dat") + os.remove(paths_factory.user_model_path(user)) print(_("Removed last model, howdy disabled for user")) else: # A place holder to contain the encodings that will remain diff --git a/howdy/src/cli/set.py b/howdy/src/cli/set.py index eddad43b..ba981c2d 100644 --- a/howdy/src/cli/set.py +++ b/howdy/src/cli/set.py @@ -5,7 +5,6 @@ import os import builtins import fileinput -import paths import paths_factory from i18n import _ diff --git a/howdy/src/cli/snap.py b/howdy/src/cli/snap.py index ff5dfa41..4d0d2cab 100644 --- a/howdy/src/cli/snap.py +++ b/howdy/src/cli/snap.py @@ -5,7 +5,6 @@ import configparser import datetime import snapshot -import paths import paths_factory from recorders.video_capture import VideoCapture diff --git a/howdy/src/cli/test.py b/howdy/src/cli/test.py index fff2e917..934d535c 100644 --- a/howdy/src/cli/test.py +++ b/howdy/src/cli/test.py @@ -10,7 +10,6 @@ import dlib import cv2 import numpy as np -import paths import paths_factory from i18n import _ diff --git a/howdy/src/compare.py b/howdy/src/compare.py index 0878ed8a..c46a1ef3 100644 --- a/howdy/src/compare.py +++ b/howdy/src/compare.py @@ -25,10 +25,6 @@ import _thread as thread import paths import paths_factory - -# Allow imports from the local howdy folder -sys.path.append('/lib/security/howdy') - from recorders.video_capture import VideoCapture from i18n import _ diff --git a/howdy/src/snapshot.py b/howdy/src/snapshot.py index 53dd7fac..c6a7abfb 100644 --- a/howdy/src/snapshot.py +++ b/howdy/src/snapshot.py @@ -15,8 +15,6 @@ def generate(frames, text_lines): if len(frames) == 0: return - # Get the path to the containing folder - core_path = os.path.dirname(os.path.abspath(__file__)) # Get frame dimensions frame_height, frame_width, cc = frames[0].shape # Spread the given frames out horizontally @@ -56,8 +54,9 @@ def generate(frames, text_lines): # Generate a filename based on the current time filename = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%S.jpg") + filepath = str(paths_factory.snapshot_path(filename)) # Write the image to that file - cv2.imwrite(paths_factory.snapshot_path(filename), snap) + cv2.imwrite(filepath, snap) # Return the saved file location - return paths_factory.snapshot_path(filename) + return filepath From d0ca17b1ec33508e5b491bf651c32d6e93c93028 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 16:56:44 +0200 Subject: [PATCH 16/29] refactor: add data dir for logo --- howdy/src/meson.build | 2 ++ howdy/src/meson.options | 1 + howdy/src/paths.py.in | 3 +++ howdy/src/paths_factory.py | 3 +++ howdy/src/snapshot.py | 2 +- 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/howdy/src/meson.build b/howdy/src/meson.build index 9d2835a5..f068fa9a 100644 --- a/howdy/src/meson.build +++ b/howdy/src/meson.build @@ -6,12 +6,14 @@ py.dependency() confdir = get_option('config_dir') != '' ? get_option('config_dir') : join_paths(get_option('sysconfdir'), 'howdy') dlibdatadir = get_option('dlib_data_dir') != '' ? get_option('dlib_data_dir') : join_paths(confdir, 'dlib-data') usermodelsdir = get_option('user_models_dir') != '' ? get_option('user_models_dir') : join_paths(confdir, 'models') +datadir = get_option('data_dir') != '' ? get_option('data_dir') : join_paths(get_option('prefix'), get_option('datadir'), 'howdy') logpath = get_option('log_path') py_conf = configuration_data({ 'config_dir': confdir, 'dlib_data_dir': dlibdatadir, 'user_models_dir': usermodelsdir, + 'data_dir': datadir, 'log_path': logpath, }) diff --git a/howdy/src/meson.options b/howdy/src/meson.options index bd4318a7..ef3346fe 100644 --- a/howdy/src/meson.options +++ b/howdy/src/meson.options @@ -3,6 +3,7 @@ option('pam_dir', type: 'string', value: '', description: 'Set the pam_howdy des option('config_dir', type: 'string', value: '', description: 'Set the howdy config directory') option('dlib_data_dir', type: 'string', value: '', description: 'Set the dlib data directory') option('user_models_dir', type: 'string', value: '', description: 'Set the user models directory') +option('data_dir', type: 'string', value: '', description: 'Set the howdy data directory') option('log_path', type: 'string', value: '/var/log/howdy', description: 'Set the log file path') option('install_in_site_packages', type: 'boolean', value: false, description: 'Install howdy python files in site packages') option('py_sources_dir', type: 'string', value: '', description: 'Set the python sources directory') \ No newline at end of file diff --git a/howdy/src/paths.py.in b/howdy/src/paths.py.in index e51d738e..68aca6c9 100644 --- a/howdy/src/paths.py.in +++ b/howdy/src/paths.py.in @@ -11,3 +11,6 @@ user_models_dir = PurePath("@user_models_dir@") # Define path to any howdy logs log_path = PurePath("@log_path@") + +# Define the absolute path to the Howdy data directory +data_dir = PurePath("@data_dir@") \ No newline at end of file diff --git a/howdy/src/paths_factory.py b/howdy/src/paths_factory.py index 7a0d1bac..63a9b4b1 100644 --- a/howdy/src/paths_factory.py +++ b/howdy/src/paths_factory.py @@ -34,3 +34,6 @@ def snapshots_dir_path() -> PurePath: def snapshot_path(snapshot: str) -> PurePath: return snapshots_dir_path() / snapshot + +def logo_path() -> PurePath: + return paths.data_dir / "logo.png" diff --git a/howdy/src/snapshot.py b/howdy/src/snapshot.py index c6a7abfb..9e55ca07 100644 --- a/howdy/src/snapshot.py +++ b/howdy/src/snapshot.py @@ -30,7 +30,7 @@ def generate(frames, text_lines): # Add the Howdy logo if there's space to do so if len(frames) > 1: # Load the logo from file - logo = cv2.imread(core_path + "/logo.png") + logo = cv2.imread(str(paths_factory.logo_path())) # Calculate the position of the logo logo_y = frame_height + 20 logo_x = frame_width * len(frames) - 210 From a92a3aade297309ce24ca2cc2c604673d066abb1 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 17:13:12 +0200 Subject: [PATCH 17/29] feat: add config path to PAM module --- howdy/src/meson.build | 8 ++++++-- howdy/src/pam/main.cc | 2 +- howdy/src/pam/meson.build | 4 +--- howdy/src/pam/paths.hh.in | 3 ++- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/howdy/src/meson.build b/howdy/src/meson.build index f068fa9a..2fc7ec8b 100644 --- a/howdy/src/meson.build +++ b/howdy/src/meson.build @@ -9,6 +9,8 @@ usermodelsdir = get_option('user_models_dir') != '' ? get_option('user_models_di datadir = get_option('data_dir') != '' ? get_option('data_dir') : join_paths(get_option('prefix'), get_option('datadir'), 'howdy') logpath = get_option('log_path') +config_path = join_paths(confdir, 'config.ini') + py_conf = configuration_data({ 'config_dir': confdir, 'dlib_data_dir': dlibdatadir, @@ -57,7 +59,10 @@ else pysourcesinstalldir = get_option('py_sources_dir') != '' ? get_option('py_sources_dir') : join_paths(get_option('prefix'), get_option('libdir'), 'howdy') endif -compare_script_path = join_paths(pysourcesinstalldir, 'compare.py') +pam_module_conf_data = configuration_data({ + 'compare_script_path': join_paths(pysourcesinstalldir, 'compare.py') , + 'config_file_path': config_path, +}) subdir('pam') if get_option('install_in_site_packages') @@ -81,7 +86,6 @@ endif install_data('logo.png', install_tag: 'meta') fs = import('fs') -config_path = join_paths(confdir, 'config.ini') if not fs.exists(config_path) install_data('config.ini', install_dir: confdir, install_mode: 'rwxr--r--', install_tag: 'config') endif diff --git a/howdy/src/pam/main.cc b/howdy/src/pam/main.cc index 2f7a996c..05c54d92 100644 --- a/howdy/src/pam/main.cc +++ b/howdy/src/pam/main.cc @@ -197,7 +197,7 @@ auto check_enabled(const INIReader &config) -> int { */ auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv, bool auth_tok) -> int { - INIReader config("/etc/howdy/config.ini"); + INIReader config(CONFIG_FILE_PATH); openlog("pam_howdy", 0, LOG_AUTHPRIV); // Error out if we could not read the config file diff --git a/howdy/src/pam/meson.build b/howdy/src/pam/meson.build index 39959dc6..fa04f7b5 100644 --- a/howdy/src/pam/meson.build +++ b/howdy/src/pam/meson.build @@ -7,12 +7,10 @@ threads = dependency('threads') subdir('po') # Paths -paths = { 'compare_script_path': compare_script_path } - paths_h = configure_file( input: 'paths.hh.in', output: 'paths.hh', - configuration: paths, + configuration: pam_module_conf_data, install_dir: get_option('pam_dir') ) diff --git a/howdy/src/pam/paths.hh.in b/howdy/src/pam/paths.hh.in index df21928d..055e0d46 100644 --- a/howdy/src/pam/paths.hh.in +++ b/howdy/src/pam/paths.hh.in @@ -1 +1,2 @@ -const auto COMPARE_PROCESS_PATH = "@compare_script_path@"; \ No newline at end of file +const auto COMPARE_PROCESS_PATH = "@compare_script_path@"; +const auto CONFIG_FILE_PATH = "@config_file_path@"; \ No newline at end of file From 37a59a06a75839cf9e88ae81f8a9b8a32d910d00 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 17:15:50 +0200 Subject: [PATCH 18/29] style: change clang tidy configuration --- howdy/src/pam/.clang-tidy | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) mode change 100644 => 120000 howdy/src/pam/.clang-tidy diff --git a/howdy/src/pam/.clang-tidy b/howdy/src/pam/.clang-tidy deleted file mode 100644 index 887f4c31..00000000 --- a/howdy/src/pam/.clang-tidy +++ /dev/null @@ -1,7 +0,0 @@ -Checks: 'clang-diagnostic-*,clang-analyser-*,-clang-diagnostic-unused-command-line-argument,google-*,bugprone-*,modernize-*,performance-*,portability-*,readability-*,-bugprone-easily-swappable-*,-readability-magic-numbers,-google-readability-todo' -WarningsAsErrors: 'clang-diagnostic-*,clang-analyser-*,-clang-diagnostic-unused-command-line-argument,google-*,bugprone-*,modernize-*,performance-*,portability-*,readability-*,-bugprone-easily-swappable-*,-readability-magic-numbers,-google-readability-todo' -CheckOptions: - - key: readability-function-cognitive-complexity.Threshold - value: '50' - -# vim:syntax=yaml diff --git a/howdy/src/pam/.clang-tidy b/howdy/src/pam/.clang-tidy new file mode 120000 index 00000000..878a8ae6 --- /dev/null +++ b/howdy/src/pam/.clang-tidy @@ -0,0 +1 @@ +../.clang-tidy \ No newline at end of file From 54ba84e033dee7748000dfa40c45855e9d80094c Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 17:22:44 +0200 Subject: [PATCH 19/29] fix: convert to string --- howdy/src/compare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/howdy/src/compare.py b/howdy/src/compare.py index c46a1ef3..747414ae 100644 --- a/howdy/src/compare.py +++ b/howdy/src/compare.py @@ -48,7 +48,7 @@ def init_detector(lock): # Test if at lest 1 of the data files is there and abort if it's not if not os.path.isfile(str(paths_factory.shape_predictor_5_face_landmarks_path())): print(_("Data files have not been downloaded, please run the following commands:")) - print("\n\tcd " + paths.dlib_data_dir) + print("\n\tcd " + str(paths.dlib_data_dir)) print("\tsudo ./install.sh\n") lock.release() exit(1) From 1f8ee26d3da78c43427615fa850ecf60a1cad178 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 25 Jun 2023 18:45:17 +0200 Subject: [PATCH 20/29] refactor: use only paths_factory --- howdy/src/cli/add.py | 7 +++---- howdy/src/cli/clear.py | 3 +-- howdy/src/cli/list.py | 3 +-- howdy/src/cli/remove.py | 3 +-- howdy/src/compare.py | 3 +-- howdy/src/paths_factory.py | 37 +++++++++++++++++++++++-------------- 6 files changed, 30 insertions(+), 26 deletions(-) diff --git a/howdy/src/cli/add.py b/howdy/src/cli/add.py index ab58cc76..fbfe97d2 100644 --- a/howdy/src/cli/add.py +++ b/howdy/src/cli/add.py @@ -8,7 +8,6 @@ import configparser import builtins import numpy as np -import paths import paths_factory from recorders.video_capture import VideoCapture @@ -31,7 +30,7 @@ # Test if at lest 1 of the data files is there and abort if it's not if not os.path.isfile(paths_factory.shape_predictor_5_face_landmarks_path()): print(_("Data files have not been downloaded, please run the following commands:")) - print("\n\tcd " + str(paths.dlib_data_dir)) + print("\n\tcd " + paths_factory.dlib_data_dir_path()) print("\tsudo ./install.sh\n") sys.exit(1) @@ -55,9 +54,9 @@ encodings = [] # Make the ./models folder if it doesn't already exist -if not os.path.exists(paths.user_models_dir): +if not os.path.exists(paths_factory.user_models_dir_path()): print(_("No face model folder found, creating one")) - os.makedirs(paths.user_models_dir) + os.makedirs(paths_factory.user_models_dir_path()) # To try read a premade encodings file if it exists try: diff --git a/howdy/src/cli/clear.py b/howdy/src/cli/clear.py index 2949eef3..64941ba9 100644 --- a/howdy/src/cli/clear.py +++ b/howdy/src/cli/clear.py @@ -4,7 +4,6 @@ import os import sys import builtins -import paths import paths_factory from i18n import _ @@ -13,7 +12,7 @@ user = builtins.howdy_user # Check if the models folder is there -if not os.path.exists(paths.user_models_dir): +if not os.path.exists(paths_factory.user_models_dir_path()): print(_("No models created yet, can't clear them if they don't exist")) sys.exit(1) diff --git a/howdy/src/cli/list.py b/howdy/src/cli/list.py index 6ed00f31..01628de5 100644 --- a/howdy/src/cli/list.py +++ b/howdy/src/cli/list.py @@ -6,7 +6,6 @@ import json import time import builtins -import paths import paths_factory from i18n import _ @@ -14,7 +13,7 @@ user = builtins.howdy_user # Check if the models file has been created yet -if not os.path.exists(paths.user_models_dir): +if not os.path.exists(paths_factory.user_models_dir_path()): print(_("Face models have not been initialized yet, please run:")) print("\n\tsudo howdy -U " + user + " add\n") sys.exit(1) diff --git a/howdy/src/cli/remove.py b/howdy/src/cli/remove.py index 595dfc67..cec96c77 100644 --- a/howdy/src/cli/remove.py +++ b/howdy/src/cli/remove.py @@ -5,7 +5,6 @@ import os import json import builtins -import paths import paths_factory from i18n import _ @@ -22,7 +21,7 @@ sys.exit(1) # Check if the models file has been created yet -if not os.path.exists(paths.user_models_dir): +if not os.path.exists(paths_factory.user_models_dir_path()): print(_("Face models have not been initialized yet, please run:")) print("\n\thowdy add\n") sys.exit(1) diff --git a/howdy/src/compare.py b/howdy/src/compare.py index 747414ae..ccc4a409 100644 --- a/howdy/src/compare.py +++ b/howdy/src/compare.py @@ -23,7 +23,6 @@ import snapshot import numpy as np import _thread as thread -import paths import paths_factory from recorders.video_capture import VideoCapture from i18n import _ @@ -48,7 +47,7 @@ def init_detector(lock): # Test if at lest 1 of the data files is there and abort if it's not if not os.path.isfile(str(paths_factory.shape_predictor_5_face_landmarks_path())): print(_("Data files have not been downloaded, please run the following commands:")) - print("\n\tcd " + str(paths.dlib_data_dir)) + print("\n\tcd " + paths_factory.dlib_data_dir_path()) print("\tsudo ./install.sh\n") lock.release() exit(1) diff --git a/howdy/src/paths_factory.py b/howdy/src/paths_factory.py index 63a9b4b1..0995243a 100644 --- a/howdy/src/paths_factory.py +++ b/howdy/src/paths_factory.py @@ -8,32 +8,41 @@ ] -def shape_predictor_5_face_landmarks_path() -> PurePath: - return paths.dlib_data_dir / models[0] +def dlib_data_dir_path() -> str: + return str(paths.dlib_data_dir) -def mmod_human_face_detector_path() -> PurePath: - return paths.dlib_data_dir / models[1] +def shape_predictor_5_face_landmarks_path() -> str: + return str(paths.dlib_data_dir / models[0]) -def dlib_face_recognition_resnet_model_v1_path() -> PurePath: - return paths.dlib_data_dir / models[2] +def mmod_human_face_detector_path() -> str: + return str(paths.dlib_data_dir / models[1]) -def user_model_path(user: str) -> PurePath: - return paths.user_models_dir / f"{user}.dat" +def dlib_face_recognition_resnet_model_v1_path() -> str: + return str(paths.dlib_data_dir / models[2]) -def config_file_path() -> PurePath: - return paths.config_dir / "config.ini" +def user_model_path(user: str) -> str: + return str(paths.user_models_dir / f"{user}.dat") + + +def config_file_path() -> str: + return str(paths.config_dir / "config.ini") def snapshots_dir_path() -> PurePath: return paths.log_path / "snapshots" -def snapshot_path(snapshot: str) -> PurePath: - return snapshots_dir_path() / snapshot +def snapshot_path(snapshot: str) -> str: + return str(snapshots_dir_path() / snapshot) + + +def user_models_dir_path() -> str: + return str(paths.user_models_dir) + -def logo_path() -> PurePath: - return paths.data_dir / "logo.png" +def logo_path() -> str: + return str(paths.data_dir / "logo.png") From 241bc6bb045d56e3c02d6f6f5e003c7f30b78509 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Mon, 26 Jun 2023 17:33:46 +0200 Subject: [PATCH 21/29] feat: add meson root script with howdy-gtk Add meson for howdy-gtk and a global meson script for both. --- howdy-gtk/bin/howdy-gtk.in | 3 + howdy-gtk/meson.build | 82 +++++++++++++++++++ howdy-gtk/src/authsticky.py | 5 +- howdy-gtk/src/onboarding.py | 21 ++--- howdy-gtk/src/paths.py.in | 13 +++ howdy-gtk/src/paths_factory.py | 32 ++++++++ howdy-gtk/src/tab_video.py | 3 +- howdy-gtk/src/window.py | 7 +- howdy/meson.build | 1 + howdy/src/compare.py | 2 +- howdy/src/meson.build | 18 +--- howdy/src/paths_factory.py | 4 +- meson.build | 21 +++++ howdy/src/meson.options => meson.options | 0 .../meson_options.txt => meson_options.txt | 0 15 files changed, 171 insertions(+), 41 deletions(-) create mode 100644 howdy-gtk/bin/howdy-gtk.in create mode 100644 howdy-gtk/meson.build create mode 100644 howdy-gtk/src/paths.py.in create mode 100644 howdy-gtk/src/paths_factory.py create mode 100644 howdy/meson.build create mode 100644 meson.build rename howdy/src/meson.options => meson.options (100%) rename howdy/src/meson_options.txt => meson_options.txt (100%) diff --git a/howdy-gtk/bin/howdy-gtk.in b/howdy-gtk/bin/howdy-gtk.in new file mode 100644 index 00000000..1ea89f70 --- /dev/null +++ b/howdy-gtk/bin/howdy-gtk.in @@ -0,0 +1,3 @@ +#!/bin/sh + +env python3 "@script_path@" "$@" \ No newline at end of file diff --git a/howdy-gtk/meson.build b/howdy-gtk/meson.build new file mode 100644 index 00000000..a5429775 --- /dev/null +++ b/howdy-gtk/meson.build @@ -0,0 +1,82 @@ +if meson.is_subproject() +project('howdy-gtk', license: 'MIT', version: 'beta', meson_version: '>= 0.64.0') +endif + +datadir = get_option('prefix') / get_option('datadir') / 'howdy-gtk' +py_conf = configuration_data(paths_dict) +py_conf.set('data_dir', datadir) + + +py_paths = configure_file( + input: 'src/paths.py.in', + output: 'paths.py', + configuration: py_conf, +) + +sources = files( + 'src/authsticky.py', + 'src/i18n.py', + 'src/init.py', + 'src/onboarding.py', + 'src/paths_factory.py', + 'src/tab_models.py', + 'src/tab_video.py', + 'src/window.py', +) + +py = import('python').find_installation( + modules: ['gi', 'elevate'] +) +py.dependency() + +if get_option('install_in_site_packages') + pysourcesinstalldir = join_paths(py.get_install_dir(), 'howdy-gtk') +else + pysourcesinstalldir = get_option('py_sources_dir') != '' ? get_option('py_sources_dir') : join_paths(get_option('prefix'), get_option('libdir'), 'howdy-gtk') +endif + +if get_option('install_in_site_packages') + py.install_sources( + sources, + py_paths, + subdir: 'howdy-gtk', + install_mode: 'r--r--r--', + install_tag: 'py_sources', + ) +else + install_data( + sources, + py_paths, + install_dir: pysourcesinstalldir, + install_mode: 'r--r--r--', + install_tag: 'py_sources', + ) +endif + +logos = files( + 'src/logo.png', + 'src/logo_about.png', +) +install_data(logos, install_dir: datadir) + +interface_files = files( + 'src/main.glade', + 'src/onboarding.glade', +) +install_data(interface_files, install_dir: datadir) + +cli_path = join_paths(pysourcesinstalldir, 'init.py') +conf_data = configuration_data({ 'script_path': cli_path }) + +bin_name = 'howdy-gtk' +bin = configure_file( + input: 'bin/howdy-gtk.in', + output: bin_name, + configuration: conf_data +) +install_data( + bin, + install_mode: 'rwxr-xr-x', + install_dir: get_option('prefix') / get_option('bindir'), + install_tag: 'bin', +) \ No newline at end of file diff --git a/howdy-gtk/src/authsticky.py b/howdy-gtk/src/authsticky.py index 9d808673..4a8c8a32 100644 --- a/howdy-gtk/src/authsticky.py +++ b/howdy-gtk/src/authsticky.py @@ -3,6 +3,7 @@ import gi import signal import sys +import paths_factory import os from i18n import _ @@ -32,9 +33,7 @@ def __init__(self): gtk.Window.__init__(self) # Get the absolute or relative path to the logo file - logo_path = "/usr/lib/howdy-gtk/logo.png" - if not os.access(logo_path, os.R_OK): - logo_path = "./logo.png" + logo_path = paths_factory.logo_path() # Create image and calculate scale size based on image size self.logo_surface = cairo.ImageSurface.create_from_png(logo_path) diff --git a/howdy-gtk/src/onboarding.py b/howdy-gtk/src/onboarding.py index a9414935..e6a4aa7b 100644 --- a/howdy-gtk/src/onboarding.py +++ b/howdy-gtk/src/onboarding.py @@ -3,6 +3,7 @@ import re import time import subprocess +import paths_factory from i18n import _ @@ -22,7 +23,7 @@ def __init__(self): self.connect("delete_event", self.exit) self.builder = gtk.Builder() - self.builder.add_from_file("./onboarding.glade") + self.builder.add_from_file(paths_factory.onboarding_wireframe_path()) self.builder.connect_signals(self) self.window = self.builder.get_object("onboardingwindow") @@ -67,29 +68,17 @@ def go_next_slide(self, button=None): self.execute_slide6() def execute_slide1(self): - conf_path = "/etc/howdy" - self.downloadoutputlabel = self.builder.get_object("downloadoutputlabel") eventbox = self.builder.get_object("downloadeventbox") eventbox.modify_bg(gtk.StateType.NORMAL, gdk.Color(red=0, green=0, blue=0)) - for lib_site in ("/lib", "/usr/lib", "/lib64", "/usr/lib64"): - if os.path.exists(lib_site + "/security/howdy/"): - break - else: - lib_site = None - - if lib_site is None: - self.downloadoutputlabel.set_text(_("Unable to find Howdy's installation location")) - return - - - if os.path.exists(conf_path + "/dlib-data/shape_predictor_5_face_landmarks.dat"): + # TODO: Better way to do this? + if os.path.exists(paths_factory.dlib_data_dir_path() / "shape_predictor_5_face_landmarks.dat"): self.downloadoutputlabel.set_text(_("Datafiles have already been downloaded!\nClick Next to continue")) self.enable_next() return - self.proc = subprocess.Popen("./install.sh", stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, cwd=conf_path + "/howdy/dlib-data") + self.proc = subprocess.Popen("./install.sh", stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, cwd=paths_factory.dlib_data_dir_path()) self.download_lines = [] self.read_download_line() diff --git a/howdy-gtk/src/paths.py.in b/howdy-gtk/src/paths.py.in new file mode 100644 index 00000000..91f2bf5c --- /dev/null +++ b/howdy-gtk/src/paths.py.in @@ -0,0 +1,13 @@ +from pathlib import PurePath + +# Define the absolute path to the config directory +config_dir = PurePath("@config_dir@") + +# Define the absolute path to the DLib models data directory +dlib_data_dir = PurePath("@dlib_data_dir@") + +# Define the absolute path to the Howdy user models directory +user_models_dir = PurePath("@user_models_dir@") + +# Define the absolute path to the Howdy data directory +data_dir = PurePath("@data_dir@") \ No newline at end of file diff --git a/howdy-gtk/src/paths_factory.py b/howdy-gtk/src/paths_factory.py new file mode 100644 index 00000000..b02a9000 --- /dev/null +++ b/howdy-gtk/src/paths_factory.py @@ -0,0 +1,32 @@ +from pathlib import PurePath +import paths + + +def config_file_path() -> str: + """Return the path to the config file""" + return str(paths.config_dir / "config.ini") + + +def user_models_dir_path() -> PurePath: + """Return the path to the user models directory""" + return paths.user_models_dir + + +def logo_path() -> str: + """Return the path to the logo file""" + return str(paths.data_dir / "logo.png") + + +def onboarding_wireframe_path() -> str: + """Return the path to the onboarding wireframe file""" + return str(paths.data_dir / "onboarding.glade") + + +def main_window_wireframe_path() -> str: + """Return the path to the main window wireframe file""" + return str(paths.data_dir / "main.glade") + + +def dlib_data_dir_path() -> PurePath: + """Return the path to the dlib data directory""" + return paths.dlib_data_dir diff --git a/howdy-gtk/src/tab_video.py b/howdy-gtk/src/tab_video.py index 2a8617a7..4f151b2c 100644 --- a/howdy-gtk/src/tab_video.py +++ b/howdy-gtk/src/tab_video.py @@ -1,6 +1,7 @@ import configparser from i18n import _ +import paths_factory from gi.repository import Gtk as gtk from gi.repository import Gdk as gdk @@ -17,7 +18,7 @@ def on_page_switch(self, notebook, page, page_num): try: self.config = configparser.ConfigParser() - self.config.read("/etc/howdy/config.ini") + self.config.read(paths_factory.config_file_path()) except Exception: print(_("Can't open camera")) diff --git a/howdy-gtk/src/window.py b/howdy-gtk/src/window.py index 737b7a45..f43e35de 100644 --- a/howdy-gtk/src/window.py +++ b/howdy-gtk/src/window.py @@ -7,6 +7,7 @@ import subprocess from i18n import _ +import paths_factory # Make sure we have the libs we need gi.require_version("Gtk", "3.0") @@ -26,7 +27,7 @@ def __init__(self): self.connect("delete_event", self.exit) self.builder = gtk.Builder() - self.builder.add_from_file("./main.glade") + self.builder.add_from_file(paths_factory.main_window_wireframe_path()) self.builder.connect_signals(self) self.window = self.builder.get_object("mainwindow") @@ -49,7 +50,7 @@ def __init__(self): # Add the treeview self.modellistbox.add(self.treeview) - filelist = os.listdir("/etc/howdy/models") + filelist = os.listdir(paths_factory.user_models_dir_path()) self.active_user = "" self.userlist.items = 0 @@ -120,7 +121,7 @@ def exit(self, widget, context): elevate.elevate() # If no models have been created yet or when it is forced, start the onboarding -if "--force-onboarding" in sys.argv or not os.path.exists("/etc/howdy/models"): +if "--force-onboarding" in sys.argv or not os.path.exists(paths_factory.user_models_dir_path()): import onboarding onboarding.OnboardingWindow() diff --git a/howdy/meson.build b/howdy/meson.build new file mode 100644 index 00000000..7edf76f7 --- /dev/null +++ b/howdy/meson.build @@ -0,0 +1 @@ +subdir('src') \ No newline at end of file diff --git a/howdy/src/compare.py b/howdy/src/compare.py index ccc4a409..00d03481 100644 --- a/howdy/src/compare.py +++ b/howdy/src/compare.py @@ -154,7 +154,7 @@ def send_to_ui(type, message): # Start the auth ui, register it to be always be closed on exit try: - gtk_proc = subprocess.Popen(["../howdy-gtk/src/init.py", "--start-auth-ui"], stdin=subprocess.PIPE, stdout=gtk_pipe, stderr=gtk_pipe) + gtk_proc = subprocess.Popen(["howdy-gtk", "--start-auth-ui"], stdin=subprocess.PIPE, stdout=gtk_pipe, stderr=gtk_pipe) atexit.register(exit) except FileNotFoundError: pass diff --git a/howdy/src/meson.build b/howdy/src/meson.build index 2fc7ec8b..2f4fbc77 100644 --- a/howdy/src/meson.build +++ b/howdy/src/meson.build @@ -1,23 +1,11 @@ +if meson.is_subproject() project('howdy', 'cpp', license: 'MIT', version: 'beta', meson_version: '>= 0.64.0') +endif py = import('python').find_installation() py.dependency() -confdir = get_option('config_dir') != '' ? get_option('config_dir') : join_paths(get_option('sysconfdir'), 'howdy') -dlibdatadir = get_option('dlib_data_dir') != '' ? get_option('dlib_data_dir') : join_paths(confdir, 'dlib-data') -usermodelsdir = get_option('user_models_dir') != '' ? get_option('user_models_dir') : join_paths(confdir, 'models') -datadir = get_option('data_dir') != '' ? get_option('data_dir') : join_paths(get_option('prefix'), get_option('datadir'), 'howdy') -logpath = get_option('log_path') - -config_path = join_paths(confdir, 'config.ini') - -py_conf = configuration_data({ - 'config_dir': confdir, - 'dlib_data_dir': dlibdatadir, - 'user_models_dir': usermodelsdir, - 'data_dir': datadir, - 'log_path': logpath, -}) +py_conf = configuration_data(paths_dict) py_paths = configure_file( input: 'paths.py.in', diff --git a/howdy/src/paths_factory.py b/howdy/src/paths_factory.py index 0995243a..5716ca5e 100644 --- a/howdy/src/paths_factory.py +++ b/howdy/src/paths_factory.py @@ -40,8 +40,8 @@ def snapshot_path(snapshot: str) -> str: return str(snapshots_dir_path() / snapshot) -def user_models_dir_path() -> str: - return str(paths.user_models_dir) +def user_models_dir_path() -> PurePath: + return paths.user_models_dir def logo_path() -> str: diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..4b92a193 --- /dev/null +++ b/meson.build @@ -0,0 +1,21 @@ +project('howdy', 'cpp', license: 'MIT', version: 'beta', meson_version: '>= 0.64.0') + +confdir = get_option('config_dir') != '' ? get_option('config_dir') : join_paths(get_option('sysconfdir'), 'howdy') +dlibdatadir = get_option('dlib_data_dir') != '' ? get_option('dlib_data_dir') : join_paths(confdir, 'dlib-data') +usermodelsdir = get_option('user_models_dir') != '' ? get_option('user_models_dir') : join_paths(confdir, 'models') +datadir = get_option('data_dir') != '' ? get_option('data_dir') : join_paths(get_option('prefix'), get_option('datadir'), 'howdy') +logpath = get_option('log_path') + +config_path = join_paths(confdir, 'config.ini') + +paths_dict = { + 'config_dir': confdir, + 'dlib_data_dir': dlibdatadir, + 'user_models_dir': usermodelsdir, + 'data_dir': datadir, + 'log_path': logpath, +} + +# We need to keep this order beause howdy-gtk defines the gtk script path +subdir('howdy-gtk') +subdir('howdy') \ No newline at end of file diff --git a/howdy/src/meson.options b/meson.options similarity index 100% rename from howdy/src/meson.options rename to meson.options diff --git a/howdy/src/meson_options.txt b/meson_options.txt similarity index 100% rename from howdy/src/meson_options.txt rename to meson_options.txt From ef2187aa8ea5b8f8cd1e4b5c8285293013404500 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Mon, 26 Jun 2023 17:38:31 +0200 Subject: [PATCH 22/29] ci: fix workflow --- howdy/src/.clang-tidy => .clang-tidy | 0 .github/workflows/check.yml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename howdy/src/.clang-tidy => .clang-tidy (100%) diff --git a/howdy/src/.clang-tidy b/.clang-tidy similarity index 100% rename from howdy/src/.clang-tidy rename to .clang-tidy diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 5b319d67..de4ef84d 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -20,7 +20,7 @@ jobs: - name: Build run: | - meson setup build howdy/src + meson setup -C build ninja -C build - name: Check source code From 9b20bc25448b974ca822946fad82854590bb7986 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Mon, 26 Jun 2023 17:40:19 +0200 Subject: [PATCH 23/29] ci: fix argument --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index de4ef84d..994880d2 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -20,7 +20,7 @@ jobs: - name: Build run: | - meson setup -C build + meson setup build ninja -C build - name: Check source code From 19f5b8d4d62a5fab0e15875318cec86a36748500 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Tue, 27 Jun 2023 21:51:39 +0200 Subject: [PATCH 24/29] build: remove mandatory modules --- howdy-gtk/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/howdy-gtk/meson.build b/howdy-gtk/meson.build index a5429775..26b18b54 100644 --- a/howdy-gtk/meson.build +++ b/howdy-gtk/meson.build @@ -25,7 +25,7 @@ sources = files( ) py = import('python').find_installation( - modules: ['gi', 'elevate'] + # modules: ['gi', 'elevate'] ) py.dependency() From a74ee5ae7c2148636fdb953dc438ac9074ef6285 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Sun, 2 Jul 2023 14:36:34 +0200 Subject: [PATCH 25/29] build: move datadir variable --- howdy/src/meson.build | 2 ++ meson.build | 4 +--- meson.options | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/howdy/src/meson.build b/howdy/src/meson.build index 2f4fbc77..cfb6cc5a 100644 --- a/howdy/src/meson.build +++ b/howdy/src/meson.build @@ -5,7 +5,9 @@ endif py = import('python').find_installation() py.dependency() +datadir = get_option('prefix') / get_option('datadir') / 'howdy' py_conf = configuration_data(paths_dict) +py_conf.set('data_dir', datadir) py_paths = configure_file( input: 'paths.py.in', diff --git a/meson.build b/meson.build index 4b92a193..d6cffff6 100644 --- a/meson.build +++ b/meson.build @@ -1,9 +1,8 @@ project('howdy', 'cpp', license: 'MIT', version: 'beta', meson_version: '>= 0.64.0') -confdir = get_option('config_dir') != '' ? get_option('config_dir') : join_paths(get_option('sysconfdir'), 'howdy') +confdir = get_option('config_dir') != '' ? get_option('config_dir') : join_paths(get_option('prefix'), get_option('sysconfdir'), 'howdy') dlibdatadir = get_option('dlib_data_dir') != '' ? get_option('dlib_data_dir') : join_paths(confdir, 'dlib-data') usermodelsdir = get_option('user_models_dir') != '' ? get_option('user_models_dir') : join_paths(confdir, 'models') -datadir = get_option('data_dir') != '' ? get_option('data_dir') : join_paths(get_option('prefix'), get_option('datadir'), 'howdy') logpath = get_option('log_path') config_path = join_paths(confdir, 'config.ini') @@ -12,7 +11,6 @@ paths_dict = { 'config_dir': confdir, 'dlib_data_dir': dlibdatadir, 'user_models_dir': usermodelsdir, - 'data_dir': datadir, 'log_path': logpath, } diff --git a/meson.options b/meson.options index ef3346fe..bd4318a7 100644 --- a/meson.options +++ b/meson.options @@ -3,7 +3,6 @@ option('pam_dir', type: 'string', value: '', description: 'Set the pam_howdy des option('config_dir', type: 'string', value: '', description: 'Set the howdy config directory') option('dlib_data_dir', type: 'string', value: '', description: 'Set the dlib data directory') option('user_models_dir', type: 'string', value: '', description: 'Set the user models directory') -option('data_dir', type: 'string', value: '', description: 'Set the howdy data directory') option('log_path', type: 'string', value: '/var/log/howdy', description: 'Set the log file path') option('install_in_site_packages', type: 'boolean', value: false, description: 'Install howdy python files in site packages') option('py_sources_dir', type: 'string', value: '', description: 'Set the python sources directory') \ No newline at end of file From 5133d05141bddcb4bd97b082dbaded70e1fdd331 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Tue, 12 Sep 2023 20:02:22 +0200 Subject: [PATCH 26/29] refactor: rename variable --- howdy/src/pam/main.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/howdy/src/pam/main.cc b/howdy/src/pam/main.cc index a8843c57..d1b8e347 100644 --- a/howdy/src/pam/main.cc +++ b/howdy/src/pam/main.cc @@ -187,8 +187,8 @@ auto check_enabled(const INIReader &config, const char* username) -> int { // pre-check if this user has face model file auto model_path = std::string(USER_MODELS_DIR) + "/" + username + ".dat"; - struct stat s_; - if (stat(model_path.c_str(), &s_) != 0) { + struct stat stat_; + if (stat(model_path.c_str(), &stat_) != 0) { return PAM_AUTHINFO_UNAVAIL; } @@ -201,11 +201,11 @@ auto check_enabled(const INIReader &config, const char* username) -> int { * @param flags Flags passed on to us by PAM, XORed * @param argc Amount of rules in the PAM config (disregared) * @param argv Options defined in the PAM config - * @param auth_tok True if we should ask for a password too + * @param ask_auth_tok True if we should ask for a password too * @return Returns a PAM return code */ auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv, - bool auth_tok) -> int { + bool ask_auth_tok) -> int { INIReader config(CONFIG_FILE_PATH); openlog("pam_howdy", 0, LOG_AUTHPRIV); @@ -319,7 +319,7 @@ auto identify(pam_handle_t *pamh, int flags, int argc, const char **argv, return std::tuple(pam_res, auth_tok_ptr); }); - auto ask_pass = auth_tok && workaround != Workaround::Off; + auto ask_pass = ask_auth_tok && workaround != Workaround::Off; // We ask for the password if the function requires it and if a workaround is // set From 29f218d9dcf79bbad66d89455c8c5c7566dc1f88 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Tue, 12 Sep 2023 22:37:53 +0200 Subject: [PATCH 27/29] build: templatize autocomplete and pam-config --- howdy/src/autocomplete/{howdy => howdy.in} | 4 +++- howdy/src/meson.build | 27 ++++++++++++++++++++++ howdy/src/pam-config/howdy | 6 ----- howdy/src/pam-config/howdy.in | 6 +++++ meson.build | 2 +- meson.options | 3 ++- 6 files changed, 39 insertions(+), 9 deletions(-) rename howdy/src/autocomplete/{howdy => howdy.in} (86%) delete mode 100644 howdy/src/pam-config/howdy create mode 100644 howdy/src/pam-config/howdy.in diff --git a/howdy/src/autocomplete/howdy b/howdy/src/autocomplete/howdy.in similarity index 86% rename from howdy/src/autocomplete/howdy rename to howdy/src/autocomplete/howdy.in index 0ee737c3..38d4fed2 100755 --- a/howdy/src/autocomplete/howdy +++ b/howdy/src/autocomplete/howdy.in @@ -4,6 +4,8 @@ _howdy() { local cur prev opts + local config_path="@config_path@" + source _variables COMPREPLY=() # The argument typed so far cur="${COMP_WORDS[COMP_CWORD]}" @@ -20,7 +22,7 @@ _howdy() { ;; # For disable, grab the current "disabled" config option and give the reverse "disable") - local status=$(cut -d'=' -f2 <<< $(cat /etc/howdy/config.ini | grep 'disabled =') | xargs echo -n) + local status=$(cut -d'=' -f2 <<< $(cat $config_path | grep 'disabled =') | xargs echo -n) [ "$status" == "false" ] && COMPREPLY="true" || COMPREPLY="false" return 0 diff --git a/howdy/src/meson.build b/howdy/src/meson.build index 57a6e70c..65339513 100644 --- a/howdy/src/meson.build +++ b/howdy/src/meson.build @@ -53,6 +53,21 @@ pam_module_conf_data = configuration_data(paths_dict) pam_module_conf_data.set('compare_script_path', join_paths(pysourcesinstalldir, 'compare.py')) pam_module_conf_data.set('config_file_path', config_path) subdir('pam') +if get_option('install_pam_config') + # pamdir is inherited from the pam subproject + pam_config = configure_file( + input: 'pam-config/howdy.in', + output: 'pam-config', + configuration: {'pamdir': pamdir} + ) + install_data( + pam_config, + install_dir: get_option('prefix') / get_option('datadir') / 'pam-configs', + install_mode: 'rwxr-xr-x', + install_tag: 'pam', + rename: 'howdy', + ) +endif if get_option('install_in_site_packages') py.install_sources( @@ -73,6 +88,18 @@ else endif install_data('logo.png', install_tag: 'meta') +autocomplete = configure_file( + input: 'autocomplete/howdy.in', + output: 'autocomplete', + configuration: configuration_data({ 'config_path': config_path }) +) +install_data( + autocomplete, + install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'bash-completion', 'completions'), + install_mode: 'rwxr--r--', + install_tag: 'bash_completion', + rename: 'howdy', +) fs = import('fs') if not fs.exists(config_path) diff --git a/howdy/src/pam-config/howdy b/howdy/src/pam-config/howdy deleted file mode 100644 index ed190397..00000000 --- a/howdy/src/pam-config/howdy +++ /dev/null @@ -1,6 +0,0 @@ -Name: Howdy -Default: yes -Priority: 512 -Auth-Type: Primary -Auth: - [success=end default=ignore] /lib/security/howdy/pam_howdy.so diff --git a/howdy/src/pam-config/howdy.in b/howdy/src/pam-config/howdy.in new file mode 100644 index 00000000..b447b258 --- /dev/null +++ b/howdy/src/pam-config/howdy.in @@ -0,0 +1,6 @@ +Name: Howdy +Default: yes +Priority: 512 +Auth-Type: Primary +Auth: + [success=end default=ignore] @pamdir@/pam_howdy.so diff --git a/meson.build b/meson.build index d6cffff6..c2150dfc 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('howdy', 'cpp', license: 'MIT', version: 'beta', meson_version: '>= 0.64.0') +dlibdatadir = get_option('dlib_data_dir') != '' ? get_option('dlib_data_dir') : join_paths(get_option('prefix'), get_option('datadir'), 'dlib-data') confdir = get_option('config_dir') != '' ? get_option('config_dir') : join_paths(get_option('prefix'), get_option('sysconfdir'), 'howdy') -dlibdatadir = get_option('dlib_data_dir') != '' ? get_option('dlib_data_dir') : join_paths(confdir, 'dlib-data') usermodelsdir = get_option('user_models_dir') != '' ? get_option('user_models_dir') : join_paths(confdir, 'models') logpath = get_option('log_path') diff --git a/meson.options b/meson.options index bd4318a7..1a35b819 100644 --- a/meson.options +++ b/meson.options @@ -5,4 +5,5 @@ option('dlib_data_dir', type: 'string', value: '', description: 'Set the dlib da option('user_models_dir', type: 'string', value: '', description: 'Set the user models directory') option('log_path', type: 'string', value: '/var/log/howdy', description: 'Set the log file path') option('install_in_site_packages', type: 'boolean', value: false, description: 'Install howdy python files in site packages') -option('py_sources_dir', type: 'string', value: '', description: 'Set the python sources directory') \ No newline at end of file +option('py_sources_dir', type: 'string', value: '', description: 'Set the python sources directory') +option('install_pam_config', type: 'boolean', value: false, description: 'Install pam config file (for Debian/Ubuntu)') \ No newline at end of file From 0eb3eeb6a728ccfd44ea252329aa8765618f41f1 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Tue, 12 Sep 2023 22:51:03 +0200 Subject: [PATCH 28/29] build: fix pamdir path --- howdy/src/pam/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/howdy/src/pam/meson.build b/howdy/src/pam/meson.build index fa04f7b5..80ffd82d 100644 --- a/howdy/src/pam/meson.build +++ b/howdy/src/pam/meson.build @@ -14,7 +14,7 @@ paths_h = configure_file( install_dir: get_option('pam_dir') ) -pamdir = get_option('pam_dir') != '' ? get_option('pam_dir') : join_paths(get_option('libdir'), 'security') +pamdir = get_option('pam_dir') != '' ? get_option('pam_dir') : join_paths(get_option('prefix'), get_option('libdir'), 'security') shared_library( 'pam_howdy', From 466c859cb8fab6e72edf3586e2e3fd07ced70653 Mon Sep 17 00:00:00 2001 From: Sayafdine Said Date: Tue, 12 Sep 2023 22:59:47 +0200 Subject: [PATCH 29/29] refactor: remove useless conversion --- howdy/src/cli/add.py | 8 ++++---- howdy/src/cli/test.py | 6 +++--- howdy/src/compare.py | 8 ++++---- howdy/src/pam/meson.build | 3 +-- howdy/src/snapshot.py | 4 ++-- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/howdy/src/cli/add.py b/howdy/src/cli/add.py index 8ee9f25c..9a2c9a0c 100644 --- a/howdy/src/cli/add.py +++ b/howdy/src/cli/add.py @@ -40,16 +40,16 @@ use_cnn = config.getboolean("core", "use_cnn", fallback=False) if use_cnn: - face_detector = dlib.cnn_face_detection_model_v1(str(paths_factory.mmod_human_face_detector_path())) + face_detector = dlib.cnn_face_detection_model_v1(paths_factory.mmod_human_face_detector_path()) else: face_detector = dlib.get_frontal_face_detector() -pose_predictor = dlib.shape_predictor(str(paths_factory.shape_predictor_5_face_landmarks_path())) -face_encoder = dlib.face_recognition_model_v1(str(paths_factory.dlib_face_recognition_resnet_model_v1_path())) +pose_predictor = dlib.shape_predictor(paths_factory.shape_predictor_5_face_landmarks_path()) +face_encoder = dlib.face_recognition_model_v1(paths_factory.dlib_face_recognition_resnet_model_v1_path()) user = builtins.howdy_user # The permanent file to store the encoded model in -enc_file = str(paths_factory.user_model_path(user)) +enc_file = paths_factory.user_model_path(user) # Known encodings encodings = [] diff --git a/howdy/src/cli/test.py b/howdy/src/cli/test.py index 934d535c..f18c4ebd 100644 --- a/howdy/src/cli/test.py +++ b/howdy/src/cli/test.py @@ -57,13 +57,13 @@ def print_text(line_number, text): if use_cnn: face_detector = dlib.cnn_face_detection_model_v1( - str(paths_factory.mmod_human_face_detector_path()) + paths_factory.mmod_human_face_detector_path() ) else: face_detector = dlib.get_frontal_face_detector() -pose_predictor = dlib.shape_predictor(str(paths_factory.shape_predictor_5_face_landmarks_path())) -face_encoder = dlib.face_recognition_model_v1(str(paths_factory.dlib_face_recognition_resnet_model_v1_path())) +pose_predictor = dlib.shape_predictor(paths_factory.shape_predictor_5_face_landmarks_path()) +face_encoder = dlib.face_recognition_model_v1(paths_factory.dlib_face_recognition_resnet_model_v1_path()) encodings = [] models = None diff --git a/howdy/src/compare.py b/howdy/src/compare.py index 00d03481..0b2213f6 100644 --- a/howdy/src/compare.py +++ b/howdy/src/compare.py @@ -45,7 +45,7 @@ def init_detector(lock): global face_detector, pose_predictor, face_encoder # Test if at lest 1 of the data files is there and abort if it's not - if not os.path.isfile(str(paths_factory.shape_predictor_5_face_landmarks_path())): + if not os.path.isfile(paths_factory.shape_predictor_5_face_landmarks_path()): print(_("Data files have not been downloaded, please run the following commands:")) print("\n\tcd " + paths_factory.dlib_data_dir_path()) print("\tsudo ./install.sh\n") @@ -54,13 +54,13 @@ def init_detector(lock): # Use the CNN detector if enabled if use_cnn: - face_detector = dlib.cnn_face_detection_model_v1(str(paths_factory.mmod_human_face_detector_path())) + face_detector = dlib.cnn_face_detection_model_v1(paths_factory.mmod_human_face_detector_path()) else: face_detector = dlib.get_frontal_face_detector() # Start the others regardless - pose_predictor = dlib.shape_predictor(str(paths_factory.shape_predictor_5_face_landmarks_path())) - face_encoder = dlib.face_recognition_model_v1(str(paths_factory.dlib_face_recognition_resnet_model_v1_path())) + pose_predictor = dlib.shape_predictor(paths_factory.shape_predictor_5_face_landmarks_path()) + face_encoder = dlib.face_recognition_model_v1(paths_factory.dlib_face_recognition_resnet_model_v1_path()) # Note the time it took to initialize detectors timings["ll"] = time.time() - timings["ll"] diff --git a/howdy/src/pam/meson.build b/howdy/src/pam/meson.build index 80ffd82d..35cdb03b 100644 --- a/howdy/src/pam/meson.build +++ b/howdy/src/pam/meson.build @@ -10,8 +10,7 @@ subdir('po') paths_h = configure_file( input: 'paths.hh.in', output: 'paths.hh', - configuration: pam_module_conf_data, - install_dir: get_option('pam_dir') + configuration: pam_module_conf_data ) pamdir = get_option('pam_dir') != '' ? get_option('pam_dir') : join_paths(get_option('prefix'), get_option('libdir'), 'security') diff --git a/howdy/src/snapshot.py b/howdy/src/snapshot.py index 9e55ca07..ae40d47d 100644 --- a/howdy/src/snapshot.py +++ b/howdy/src/snapshot.py @@ -30,7 +30,7 @@ def generate(frames, text_lines): # Add the Howdy logo if there's space to do so if len(frames) > 1: # Load the logo from file - logo = cv2.imread(str(paths_factory.logo_path())) + logo = cv2.imread(paths_factory.logo_path()) # Calculate the position of the logo logo_y = frame_height + 20 logo_x = frame_width * len(frames) - 210 @@ -54,7 +54,7 @@ def generate(frames, text_lines): # Generate a filename based on the current time filename = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%S.jpg") - filepath = str(paths_factory.snapshot_path(filename)) + filepath = paths_factory.snapshot_path(filename) # Write the image to that file cv2.imwrite(filepath, snap)