Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/telephony/client/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,6 @@ viewswitcher button:focus {

.blocklist-on { color: #c01c28; }
.blocklist-off { color: alpha(@theme_fg_color, 0.25); }

/* Camera and playback previews: bare rounded surfaces, no card backing. */
.preview-round { border-radius: 12px; }
8 changes: 6 additions & 2 deletions src/telephony/client/ui/widgets/common_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,10 @@ def present_sheet_page(window, page, replace=False):

A page going onto an open sheet asks for the height the sheet
already has, so a short page does not shrink the sheet around it
and let it spring back when the page is left.
and let it spring back when the page is left. A page that asks for
more keeps its own ask: a capture page opened from a small chooser
would otherwise start small and leap once its viewfinder starts
drawing.

The page takes the focus itself so it does not go to whatever the
page happens to hold first. A text field taking it brings the
Expand All @@ -163,7 +166,8 @@ def present_sheet_page(window, page, replace=False):
host = window.sheet_host
nav = sheet_navigation(host.get_sheet()) if host.get_open() else None
if nav is not None:
page.set_size_request(-1, nav.get_height())
own_height = page.get_size_request()[1]
page.set_size_request(-1, max(nav.get_height(), own_height))
if replace:
nav.replace([page])
return
Expand Down
29 changes: 13 additions & 16 deletions src/telephony/client/ui/windows/camera_photo_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

import os
import time
import tempfile
from gettext import gettext as _

import gi
Expand All @@ -26,7 +25,7 @@
from telephony.shared.utils.log_utils import logger

from telephony.shared.utils.thread_utils import run_in_background
from telephony.shared.constants import (VIEWFINDER_START_DELAY_MS, CAPTURE_SHEET_HEIGHT)
from telephony.shared.constants import VIEWFINDER_START_DELAY_MS
from telephony.client.ui.windows.media_window_base import MediaCaptureWindow
from telephony.client.ui.widgets.common_widget import close_sheet_page

Expand All @@ -43,7 +42,7 @@ class CameraPhoto(MediaCaptureWindow):

def __init__(self, parent_window, on_attach_callback):
super().__init__()
self.set_size_request(-1, CAPTURE_SHEET_HEIGHT)
self.request_capture_height(parent_window)
self.on_attach_callback = on_attach_callback
self.set_title(_("Take Picture"))

Expand Down Expand Up @@ -86,9 +85,6 @@ def _setup_ui(self):
header.set_show_end_title_buttons(False)
content.append(header)

btn_cancel = Gtk.Button(label=_("Cancel"))
btn_cancel.connect("clicked", lambda b: GLib.idle_add(lambda: self._on_cancel_clicked(b) or False))
header.pack_start(btn_cancel)

self.stack = Gtk.Stack()
self.stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
Expand All @@ -98,7 +94,7 @@ def _setup_ui(self):
self.page_capture = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

card_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
card_box.add_css_class("card")
card_box.add_css_class("preview-round")
card_box.set_hexpand(True)
card_box.set_vexpand(True)
card_box.set_margin_top(10)
Expand All @@ -108,12 +104,14 @@ def _setup_ui(self):
card_box.set_overflow(Gtk.Overflow.HIDDEN)

self.viewfinder_widget = Gtk.Picture()
self.viewfinder_widget.add_css_class("preview-round")
self.viewfinder_widget.set_overflow(Gtk.Overflow.HIDDEN)
self.viewfinder_widget.set_can_shrink(True)
self.viewfinder_widget.set_hexpand(True)
self.viewfinder_widget.set_vexpand(True)
self.viewfinder_widget.set_content_fit(Gtk.ContentFit.CONTAIN)

card_box.append(self.viewfinder_widget)
card_box.append(self.letterbox(self.viewfinder_widget))
self.page_capture.append(card_box)

ctrl_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
Expand All @@ -134,7 +132,7 @@ def _setup_ui(self):
self.page_review = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

review_card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
review_card.add_css_class("card")
review_card.add_css_class("preview-round")
review_card.set_hexpand(True)
review_card.set_vexpand(True)
review_card.set_margin_top(10)
Expand All @@ -144,12 +142,14 @@ def _setup_ui(self):
review_card.set_overflow(Gtk.Overflow.HIDDEN)

self.review_image = Gtk.Picture()
self.review_image.add_css_class("preview-round")
self.review_image.set_overflow(Gtk.Overflow.HIDDEN)
self.review_image.set_can_shrink(True)
self.review_image.set_hexpand(True)
self.review_image.set_vexpand(True)
self.review_image.set_content_fit(Gtk.ContentFit.CONTAIN)

review_card.append(self.review_image)
review_card.append(self.letterbox(self.review_image))
self.page_review.append(review_card)

act_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=20)
Expand Down Expand Up @@ -194,6 +194,7 @@ def _start_viewfinder(self):
if sink:
paintable = sink.get_property("paintable")
self.viewfinder_widget.set_paintable(paintable)
self.reveal_on_first_frame(self.viewfinder_widget)

self.bus, self.bus_handler_id = self._watch_bus(self.pipeline, self._on_viewfinder_message)

Expand Down Expand Up @@ -238,7 +239,7 @@ def _attempt_capture(self):

def _capture_frame(self):
"""Capture a single frame from the camera."""
self.temp_capture_path = os.path.join(tempfile.gettempdir(), f"cam_cap_{int(time.time())}.jpg")
self.temp_capture_path = os.path.join(self.capture_dir(), f"cam_cap_{int(time.time())}.jpg")
self.frame_count = 0
self._capture_taken = False

Expand Down Expand Up @@ -379,7 +380,7 @@ def _process_image(self, path):
if w > MAX_IMAGE_DIMENSION or h > MAX_IMAGE_DIMENSION:
img.thumbnail((MAX_IMAGE_DIMENSION, MAX_IMAGE_DIMENSION))

output_path = os.path.join(tempfile.gettempdir(), f"photo_{int(time.time())}.jpg")
output_path = os.path.join(self.capture_dir(), f"photo_{int(time.time())}.jpg")
img.save(output_path, "JPEG", quality=JPEG_QUALITY)

if path != output_path:
Expand Down Expand Up @@ -415,10 +416,6 @@ def _on_attach_clicked(self, btn):
self.on_attach_callback(self.output_path)
GLib.idle_add(lambda: close_sheet_page(self.get_root()) or False)

def _on_cancel_clicked(self, btn):
"""Handle cancel button click."""
GLib.idle_add(lambda: close_sheet_page(self.get_root()) or False)

def _on_closed(self, _dialog):
"""Tear down capture state when the sheet closes."""
self._closed = True
Expand Down
45 changes: 28 additions & 17 deletions src/telephony/client/ui/windows/camera_video_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

import os
import time
import tempfile
from gettext import gettext as _

import gi
Expand All @@ -25,7 +24,7 @@
from gi.repository import Gtk, Adw, Gst, GLib
from telephony.shared.utils.log_utils import logger

from telephony.shared.constants import (CAPTURE_SHEET_HEIGHT, VIEWFINDER_START_DELAY_MS, PLAYBACK_PROGRESS_INTERVAL_MS, EOS_TIMEOUT_MS, PROGRESS_BAR_WIDTH)
from telephony.shared.constants import (VIEWFINDER_START_DELAY_MS, PLAYBACK_PROGRESS_INTERVAL_MS, EOS_TIMEOUT_MS, PROGRESS_BAR_WIDTH)
from telephony.client.ui.windows.media_window_base import MediaCaptureWindow
from telephony.client.ui.widgets.common_widget import close_sheet_page

Expand All @@ -43,7 +42,7 @@ class CameraVideo(MediaCaptureWindow):

def __init__(self, parent_window, on_attach_callback):
super().__init__()
self.set_size_request(-1, CAPTURE_SHEET_HEIGHT)
self.request_capture_height(parent_window)

registry = Gst.Registry.get()
droidvdec = registry.lookup_feature("droidvdec")
Expand Down Expand Up @@ -92,9 +91,6 @@ def _setup_ui(self):
header.set_show_end_title_buttons(False)
content.append(header)

btn_cancel = Gtk.Button(label=_("Cancel"))
btn_cancel.connect("clicked", lambda b: GLib.idle_add(lambda: self._on_cancel_clicked(b) or False))
header.pack_start(btn_cancel)

self.stack = Gtk.Stack()
self.stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
Expand All @@ -104,7 +100,7 @@ def _setup_ui(self):
self.page_capture = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

card_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
card_box.add_css_class("card")
card_box.add_css_class("preview-round")
card_box.set_hexpand(True)
card_box.set_vexpand(True)
card_box.set_margin_top(10)
Expand All @@ -114,12 +110,14 @@ def _setup_ui(self):
card_box.set_overflow(Gtk.Overflow.HIDDEN)

self.viewfinder_widget = Gtk.Picture()
self.viewfinder_widget.add_css_class("preview-round")
self.viewfinder_widget.set_overflow(Gtk.Overflow.HIDDEN)
self.viewfinder_widget.set_can_shrink(True)
self.viewfinder_widget.set_hexpand(True)
self.viewfinder_widget.set_vexpand(True)
self.viewfinder_widget.set_content_fit(Gtk.ContentFit.CONTAIN)

card_box.append(self.viewfinder_widget)
card_box.append(self.letterbox(self.viewfinder_widget))
self.page_capture.append(card_box)

ctrl_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
Expand Down Expand Up @@ -148,7 +146,7 @@ def _setup_ui(self):
self.page_review = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

review_card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
review_card.add_css_class("card")
review_card.add_css_class("preview-round")
review_card.set_hexpand(True)
review_card.set_vexpand(True)
review_card.set_margin_top(10)
Expand All @@ -158,12 +156,14 @@ def _setup_ui(self):
review_card.set_overflow(Gtk.Overflow.HIDDEN)

self.review_widget = Gtk.Picture()
self.review_widget.add_css_class("preview-round")
self.review_widget.set_overflow(Gtk.Overflow.HIDDEN)
self.review_widget.set_can_shrink(True)
self.review_widget.set_hexpand(True)
self.review_widget.set_vexpand(True)
self.review_widget.set_content_fit(Gtk.ContentFit.CONTAIN)

review_card.append(self.review_widget)
review_card.append(self.letterbox(self.review_widget))
self.page_review.append(review_card)

act_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
Expand Down Expand Up @@ -228,6 +228,7 @@ def _start_viewfinder(self):
if sink:
paintable = sink.get_property("paintable")
self.viewfinder_widget.set_paintable(paintable)
self.reveal_on_first_frame(self.viewfinder_widget)

self.bus, self.bus_handler_id = self._watch_bus(self.pipeline, self._on_viewfinder_message)

Expand Down Expand Up @@ -280,7 +281,7 @@ def _attempt_recording(self):

def _start_recording(self):
"""Initialize and start the GStreamer recording pipeline."""
self.output_path = os.path.join(tempfile.gettempdir(), f"video_{int(time.time())}.mkv")
self.output_path = os.path.join(self.capture_dir(), f"video_{int(time.time())}.mkv")

pipeline_str = (
f"matroskamux name=mux ! filesink location={self.output_path} "
Expand All @@ -299,6 +300,7 @@ def _start_recording(self):
if sink:
paintable = sink.get_property("paintable")
self.viewfinder_widget.set_paintable(paintable)
self.reveal_on_first_frame(self.viewfinder_widget)

self.bus, self.bus_handler_id = self._watch_bus(self.pipeline, self._on_record_message)

Expand Down Expand Up @@ -444,6 +446,7 @@ def _start_playback(self):

paintable = sink.get_property("paintable")
self.review_widget.set_paintable(paintable)
self.reveal_on_first_frame(self.review_widget)

self.player_bus, self.player_bus_handler_id = self._watch_bus(self.player, self._on_player_message)

Expand Down Expand Up @@ -471,15 +474,27 @@ def _stop_playback(self):
if self.player:
self.player.set_state(Gst.State.NULL)
self.player = None
self.review_widget.set_visible(False)
self.btn_play.set_icon_name("media-playback-start-symbolic")
self.lbl_progress.set_label(self._playback_progress_text(0, 0))
self.progress_bar.set_fraction(0.0)

def _on_player_message(self, bus, message):
"""Handle playback messages."""
"""Handle playback messages.

The end of the video is not the end of the player: tearing it
down leaves the picture holding a dead paintable, which paints
black. Rewound and paused it keeps the first frame up as a
poster, and play starts it again from the top.
"""
t = message.type
if t == Gst.MessageType.EOS:
self._stop_playback()
self.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, 0)
self.player.set_state(Gst.State.PAUSED)
if self.progress_timer_id:
self._cancel_timeout(self.progress_timer_id)
self.progress_timer_id = None
self.btn_play.set_icon_name("media-playback-start-symbolic")
elif t == Gst.MessageType.ERROR:
err, debug = message.parse_error()
logger.error(f"[Camera-Video] Playback error: {err} : {debug}")
Expand Down Expand Up @@ -516,10 +531,6 @@ def _on_attach_clicked(self, btn):
self.on_attach_callback(self.output_path)
GLib.idle_add(lambda: close_sheet_page(self.get_root()) or False)

def _on_cancel_clicked(self, btn):
"""Handle cancel button click."""
GLib.idle_add(lambda: close_sheet_page(self.get_root()) or False)

def _on_closed(self, _dialog):
"""Tear down capture state when the sheet closes."""
self._closed = True
Expand Down
Loading