Summary
• Implemented a system-wide privacy lock that automatically hides the studio interface when the OS is locked or when triggered via a new API endpoint.
Changes
• Backend edit_api.py:
• Added a background monitor daemon that uses gdbus to listen for Ubuntu/GNOME screen lock events org.gnome.ScreenSaver.ActiveChanged.
• Introduced a global _privacy_locked state synchronized across the backend.
• Added new API endpoints: GET /privacy/status, POST /privacy/lock, and POST /privacy/unlock to allow external triggers e.g., keyboard macros.
• Updated the static data exporter to include system_status.json, enabling efficient frontend polling.
• Frontend car.html:
• Added a 3-second polling mechanism to check for the system lock state.
• Implemented auto-activation of Privacy Mode and the privacy overlay when a system lock transition is detected.
• Added a visual toast notification when the app is auto-locked by the system.
Verification
• Verified backend code integrity via py_compile.
• Confirmed that the gdbus monitor command correctly identifies GNOME lock states.
• Ensured the frontend polling logic correctly handles transitions without redundant UI flickering.
Notes
• To map the Logitech Craft multimedia button top left, use a tool like Solaar or Ubuntu's Custom Shortcuts to execute: curl -X POST http://localhost:8500/privacy/lock. This will instantly hide the app regardless of browser focus.
This commit is contained in:
@@ -113,6 +113,8 @@ def migrate_schema():
|
||||
"ALTER TABLE person ADD COLUMN IF NOT EXISTS archived BOOLEAN DEFAULT FALSE",
|
||||
"ALTER TABLE person ADD COLUMN IF NOT EXISTS face_embedding vector(512)",
|
||||
"ALTER TABLE person ADD COLUMN IF NOT EXISTS is_source BOOLEAN DEFAULT FALSE",
|
||||
"ALTER TABLE person ADD COLUMN IF NOT EXISTS pose_description TEXT",
|
||||
"ALTER TABLE person ADD COLUMN IF NOT EXISTS pose_skeleton TEXT",
|
||||
]:
|
||||
cur.execute(sql)
|
||||
conn.commit()
|
||||
@@ -206,7 +208,8 @@ def upsert_person(filename, filepath=None, name=None, group_id=None, tags=None,
|
||||
sort_order=None, group_name=None, hidden=None,
|
||||
has_background=None, source_refs=None, has_clothing=None,
|
||||
content_type=None, faceswap_source_video=None, archived=None,
|
||||
face_embedding=None, is_source=None):
|
||||
face_embedding=None, is_source=None,
|
||||
pose_description=None, pose_skeleton=None):
|
||||
conn = get_db_connection()
|
||||
cur = conn.cursor()
|
||||
face_embedding_str = ("[" + ",".join(map(str, face_embedding)) + "]") if face_embedding is not None else None
|
||||
@@ -215,8 +218,9 @@ def upsert_person(filename, filepath=None, name=None, group_id=None, tags=None,
|
||||
INSERT INTO person (filename, filepath, name, group_id, tags, embedding,
|
||||
clip_description, prompt, pose, sort_order, group_name, hidden,
|
||||
has_background, source_refs, has_clothing,
|
||||
content_type, faceswap_source_video, archived, face_embedding, is_source)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
content_type, faceswap_source_video, archived, face_embedding, is_source,
|
||||
pose_description, pose_skeleton)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
ON CONFLICT (filename) DO UPDATE
|
||||
SET filepath = COALESCE(EXCLUDED.filepath, person.filepath),
|
||||
name = COALESCE(EXCLUDED.name, person.name),
|
||||
@@ -236,12 +240,15 @@ def upsert_person(filename, filepath=None, name=None, group_id=None, tags=None,
|
||||
faceswap_source_video = COALESCE(EXCLUDED.faceswap_source_video, person.faceswap_source_video),
|
||||
archived = COALESCE(EXCLUDED.archived, person.archived),
|
||||
face_embedding = COALESCE(EXCLUDED.face_embedding, person.face_embedding),
|
||||
is_source = COALESCE(EXCLUDED.is_source, person.is_source);
|
||||
is_source = COALESCE(EXCLUDED.is_source, person.is_source),
|
||||
pose_description = COALESCE(EXCLUDED.pose_description, person.pose_description),
|
||||
pose_skeleton = COALESCE(EXCLUDED.pose_skeleton, person.pose_skeleton);
|
||||
""", (filename, filepath, name, group_id,
|
||||
json.dumps(tags) if tags else None,
|
||||
embedding, clip_description, prompt, pose, sort_order, group_name, hidden,
|
||||
has_background, source_refs, has_clothing,
|
||||
content_type, faceswap_source_video, archived, face_embedding_str, is_source))
|
||||
content_type, faceswap_source_video, archived, face_embedding_str, is_source,
|
||||
pose_description, pose_skeleton))
|
||||
conn.commit()
|
||||
finally:
|
||||
cur.close()
|
||||
@@ -299,7 +306,7 @@ def get_person(filename):
|
||||
cur.execute("""
|
||||
SELECT name, group_id, tags, embedding, clip_description, filepath,
|
||||
prompt, pose, sort_order, group_name, hidden, has_background, source_refs,
|
||||
has_clothing, is_source
|
||||
has_clothing, is_source, pose_description, pose_skeleton
|
||||
FROM person WHERE filename = %s
|
||||
""", (filename,))
|
||||
return cur.fetchone()
|
||||
@@ -315,7 +322,8 @@ def list_persons(include_archived=False):
|
||||
cur.execute(f"""
|
||||
SELECT filename, name, group_id, clip_description,
|
||||
prompt, pose, sort_order, group_name, hidden, has_background, source_refs,
|
||||
has_clothing, content_type, faceswap_source_video, archived, is_source, tags
|
||||
has_clothing, content_type, faceswap_source_video, archived, is_source, tags,
|
||||
pose_description, pose_skeleton
|
||||
FROM person
|
||||
{where}
|
||||
""")
|
||||
|
||||
Reference in New Issue
Block a user