Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CameraView: Allow to change the camera resolution and framerate #224

Draft
wants to merge 23 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
113 changes: 95 additions & 18 deletions src/MainWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ public class Camera.MainWindow : Hdy.ApplicationWindow {
public const string ACTION_FULLSCREEN = "fullscreen";
public const string ACTION_TAKE_PHOTO = "take_photo";
public const string ACTION_RECORD = "record";
public const string ACTION_CHANGE_CAPS = "change-caps";

private const GLib.ActionEntry[] ACTION_ENTRIES = {
{ACTION_FULLSCREEN, on_fullscreen},
{ACTION_TAKE_PHOTO, on_take_photo},
{ACTION_RECORD, on_record, null, "false", null},
{ACTION_CHANGE_CAPS, on_change_caps, "u", "uint32 0", null},
};

private const string PHOTO_ICON_SYMBOLIC = "view-list-images-symbolic";
Expand All @@ -39,6 +41,8 @@ public class Camera.MainWindow : Hdy.ApplicationWindow {

private Widgets.CameraView camera_view;
private Gtk.Menu camera_options;
private GLib.Menu resolution_menu;
private Gtk.MenuButton resolution_button;
private Gtk.Button take_button;
private Gtk.Image take_image;
private Gtk.Label take_timer_label;
Expand All @@ -49,6 +53,7 @@ public class Camera.MainWindow : Hdy.ApplicationWindow {
private Gtk.MenuButton menu_button;
private Gtk.Box linked_box;

private Gst.Device? current_device = null;
private bool timer_running = false;
public bool recording { get; private set; default = false; }

Expand Down Expand Up @@ -97,7 +102,6 @@ public class Camera.MainWindow : Hdy.ApplicationWindow {
});

var recording_finished_fail_toast = new Granite.Widgets.Toast (_("Recording failed"));

var overlay = new Gtk.Overlay ();
overlay.add (camera_view);
overlay.add_overlay (recording_finished_toast);
Expand Down Expand Up @@ -163,23 +167,10 @@ public class Camera.MainWindow : Hdy.ApplicationWindow {
mode_switch = new Granite.ModeSwitch.from_icon_name (PHOTO_ICON_SYMBOLIC, VIDEO_ICON_SYMBOLIC) {
valign = Gtk.Align.CENTER
};
mode_switch.notify["active"].connect (() => {
if (mode_switch.active) {
Camera.Application.settings.set_enum ("mode", Utils.ActionType.VIDEO);
take_button.action_name = Camera.MainWindow.ACTION_PREFIX + Camera.MainWindow.ACTION_RECORD;
take_image.icon_name = VIDEO_ICON_SYMBOLIC;
timer_button.sensitive = false;
} else {
Camera.Application.settings.set_enum ("mode", Utils.ActionType.PHOTO);
take_button.action_name = Camera.MainWindow.ACTION_PREFIX + Camera.MainWindow.ACTION_TAKE_PHOTO;
take_image.icon_name = PHOTO_ICON_SYMBOLIC;
timer_button.sensitive = true;
}
});
Camera.Application.settings.changed["mode"].connect ((key) => {
mode_switch.active = Camera.Application.settings.get_enum ("mode") == Utils.ActionType.VIDEO;
});

mode_switch.notify["active"].connect_after (on_mode_changed);
mode_switch.active = Camera.Application.settings.get_enum ("mode") == Utils.ActionType.VIDEO;
// No need to monitor external changes to own settings as inside sandbox and only changed by MainWindow

/* Construct AppMenu */
var mirror_switch = new Granite.SwitchModelButton (_("Mirror"));
Expand Down Expand Up @@ -268,6 +259,11 @@ public class Camera.MainWindow : Hdy.ApplicationWindow {
linked_box.pack_start (take_button);
linked_box.pack_start (camera_menu_revealer);

resolution_menu = new GLib.Menu ();
resolution_button = new Gtk.MenuButton () {
image = new Gtk.Image.from_icon_name ("preferences-desktop-display-symbolic", Gtk.IconSize.MENU),
};
resolution_button.set_menu_model (resolution_menu);
/* Pack tools into HeaderBar */
var header_widget = new Gtk.HeaderBar () {
show_close_button = true, // Gtk4 -> show_title_buttons = true,
Expand All @@ -277,6 +273,8 @@ public class Camera.MainWindow : Hdy.ApplicationWindow {
header_widget.pack_start (timer_button);
header_widget.pack_end (menu_button);
header_widget.pack_end (new Gtk.Separator (Gtk.Orientation.HORIZONTAL));
header_widget.pack_end (resolution_button);
header_widget.pack_end (new Gtk.Separator (Gtk.Orientation.HORIZONTAL));
header_widget.pack_end (mode_switch);

notify["recording"].connect (() => {
Expand Down Expand Up @@ -331,6 +329,13 @@ public class Camera.MainWindow : Hdy.ApplicationWindow {
}
}

private void on_change_caps (GLib.SimpleAction action, GLib.Variant? parameter) {
if (parameter != null) {
camera_view.change_caps ((int)parameter.get_uint32 ());
change_action_state (ACTION_CHANGE_CAPS, parameter);
}
}

public override bool configure_event (Gdk.EventConfigure event) {
if (configure_id != 0) {
GLib.Source.remove (configure_id);
Expand Down Expand Up @@ -364,6 +369,7 @@ public class Camera.MainWindow : Hdy.ApplicationWindow {
}

private void add_camera_option (Gst.Device camera) {
current_device = camera;
var menuitem = new Gtk.RadioMenuItem.with_label (null, camera.display_name);
menuitem.set_data<Gst.Device> ("camera", camera);
camera_options.append (menuitem);
Expand All @@ -375,13 +381,16 @@ public class Camera.MainWindow : Hdy.ApplicationWindow {
}
menuitem.active = true;
menuitem.activate.connect (() => {
current_device = menuitem.get_data<Gst.Device> ("camera");
if (menuitem.active) {
camera_view.change_camera (menuitem.get_data<Gst.Device> ("camera"));
camera_view.change_camera (current_device);
update_resolution_menu ();
}
});
menuitem.show ();

update_take_button ();
update_resolution_menu ();
enable_header (true);
}

Expand All @@ -399,10 +408,78 @@ public class Camera.MainWindow : Hdy.ApplicationWindow {
camera_options.remove (to_remove);
}

if (camera_options.get_children ().length () > 0) {
camera_options.active = 0;
}

update_resolution_menu ();
update_take_button ();
enable_header (camera_options.get_children ().length () > 0);
}

private void update_resolution_menu () {
resolution_button.tooltip_text = mode_switch.active ? _("Video capture resolution") : _("Photo capture resolution");
var caps_index = mode_switch.active ? camera_view.current_video_caps_index : camera_view.current_picture_caps_index;
if (caps_index < 0) {
// Suppress terminal warnings during startup
return;
}

resolution_menu.remove_all ();
if (current_device == null || current_device.get_caps () == null) {
return;
}

int prev_w = 0, prev_h = 0;
double prev_fr = 0.0;
var caps = current_device.get_caps ();
for (uint i = 0; i < caps.get_size (); i++) {
unowned var s = caps.get_structure (i);
if (s.get_name () != (mode_switch.active ? "video/x-raw" : "image/jpeg")) {
continue;
}

int w, h;
double fr = 0.0;
if (Camera.Utils.parse_structure (s, out w, out h, out fr)) {
// Check not duplicate ( for simplicity assume duplicates listed next to each other)
if (w != prev_w || h != prev_h || fr != prev_fr) {
if (mode_switch.active) { // Show framerate for video capture
resolution_menu.append (
"%d×%d (%0.f fps)".printf (w, h, fr),
GLib.Action.print_detailed_name ("win.change-caps", new GLib.Variant.uint32 (i))
);
} else { // Framerate not useful for still image capture
resolution_menu.append (
"%d×%d".printf (w, h),
GLib.Action.print_detailed_name ("win.change-caps", new GLib.Variant.uint32 (i))
);
}
}

prev_w = w;
prev_h = h;
prev_fr = fr;
}
}

change_action_state (ACTION_CHANGE_CAPS, new GLib.Variant.uint32 (caps_index));
}

private void on_mode_changed () {
camera_view.on_mode_changed (mode_switch.active);
update_resolution_menu ();
if (mode_switch.active) {
take_button.action_name = Camera.MainWindow.ACTION_PREFIX + Camera.MainWindow.ACTION_RECORD;
take_image.icon_name = VIDEO_ICON_SYMBOLIC;
timer_button.sensitive = false;
} else {
take_button.action_name = Camera.MainWindow.ACTION_PREFIX + Camera.MainWindow.ACTION_TAKE_PHOTO;
take_image.icon_name = PHOTO_ICON_SYMBOLIC;
timer_button.sensitive = true;
}
}

private void update_take_button () {
unowned Gtk.StyleContext take_button_style_context = take_button.get_style_context ();
if (camera_options.get_children ().length () > 1) {
Expand Down
31 changes: 31 additions & 0 deletions src/Utils.vala
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,35 @@ namespace Camera.Utils {

return GLib.Path.build_path (Path.DIR_SEPARATOR_S, media_directory, "Webcam");
}

public bool parse_structure (Gst.Structure s, out int width, out int height, out double framerate) {
framerate = 0.0;
int num = 0, den = 1;
if (s.get ("width", typeof (int), out width,
"height", typeof (int), out height)) {

unowned GLib.Value? fraction = s.get_value ("framerate");
if (fraction.holds (typeof (Gst.Fraction))) {
num = Gst.Value.get_fraction_numerator (fraction);
den = Gst.Value.get_fraction_denominator (fraction);
} else if (fraction.holds (typeof (Gst.FractionRange))) {
var range_max = Gst.Value.get_fraction_range_max (fraction);
num = Gst.Value.get_fraction_numerator (range_max);
den = Gst.Value.get_fraction_denominator (range_max);
} else if (fraction.holds (typeof (Gst.ValueList))) {
unowned GLib.Value? val = Gst.ValueList.get_value (fraction, 0);
num = Gst.Value.get_fraction_numerator (val);
den = Gst.Value.get_fraction_denominator (val);
} else {
warning ("Unknown fraction type: %s", fraction.type_name ());
return false;
}
} else {
warning ("no resolution in caps");
return false;
}

framerate = (double)num / (double)den;
return true;
}
}
Loading