diff --git a/debian/changelog b/debian/changelog index 66a0ecb..338f9d4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +howdy (2.5.1) xenial; urgency=medium + + * Removed dismiss_lockscreen as it could lock users out of their system (thanks @ujjwalbe, @ju916 and many others!) + * Added option to disable howdy when the laptop lid is closed (thanks @accek!) + * Added automatic fallback to default frame color palette (thanks @Ethiarpus!) + * Added manual exposure setting (thanks @accek!) + * Fixed test command ignoring dark frame threshold (thanks @eduncan911!) + * Fixed import error in v4l2 recorder (thanks @timwelch!) + + -- boltgolt Fri, 29 Mar 2019 23:02:21 +0100 + howdy (2.5.0) xenial; urgency=medium * Added FFmpeg and v4l2 recorders (thanks @timwelch!) diff --git a/debian/postinst b/debian/postinst index 7ceb41c..7ab3b89 100755 --- a/debian/postinst +++ b/debian/postinst @@ -70,6 +70,13 @@ if not os.path.exists("/tmp/howdy_picked_device"): if key == "timout": key = "timeout" + # MIGRATION 2.5.0 -> 2.5.1 + # Remove unsafe automatic dismissal of lock screen + if key == "dismiss_lockscreen": + if value == "true": + print("DEPRECATION: Config falue dismiss_lockscreen is no longer supported because of login loop issues.") + continue + try: newConf.set(section, key, value) # Add a new section where needed @@ -152,24 +159,6 @@ log("Building dlib") cmd = ["sudo", "python3", "setup.py", "install"] cuda_used = False -flags = "" - -# Get the CPU details -with open("/proc/cpuinfo") as info: - for line in info: - if "flags" in line: - flags = line - break - -# Use the most efficient instruction set the CPU supports -if "avx" in flags: - cmd += ["--yes", "USE_AVX_INSTRUCTIONS"] -elif "sse4" in flags: - cmd += ["--yes", "USE_SSE4_INSTRUCTIONS"] -elif "sse3" in flags: - cmd += ["--yes", "USE_SSE3_INSTRUCTIONS"] -elif "sse2" in flags: - cmd += ["--yes", "USE_SSE2_INSTRUCTIONS"] # Compile and link dlib try: diff --git a/debian/prerm b/debian/prerm index 150e7b8..e7c2469 100755 --- a/debian/prerm +++ b/debian/prerm @@ -12,7 +12,7 @@ from shutil import rmtree if "remove" not in sys.argv and "purge" not in sys.argv: sys.exit(0) -# Don't try running this if it's already gome +# Don't try running this if it's already gone if not os.path.exists("/lib/security/howdy/cli"): sys.exit(0) diff --git a/src/cli/test.py b/src/cli/test.py index c3cbebb..16fadb1 100644 --- a/src/cli/test.py +++ b/src/cli/test.py @@ -1,4 +1,4 @@ -# Show a windows with the video stream and testing information +# Show a window with the video stream and testing information # Import required modules import configparser @@ -37,6 +37,10 @@ if fw != -1: if fh != -1: video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, fh) +# Read exposure and dark_thresholds from config to use in the main loop +exposure = config.getint("video", "exposure", fallback=-1) +dark_threshold = config.getfloat("video", "dark_threshold") + # Let the user know what's up print(""" Opening a window with a test feed @@ -106,7 +110,17 @@ try: # Grab a single frame of video ret, frame = video_capture.read() - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + + try: + # Convert from color to grayscale + # First processing of frame, so frame errors show up here + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + except RuntimeError: + pass + except cv2.error: + print("\nUnknown camera, please check your 'device_path' config value.\n") + raise + frame = clahe.apply(frame) # Make a frame to put overlays in overlay = frame.copy() @@ -133,9 +147,6 @@ try: # Draw the bar in green cv2.rectangle(overlay, p1, p2, (0, 200, 0), thickness=cv2.FILLED) - # Draw a stripe indicating the dark threshold - cv2.rectangle(overlay, (8, 35), (20, 36), (255, 0, 0), thickness=cv2.FILLED) - # Print the statis in the bottom left print_text(0, "RESOLUTION: %dx%d" % (height, width)) print_text(1, "FPS: %d" % (fps, )) @@ -147,7 +158,7 @@ try: cv2.putText(overlay, "SLOW MODE", (width - 66, height - 10), cv2.FONT_HERSHEY_SIMPLEX, .3, (0, 0, 255), 0, cv2.LINE_AA) # Ignore dark frames - if hist_perc[0] > 50: + if hist_perc[0] > dark_threshold: # Show that this is an ignored frame in the top right cv2.putText(overlay, "DARK FRAME", (width - 68, 16), cv2.FONT_HERSHEY_SIMPLEX, .3, (0, 0, 255), 0, cv2.LINE_AA) else: @@ -156,7 +167,8 @@ try: rec_tm = time.time() # Get the locations of all faces and their locations - face_locations = face_detector(frame, 1) # upsample 1 time + # Upsample it once + face_locations = face_detector(frame, 1) rec_tm = time.time() - rec_tm # Loop though all faces and paint a circle around them @@ -193,6 +205,15 @@ try: if slow_mode: time.sleep(.5 - frame_time) + if exposure != -1: + # For a strange reason on some cameras (e.g. Lenoxo X1E) + # setting manual exposure works only after a couple frames + # are captured and even after a delay it does not + # always work. Setting exposure at every frame is + # reliable though. + video_capture.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1.0) # 1 = Manual + video_capture.set(cv2.CAP_PROP_EXPOSURE, float(exposure)) + # On ctrl+C except KeyboardInterrupt: # Let the user know we're stopping diff --git a/src/compare.py b/src/compare.py index 3f472b8..98c6b72 100644 --- a/src/compare.py +++ b/src/compare.py @@ -144,6 +144,9 @@ if fh != -1: # This will let the camera adjust its light levels while we're importing for faster scanning video_capture.grab() +# Read exposure from config to use in the main loop +exposure = config.getint("video", "exposure", fallback=-1) + # Note the time it took to open the camera timings["ic"] = time.time() - timings["ic"] @@ -180,10 +183,16 @@ while True: # Grab a single frame of video ret, frame = video_capture.read() + if frames == 1 and ret is False: + print("Could not read from camera") + exit(12) + try: # Convert from color to grayscale # First processing of frame, so frame errors show up here gsframe = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + except RuntimeError: + gsframe = frame except cv2.error: print("\nUnknown camera, please check your 'device_path' config value.\n") raise @@ -261,3 +270,12 @@ while True: # End peacefully stop(0) + + if exposure != -1: + # For a strange reason on some cameras (e.g. Lenoxo X1E) + # setting manual exposure works only after a couple frames + # are captured and even after a delay it does not + # always work. Setting exposure at every frame is + # reliable though. + video_capture.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1.0) # 1 = Manual + video_capture.set(cv2.CAP_PROP_EXPOSURE, float(exposure)) diff --git a/src/config.ini b/src/config.ini index 34d5685..1183f48 100644 --- a/src/config.ini +++ b/src/config.ini @@ -15,10 +15,8 @@ suppress_unknown = false # Disable Howdy in remote shells ignore_ssh = true -# Auto dismiss lock screen on confirmation -# Will run loginctl unlock-sessions after every auth -# Experimental, can behave incorrectly on some systems -dismiss_lockscreen = false +# Disable Howdy if lid is closed +ignore_closed_lid = true # Disable howdy in the PAM # The howdy command will still function @@ -70,6 +68,12 @@ device_format = v4l2 # OPENCV only. force_mjpeg = false +# Specify exposure value explicitly. This disables autoexposure. +# Use qv4l2 to determine an appropriate value. +# OPENCV only. +exposure = -1 + [debug] # Show a short but detailed diagnostic report in console +# Enabling this can cause some UI apps to fail, only enable it to debug end_report = false diff --git a/src/pam.py b/src/pam.py index da98cf1..3d44114 100644 --- a/src/pam.py +++ b/src/pam.py @@ -4,6 +4,7 @@ import subprocess import sys import os +import glob # pam-python is running python 2, so we use the old module here import ConfigParser @@ -25,8 +26,13 @@ def doAuth(pamh): if "SSH_CONNECTION" in os.environ or "SSH_CLIENT" in os.environ or "SSHD_OPTS" in os.environ: sys.exit(0) + # Abort if lid is closed + if config.getboolean("core", "ignore_closed_lid"): + if any("closed" in open(f).read() for f in glob.glob("/proc/acpi/button/lid/*/state")): + sys.exit(0) + # Alert the user that we are doing face detection - if config.get("core", "detection_notice") == "true": + if config.getboolean("core", "detection_notice"): pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, "Attempting face detection")) # Run compare as python3 subprocess to circumvent python version and import issues @@ -50,11 +56,6 @@ def doAuth(pamh): if not config.getboolean("core", "no_confirmation"): pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, "Identified face as " + pamh.get_user())) - # Try to dismiss the lock screen if enabled - if config.get("core", "dismiss_lockscreen"): - # Run it as root with a timeout of 1s, and never ask for a password through the UI - subprocess.Popen(["sudo", "timeout", "1", "loginctl", "unlock-sessions", "--no-ask-password"]) - return pamh.PAM_SUCCESS # Otherwise, we can't discribe what happend but it wasn't successful diff --git a/src/recorders/pyv4l2_reader.py b/src/recorders/pyv4l2_reader.py index 6d52871..d0efcf5 100644 --- a/src/recorders/pyv4l2_reader.py +++ b/src/recorders/pyv4l2_reader.py @@ -8,7 +8,7 @@ import sys from cv2 import cvtColor, COLOR_GRAY2BGR, CAP_PROP_FRAME_WIDTH, CAP_PROP_FRAME_HEIGHT try: - from v4l2.frame import Frame + from pyv4l2.frame import Frame except ImportError: print("Missing pyv4l2 module, please run:") print(" pip3 install pyv4l2\n")