From 84ad104499d9efc0253dae1a60ee070ed375ad95 Mon Sep 17 00:00:00 2001
From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Date: Thu, 20 Oct 2022 00:44:55 +0300
Subject: Move test applications to src/apps/

The cam and qcam test application share code, currently through a crude
hack that references the cam source files directly from the qcam
meson.build file. To prepare for the introduction of hosting that code
in a static library, move all applications to src/apps/.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
---
 src/apps/qcam/assets/feathericons/activity.svg     |   1 +
 src/apps/qcam/assets/feathericons/airplay.svg      |   1 +
 src/apps/qcam/assets/feathericons/alert-circle.svg |   1 +
 .../qcam/assets/feathericons/alert-octagon.svg     |   1 +
 .../qcam/assets/feathericons/alert-triangle.svg    |   1 +
 src/apps/qcam/assets/feathericons/align-center.svg |   1 +
 .../qcam/assets/feathericons/align-justify.svg     |   1 +
 src/apps/qcam/assets/feathericons/align-left.svg   |   1 +
 src/apps/qcam/assets/feathericons/align-right.svg  |   1 +
 src/apps/qcam/assets/feathericons/anchor.svg       |   1 +
 src/apps/qcam/assets/feathericons/aperture.svg     |   1 +
 src/apps/qcam/assets/feathericons/archive.svg      |   1 +
 .../qcam/assets/feathericons/arrow-down-circle.svg |   1 +
 .../qcam/assets/feathericons/arrow-down-left.svg   |   1 +
 .../qcam/assets/feathericons/arrow-down-right.svg  |   1 +
 src/apps/qcam/assets/feathericons/arrow-down.svg   |   1 +
 .../qcam/assets/feathericons/arrow-left-circle.svg |   1 +
 src/apps/qcam/assets/feathericons/arrow-left.svg   |   1 +
 .../assets/feathericons/arrow-right-circle.svg     |   1 +
 src/apps/qcam/assets/feathericons/arrow-right.svg  |   1 +
 .../qcam/assets/feathericons/arrow-up-circle.svg   |   1 +
 .../qcam/assets/feathericons/arrow-up-left.svg     |   1 +
 .../qcam/assets/feathericons/arrow-up-right.svg    |   1 +
 src/apps/qcam/assets/feathericons/arrow-up.svg     |   1 +
 src/apps/qcam/assets/feathericons/at-sign.svg      |   1 +
 src/apps/qcam/assets/feathericons/award.svg        |   1 +
 src/apps/qcam/assets/feathericons/bar-chart-2.svg  |   1 +
 src/apps/qcam/assets/feathericons/bar-chart.svg    |   1 +
 .../qcam/assets/feathericons/battery-charging.svg  |   1 +
 src/apps/qcam/assets/feathericons/battery.svg      |   1 +
 src/apps/qcam/assets/feathericons/bell-off.svg     |   1 +
 src/apps/qcam/assets/feathericons/bell.svg         |   1 +
 src/apps/qcam/assets/feathericons/bluetooth.svg    |   1 +
 src/apps/qcam/assets/feathericons/bold.svg         |   1 +
 src/apps/qcam/assets/feathericons/book-open.svg    |   1 +
 src/apps/qcam/assets/feathericons/book.svg         |   1 +
 src/apps/qcam/assets/feathericons/bookmark.svg     |   1 +
 src/apps/qcam/assets/feathericons/box.svg          |   1 +
 src/apps/qcam/assets/feathericons/briefcase.svg    |   1 +
 src/apps/qcam/assets/feathericons/calendar.svg     |   1 +
 src/apps/qcam/assets/feathericons/camera-off.svg   |   1 +
 src/apps/qcam/assets/feathericons/camera.svg       |   1 +
 src/apps/qcam/assets/feathericons/cast.svg         |   1 +
 src/apps/qcam/assets/feathericons/check-circle.svg |   1 +
 src/apps/qcam/assets/feathericons/check-square.svg |   1 +
 src/apps/qcam/assets/feathericons/check.svg        |   1 +
 src/apps/qcam/assets/feathericons/chevron-down.svg |   1 +
 src/apps/qcam/assets/feathericons/chevron-left.svg |   1 +
 .../qcam/assets/feathericons/chevron-right.svg     |   1 +
 src/apps/qcam/assets/feathericons/chevron-up.svg   |   1 +
 .../qcam/assets/feathericons/chevrons-down.svg     |   1 +
 .../qcam/assets/feathericons/chevrons-left.svg     |   1 +
 .../qcam/assets/feathericons/chevrons-right.svg    |   1 +
 src/apps/qcam/assets/feathericons/chevrons-up.svg  |   1 +
 src/apps/qcam/assets/feathericons/chrome.svg       |   1 +
 src/apps/qcam/assets/feathericons/circle.svg       |   1 +
 src/apps/qcam/assets/feathericons/clipboard.svg    |   1 +
 src/apps/qcam/assets/feathericons/clock.svg        |   1 +
 .../qcam/assets/feathericons/cloud-drizzle.svg     |   1 +
 .../qcam/assets/feathericons/cloud-lightning.svg   |   1 +
 src/apps/qcam/assets/feathericons/cloud-off.svg    |   1 +
 src/apps/qcam/assets/feathericons/cloud-rain.svg   |   1 +
 src/apps/qcam/assets/feathericons/cloud-snow.svg   |   1 +
 src/apps/qcam/assets/feathericons/cloud.svg        |   1 +
 src/apps/qcam/assets/feathericons/code.svg         |   1 +
 src/apps/qcam/assets/feathericons/codepen.svg      |   1 +
 src/apps/qcam/assets/feathericons/codesandbox.svg  |   1 +
 src/apps/qcam/assets/feathericons/coffee.svg       |   1 +
 src/apps/qcam/assets/feathericons/columns.svg      |   1 +
 src/apps/qcam/assets/feathericons/command.svg      |   1 +
 src/apps/qcam/assets/feathericons/compass.svg      |   1 +
 src/apps/qcam/assets/feathericons/copy.svg         |   1 +
 .../qcam/assets/feathericons/corner-down-left.svg  |   1 +
 .../qcam/assets/feathericons/corner-down-right.svg |   1 +
 .../qcam/assets/feathericons/corner-left-down.svg  |   1 +
 .../qcam/assets/feathericons/corner-left-up.svg    |   1 +
 .../qcam/assets/feathericons/corner-right-down.svg |   1 +
 .../qcam/assets/feathericons/corner-right-up.svg   |   1 +
 .../qcam/assets/feathericons/corner-up-left.svg    |   1 +
 .../qcam/assets/feathericons/corner-up-right.svg   |   1 +
 src/apps/qcam/assets/feathericons/cpu.svg          |   1 +
 src/apps/qcam/assets/feathericons/credit-card.svg  |   1 +
 src/apps/qcam/assets/feathericons/crop.svg         |   1 +
 src/apps/qcam/assets/feathericons/crosshair.svg    |   1 +
 src/apps/qcam/assets/feathericons/database.svg     |   1 +
 src/apps/qcam/assets/feathericons/delete.svg       |   1 +
 src/apps/qcam/assets/feathericons/disc.svg         |   1 +
 src/apps/qcam/assets/feathericons/dollar-sign.svg  |   1 +
 .../qcam/assets/feathericons/download-cloud.svg    |   1 +
 src/apps/qcam/assets/feathericons/download.svg     |   1 +
 src/apps/qcam/assets/feathericons/droplet.svg      |   1 +
 src/apps/qcam/assets/feathericons/edit-2.svg       |   1 +
 src/apps/qcam/assets/feathericons/edit-3.svg       |   1 +
 src/apps/qcam/assets/feathericons/edit.svg         |   1 +
 .../qcam/assets/feathericons/external-link.svg     |   1 +
 src/apps/qcam/assets/feathericons/eye-off.svg      |   1 +
 src/apps/qcam/assets/feathericons/eye.svg          |   1 +
 src/apps/qcam/assets/feathericons/facebook.svg     |   1 +
 src/apps/qcam/assets/feathericons/fast-forward.svg |   1 +
 src/apps/qcam/assets/feathericons/feather.svg      |   1 +
 src/apps/qcam/assets/feathericons/feathericons.qrc |  11 +
 src/apps/qcam/assets/feathericons/figma.svg        |   1 +
 src/apps/qcam/assets/feathericons/file-minus.svg   |   1 +
 src/apps/qcam/assets/feathericons/file-plus.svg    |   1 +
 src/apps/qcam/assets/feathericons/file-text.svg    |   1 +
 src/apps/qcam/assets/feathericons/file.svg         |   1 +
 src/apps/qcam/assets/feathericons/film.svg         |   1 +
 src/apps/qcam/assets/feathericons/filter.svg       |   1 +
 src/apps/qcam/assets/feathericons/flag.svg         |   1 +
 src/apps/qcam/assets/feathericons/folder-minus.svg |   1 +
 src/apps/qcam/assets/feathericons/folder-plus.svg  |   1 +
 src/apps/qcam/assets/feathericons/folder.svg       |   1 +
 src/apps/qcam/assets/feathericons/framer.svg       |   1 +
 src/apps/qcam/assets/feathericons/frown.svg        |   1 +
 src/apps/qcam/assets/feathericons/gift.svg         |   1 +
 src/apps/qcam/assets/feathericons/git-branch.svg   |   1 +
 src/apps/qcam/assets/feathericons/git-commit.svg   |   1 +
 src/apps/qcam/assets/feathericons/git-merge.svg    |   1 +
 .../qcam/assets/feathericons/git-pull-request.svg  |   1 +
 src/apps/qcam/assets/feathericons/github.svg       |   1 +
 src/apps/qcam/assets/feathericons/gitlab.svg       |   1 +
 src/apps/qcam/assets/feathericons/globe.svg        |   1 +
 src/apps/qcam/assets/feathericons/grid.svg         |   1 +
 src/apps/qcam/assets/feathericons/hard-drive.svg   |   1 +
 src/apps/qcam/assets/feathericons/hash.svg         |   1 +
 src/apps/qcam/assets/feathericons/headphones.svg   |   1 +
 src/apps/qcam/assets/feathericons/heart.svg        |   1 +
 src/apps/qcam/assets/feathericons/help-circle.svg  |   1 +
 src/apps/qcam/assets/feathericons/hexagon.svg      |   1 +
 src/apps/qcam/assets/feathericons/home.svg         |   1 +
 src/apps/qcam/assets/feathericons/image.svg        |   1 +
 src/apps/qcam/assets/feathericons/inbox.svg        |   1 +
 src/apps/qcam/assets/feathericons/info.svg         |   1 +
 src/apps/qcam/assets/feathericons/instagram.svg    |   1 +
 src/apps/qcam/assets/feathericons/italic.svg       |   1 +
 src/apps/qcam/assets/feathericons/key.svg          |   1 +
 src/apps/qcam/assets/feathericons/layers.svg       |   1 +
 src/apps/qcam/assets/feathericons/layout.svg       |   1 +
 src/apps/qcam/assets/feathericons/life-buoy.svg    |   1 +
 src/apps/qcam/assets/feathericons/link-2.svg       |   1 +
 src/apps/qcam/assets/feathericons/link.svg         |   1 +
 src/apps/qcam/assets/feathericons/linkedin.svg     |   1 +
 src/apps/qcam/assets/feathericons/list.svg         |   1 +
 src/apps/qcam/assets/feathericons/loader.svg       |   1 +
 src/apps/qcam/assets/feathericons/lock.svg         |   1 +
 src/apps/qcam/assets/feathericons/log-in.svg       |   1 +
 src/apps/qcam/assets/feathericons/log-out.svg      |   1 +
 src/apps/qcam/assets/feathericons/mail.svg         |   1 +
 src/apps/qcam/assets/feathericons/map-pin.svg      |   1 +
 src/apps/qcam/assets/feathericons/map.svg          |   1 +
 src/apps/qcam/assets/feathericons/maximize-2.svg   |   1 +
 src/apps/qcam/assets/feathericons/maximize.svg     |   1 +
 src/apps/qcam/assets/feathericons/meh.svg          |   1 +
 src/apps/qcam/assets/feathericons/menu.svg         |   1 +
 .../qcam/assets/feathericons/message-circle.svg    |   1 +
 .../qcam/assets/feathericons/message-square.svg    |   1 +
 src/apps/qcam/assets/feathericons/mic-off.svg      |   1 +
 src/apps/qcam/assets/feathericons/mic.svg          |   1 +
 src/apps/qcam/assets/feathericons/minimize-2.svg   |   1 +
 src/apps/qcam/assets/feathericons/minimize.svg     |   1 +
 src/apps/qcam/assets/feathericons/minus-circle.svg |   1 +
 src/apps/qcam/assets/feathericons/minus-square.svg |   1 +
 src/apps/qcam/assets/feathericons/minus.svg        |   1 +
 src/apps/qcam/assets/feathericons/monitor.svg      |   1 +
 src/apps/qcam/assets/feathericons/moon.svg         |   1 +
 .../qcam/assets/feathericons/more-horizontal.svg   |   1 +
 .../qcam/assets/feathericons/more-vertical.svg     |   1 +
 .../qcam/assets/feathericons/mouse-pointer.svg     |   1 +
 src/apps/qcam/assets/feathericons/move.svg         |   1 +
 src/apps/qcam/assets/feathericons/music.svg        |   1 +
 src/apps/qcam/assets/feathericons/navigation-2.svg |   1 +
 src/apps/qcam/assets/feathericons/navigation.svg   |   1 +
 src/apps/qcam/assets/feathericons/octagon.svg      |   1 +
 src/apps/qcam/assets/feathericons/package.svg      |   1 +
 src/apps/qcam/assets/feathericons/paperclip.svg    |   1 +
 src/apps/qcam/assets/feathericons/pause-circle.svg |   1 +
 src/apps/qcam/assets/feathericons/pause.svg        |   1 +
 src/apps/qcam/assets/feathericons/pen-tool.svg     |   1 +
 src/apps/qcam/assets/feathericons/percent.svg      |   1 +
 src/apps/qcam/assets/feathericons/phone-call.svg   |   1 +
 .../qcam/assets/feathericons/phone-forwarded.svg   |   1 +
 .../qcam/assets/feathericons/phone-incoming.svg    |   1 +
 src/apps/qcam/assets/feathericons/phone-missed.svg |   1 +
 src/apps/qcam/assets/feathericons/phone-off.svg    |   1 +
 .../qcam/assets/feathericons/phone-outgoing.svg    |   1 +
 src/apps/qcam/assets/feathericons/phone.svg        |   1 +
 src/apps/qcam/assets/feathericons/pie-chart.svg    |   1 +
 src/apps/qcam/assets/feathericons/play-circle.svg  |   1 +
 src/apps/qcam/assets/feathericons/play.svg         |   1 +
 src/apps/qcam/assets/feathericons/plus-circle.svg  |   1 +
 src/apps/qcam/assets/feathericons/plus-square.svg  |   1 +
 src/apps/qcam/assets/feathericons/plus.svg         |   1 +
 src/apps/qcam/assets/feathericons/pocket.svg       |   1 +
 src/apps/qcam/assets/feathericons/power.svg        |   1 +
 src/apps/qcam/assets/feathericons/printer.svg      |   1 +
 src/apps/qcam/assets/feathericons/radio.svg        |   1 +
 src/apps/qcam/assets/feathericons/refresh-ccw.svg  |   1 +
 src/apps/qcam/assets/feathericons/refresh-cw.svg   |   1 +
 src/apps/qcam/assets/feathericons/repeat.svg       |   1 +
 src/apps/qcam/assets/feathericons/rewind.svg       |   1 +
 src/apps/qcam/assets/feathericons/rotate-ccw.svg   |   1 +
 src/apps/qcam/assets/feathericons/rotate-cw.svg    |   1 +
 src/apps/qcam/assets/feathericons/rss.svg          |   1 +
 src/apps/qcam/assets/feathericons/save.svg         |   1 +
 src/apps/qcam/assets/feathericons/scissors.svg     |   1 +
 src/apps/qcam/assets/feathericons/search.svg       |   1 +
 src/apps/qcam/assets/feathericons/send.svg         |   1 +
 src/apps/qcam/assets/feathericons/server.svg       |   1 +
 src/apps/qcam/assets/feathericons/settings.svg     |   1 +
 src/apps/qcam/assets/feathericons/share-2.svg      |   1 +
 src/apps/qcam/assets/feathericons/share.svg        |   1 +
 src/apps/qcam/assets/feathericons/shield-off.svg   |   1 +
 src/apps/qcam/assets/feathericons/shield.svg       |   1 +
 src/apps/qcam/assets/feathericons/shopping-bag.svg |   1 +
 .../qcam/assets/feathericons/shopping-cart.svg     |   1 +
 src/apps/qcam/assets/feathericons/shuffle.svg      |   1 +
 src/apps/qcam/assets/feathericons/sidebar.svg      |   1 +
 src/apps/qcam/assets/feathericons/skip-back.svg    |   1 +
 src/apps/qcam/assets/feathericons/skip-forward.svg |   1 +
 src/apps/qcam/assets/feathericons/slack.svg        |   1 +
 src/apps/qcam/assets/feathericons/slash.svg        |   1 +
 src/apps/qcam/assets/feathericons/sliders.svg      |   1 +
 src/apps/qcam/assets/feathericons/smartphone.svg   |   1 +
 src/apps/qcam/assets/feathericons/smile.svg        |   1 +
 src/apps/qcam/assets/feathericons/speaker.svg      |   1 +
 src/apps/qcam/assets/feathericons/square.svg       |   1 +
 src/apps/qcam/assets/feathericons/star.svg         |   1 +
 src/apps/qcam/assets/feathericons/stop-circle.svg  |   1 +
 src/apps/qcam/assets/feathericons/sun.svg          |   1 +
 src/apps/qcam/assets/feathericons/sunrise.svg      |   1 +
 src/apps/qcam/assets/feathericons/sunset.svg       |   1 +
 src/apps/qcam/assets/feathericons/tablet.svg       |   1 +
 src/apps/qcam/assets/feathericons/tag.svg          |   1 +
 src/apps/qcam/assets/feathericons/target.svg       |   1 +
 src/apps/qcam/assets/feathericons/terminal.svg     |   1 +
 src/apps/qcam/assets/feathericons/thermometer.svg  |   1 +
 src/apps/qcam/assets/feathericons/thumbs-down.svg  |   1 +
 src/apps/qcam/assets/feathericons/thumbs-up.svg    |   1 +
 src/apps/qcam/assets/feathericons/toggle-left.svg  |   1 +
 src/apps/qcam/assets/feathericons/toggle-right.svg |   1 +
 src/apps/qcam/assets/feathericons/tool.svg         |   1 +
 src/apps/qcam/assets/feathericons/trash-2.svg      |   1 +
 src/apps/qcam/assets/feathericons/trash.svg        |   1 +
 src/apps/qcam/assets/feathericons/trello.svg       |   1 +
 .../qcam/assets/feathericons/trending-down.svg     |   1 +
 src/apps/qcam/assets/feathericons/trending-up.svg  |   1 +
 src/apps/qcam/assets/feathericons/triangle.svg     |   1 +
 src/apps/qcam/assets/feathericons/truck.svg        |   1 +
 src/apps/qcam/assets/feathericons/tv.svg           |   1 +
 src/apps/qcam/assets/feathericons/twitch.svg       |   1 +
 src/apps/qcam/assets/feathericons/twitter.svg      |   1 +
 src/apps/qcam/assets/feathericons/type.svg         |   1 +
 src/apps/qcam/assets/feathericons/umbrella.svg     |   1 +
 src/apps/qcam/assets/feathericons/underline.svg    |   1 +
 src/apps/qcam/assets/feathericons/unlock.svg       |   1 +
 src/apps/qcam/assets/feathericons/upload-cloud.svg |   1 +
 src/apps/qcam/assets/feathericons/upload.svg       |   1 +
 src/apps/qcam/assets/feathericons/user-check.svg   |   1 +
 src/apps/qcam/assets/feathericons/user-minus.svg   |   1 +
 src/apps/qcam/assets/feathericons/user-plus.svg    |   1 +
 src/apps/qcam/assets/feathericons/user-x.svg       |   1 +
 src/apps/qcam/assets/feathericons/user.svg         |   1 +
 src/apps/qcam/assets/feathericons/users.svg        |   1 +
 src/apps/qcam/assets/feathericons/video-off.svg    |   1 +
 src/apps/qcam/assets/feathericons/video.svg        |   1 +
 src/apps/qcam/assets/feathericons/voicemail.svg    |   1 +
 src/apps/qcam/assets/feathericons/volume-1.svg     |   1 +
 src/apps/qcam/assets/feathericons/volume-2.svg     |   1 +
 src/apps/qcam/assets/feathericons/volume-x.svg     |   1 +
 src/apps/qcam/assets/feathericons/volume.svg       |   1 +
 src/apps/qcam/assets/feathericons/watch.svg        |   1 +
 src/apps/qcam/assets/feathericons/wifi-off.svg     |   1 +
 src/apps/qcam/assets/feathericons/wifi.svg         |   1 +
 src/apps/qcam/assets/feathericons/wind.svg         |   1 +
 src/apps/qcam/assets/feathericons/x-circle.svg     |   1 +
 src/apps/qcam/assets/feathericons/x-octagon.svg    |   1 +
 src/apps/qcam/assets/feathericons/x-square.svg     |   1 +
 src/apps/qcam/assets/feathericons/x.svg            |   1 +
 src/apps/qcam/assets/feathericons/youtube.svg      |   1 +
 src/apps/qcam/assets/feathericons/zap-off.svg      |   1 +
 src/apps/qcam/assets/feathericons/zap.svg          |   1 +
 src/apps/qcam/assets/feathericons/zoom-in.svg      |   1 +
 src/apps/qcam/assets/feathericons/zoom-out.svg     |   1 +
 src/apps/qcam/assets/shader/RGB.frag               |  22 +
 src/apps/qcam/assets/shader/YUV_2_planes.frag      |  42 ++
 src/apps/qcam/assets/shader/YUV_3_planes.frag      |  36 +
 src/apps/qcam/assets/shader/YUV_packed.frag        |  83 ++
 src/apps/qcam/assets/shader/bayer_1x_packed.frag   | 216 ++++++
 src/apps/qcam/assets/shader/bayer_8.frag           | 107 +++
 src/apps/qcam/assets/shader/bayer_8.vert           |  51 ++
 src/apps/qcam/assets/shader/identity.vert          |  18 +
 src/apps/qcam/assets/shader/shaders.qrc            |  13 +
 src/apps/qcam/cam_select_dialog.cpp                | 111 +++
 src/apps/qcam/cam_select_dialog.h                  |  47 ++
 src/apps/qcam/format_converter.cpp                 | 359 +++++++++
 src/apps/qcam/format_converter.h                   |  62 ++
 src/apps/qcam/main.cpp                             |  89 +++
 src/apps/qcam/main_window.cpp                      | 790 +++++++++++++++++++
 src/apps/qcam/main_window.h                        | 133 ++++
 src/apps/qcam/meson.build                          |  83 ++
 src/apps/qcam/message_handler.cpp                  |  27 +
 src/apps/qcam/message_handler.h                    |  24 +
 src/apps/qcam/viewfinder.h                         |  34 +
 src/apps/qcam/viewfinder_gl.cpp                    | 835 +++++++++++++++++++++
 src/apps/qcam/viewfinder_gl.h                      | 108 +++
 src/apps/qcam/viewfinder_qt.cpp                    | 181 +++++
 src/apps/qcam/viewfinder_qt.h                      |  64 ++
 307 files changed, 3828 insertions(+)
 create mode 100644 src/apps/qcam/assets/feathericons/activity.svg
 create mode 100644 src/apps/qcam/assets/feathericons/airplay.svg
 create mode 100644 src/apps/qcam/assets/feathericons/alert-circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/alert-octagon.svg
 create mode 100644 src/apps/qcam/assets/feathericons/alert-triangle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/align-center.svg
 create mode 100644 src/apps/qcam/assets/feathericons/align-justify.svg
 create mode 100644 src/apps/qcam/assets/feathericons/align-left.svg
 create mode 100644 src/apps/qcam/assets/feathericons/align-right.svg
 create mode 100644 src/apps/qcam/assets/feathericons/anchor.svg
 create mode 100644 src/apps/qcam/assets/feathericons/aperture.svg
 create mode 100644 src/apps/qcam/assets/feathericons/archive.svg
 create mode 100644 src/apps/qcam/assets/feathericons/arrow-down-circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/arrow-down-left.svg
 create mode 100644 src/apps/qcam/assets/feathericons/arrow-down-right.svg
 create mode 100644 src/apps/qcam/assets/feathericons/arrow-down.svg
 create mode 100644 src/apps/qcam/assets/feathericons/arrow-left-circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/arrow-left.svg
 create mode 100644 src/apps/qcam/assets/feathericons/arrow-right-circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/arrow-right.svg
 create mode 100644 src/apps/qcam/assets/feathericons/arrow-up-circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/arrow-up-left.svg
 create mode 100644 src/apps/qcam/assets/feathericons/arrow-up-right.svg
 create mode 100644 src/apps/qcam/assets/feathericons/arrow-up.svg
 create mode 100644 src/apps/qcam/assets/feathericons/at-sign.svg
 create mode 100644 src/apps/qcam/assets/feathericons/award.svg
 create mode 100644 src/apps/qcam/assets/feathericons/bar-chart-2.svg
 create mode 100644 src/apps/qcam/assets/feathericons/bar-chart.svg
 create mode 100644 src/apps/qcam/assets/feathericons/battery-charging.svg
 create mode 100644 src/apps/qcam/assets/feathericons/battery.svg
 create mode 100644 src/apps/qcam/assets/feathericons/bell-off.svg
 create mode 100644 src/apps/qcam/assets/feathericons/bell.svg
 create mode 100644 src/apps/qcam/assets/feathericons/bluetooth.svg
 create mode 100644 src/apps/qcam/assets/feathericons/bold.svg
 create mode 100644 src/apps/qcam/assets/feathericons/book-open.svg
 create mode 100644 src/apps/qcam/assets/feathericons/book.svg
 create mode 100644 src/apps/qcam/assets/feathericons/bookmark.svg
 create mode 100644 src/apps/qcam/assets/feathericons/box.svg
 create mode 100644 src/apps/qcam/assets/feathericons/briefcase.svg
 create mode 100644 src/apps/qcam/assets/feathericons/calendar.svg
 create mode 100644 src/apps/qcam/assets/feathericons/camera-off.svg
 create mode 100644 src/apps/qcam/assets/feathericons/camera.svg
 create mode 100644 src/apps/qcam/assets/feathericons/cast.svg
 create mode 100644 src/apps/qcam/assets/feathericons/check-circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/check-square.svg
 create mode 100644 src/apps/qcam/assets/feathericons/check.svg
 create mode 100644 src/apps/qcam/assets/feathericons/chevron-down.svg
 create mode 100644 src/apps/qcam/assets/feathericons/chevron-left.svg
 create mode 100644 src/apps/qcam/assets/feathericons/chevron-right.svg
 create mode 100644 src/apps/qcam/assets/feathericons/chevron-up.svg
 create mode 100644 src/apps/qcam/assets/feathericons/chevrons-down.svg
 create mode 100644 src/apps/qcam/assets/feathericons/chevrons-left.svg
 create mode 100644 src/apps/qcam/assets/feathericons/chevrons-right.svg
 create mode 100644 src/apps/qcam/assets/feathericons/chevrons-up.svg
 create mode 100644 src/apps/qcam/assets/feathericons/chrome.svg
 create mode 100644 src/apps/qcam/assets/feathericons/circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/clipboard.svg
 create mode 100644 src/apps/qcam/assets/feathericons/clock.svg
 create mode 100644 src/apps/qcam/assets/feathericons/cloud-drizzle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/cloud-lightning.svg
 create mode 100644 src/apps/qcam/assets/feathericons/cloud-off.svg
 create mode 100644 src/apps/qcam/assets/feathericons/cloud-rain.svg
 create mode 100644 src/apps/qcam/assets/feathericons/cloud-snow.svg
 create mode 100644 src/apps/qcam/assets/feathericons/cloud.svg
 create mode 100644 src/apps/qcam/assets/feathericons/code.svg
 create mode 100644 src/apps/qcam/assets/feathericons/codepen.svg
 create mode 100644 src/apps/qcam/assets/feathericons/codesandbox.svg
 create mode 100644 src/apps/qcam/assets/feathericons/coffee.svg
 create mode 100644 src/apps/qcam/assets/feathericons/columns.svg
 create mode 100644 src/apps/qcam/assets/feathericons/command.svg
 create mode 100644 src/apps/qcam/assets/feathericons/compass.svg
 create mode 100644 src/apps/qcam/assets/feathericons/copy.svg
 create mode 100644 src/apps/qcam/assets/feathericons/corner-down-left.svg
 create mode 100644 src/apps/qcam/assets/feathericons/corner-down-right.svg
 create mode 100644 src/apps/qcam/assets/feathericons/corner-left-down.svg
 create mode 100644 src/apps/qcam/assets/feathericons/corner-left-up.svg
 create mode 100644 src/apps/qcam/assets/feathericons/corner-right-down.svg
 create mode 100644 src/apps/qcam/assets/feathericons/corner-right-up.svg
 create mode 100644 src/apps/qcam/assets/feathericons/corner-up-left.svg
 create mode 100644 src/apps/qcam/assets/feathericons/corner-up-right.svg
 create mode 100644 src/apps/qcam/assets/feathericons/cpu.svg
 create mode 100644 src/apps/qcam/assets/feathericons/credit-card.svg
 create mode 100644 src/apps/qcam/assets/feathericons/crop.svg
 create mode 100644 src/apps/qcam/assets/feathericons/crosshair.svg
 create mode 100644 src/apps/qcam/assets/feathericons/database.svg
 create mode 100644 src/apps/qcam/assets/feathericons/delete.svg
 create mode 100644 src/apps/qcam/assets/feathericons/disc.svg
 create mode 100644 src/apps/qcam/assets/feathericons/dollar-sign.svg
 create mode 100644 src/apps/qcam/assets/feathericons/download-cloud.svg
 create mode 100644 src/apps/qcam/assets/feathericons/download.svg
 create mode 100644 src/apps/qcam/assets/feathericons/droplet.svg
 create mode 100644 src/apps/qcam/assets/feathericons/edit-2.svg
 create mode 100644 src/apps/qcam/assets/feathericons/edit-3.svg
 create mode 100644 src/apps/qcam/assets/feathericons/edit.svg
 create mode 100644 src/apps/qcam/assets/feathericons/external-link.svg
 create mode 100644 src/apps/qcam/assets/feathericons/eye-off.svg
 create mode 100644 src/apps/qcam/assets/feathericons/eye.svg
 create mode 100644 src/apps/qcam/assets/feathericons/facebook.svg
 create mode 100644 src/apps/qcam/assets/feathericons/fast-forward.svg
 create mode 100644 src/apps/qcam/assets/feathericons/feather.svg
 create mode 100644 src/apps/qcam/assets/feathericons/feathericons.qrc
 create mode 100644 src/apps/qcam/assets/feathericons/figma.svg
 create mode 100644 src/apps/qcam/assets/feathericons/file-minus.svg
 create mode 100644 src/apps/qcam/assets/feathericons/file-plus.svg
 create mode 100644 src/apps/qcam/assets/feathericons/file-text.svg
 create mode 100644 src/apps/qcam/assets/feathericons/file.svg
 create mode 100644 src/apps/qcam/assets/feathericons/film.svg
 create mode 100644 src/apps/qcam/assets/feathericons/filter.svg
 create mode 100644 src/apps/qcam/assets/feathericons/flag.svg
 create mode 100644 src/apps/qcam/assets/feathericons/folder-minus.svg
 create mode 100644 src/apps/qcam/assets/feathericons/folder-plus.svg
 create mode 100644 src/apps/qcam/assets/feathericons/folder.svg
 create mode 100644 src/apps/qcam/assets/feathericons/framer.svg
 create mode 100644 src/apps/qcam/assets/feathericons/frown.svg
 create mode 100644 src/apps/qcam/assets/feathericons/gift.svg
 create mode 100644 src/apps/qcam/assets/feathericons/git-branch.svg
 create mode 100644 src/apps/qcam/assets/feathericons/git-commit.svg
 create mode 100644 src/apps/qcam/assets/feathericons/git-merge.svg
 create mode 100644 src/apps/qcam/assets/feathericons/git-pull-request.svg
 create mode 100644 src/apps/qcam/assets/feathericons/github.svg
 create mode 100644 src/apps/qcam/assets/feathericons/gitlab.svg
 create mode 100644 src/apps/qcam/assets/feathericons/globe.svg
 create mode 100644 src/apps/qcam/assets/feathericons/grid.svg
 create mode 100644 src/apps/qcam/assets/feathericons/hard-drive.svg
 create mode 100644 src/apps/qcam/assets/feathericons/hash.svg
 create mode 100644 src/apps/qcam/assets/feathericons/headphones.svg
 create mode 100644 src/apps/qcam/assets/feathericons/heart.svg
 create mode 100644 src/apps/qcam/assets/feathericons/help-circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/hexagon.svg
 create mode 100644 src/apps/qcam/assets/feathericons/home.svg
 create mode 100644 src/apps/qcam/assets/feathericons/image.svg
 create mode 100644 src/apps/qcam/assets/feathericons/inbox.svg
 create mode 100644 src/apps/qcam/assets/feathericons/info.svg
 create mode 100644 src/apps/qcam/assets/feathericons/instagram.svg
 create mode 100644 src/apps/qcam/assets/feathericons/italic.svg
 create mode 100644 src/apps/qcam/assets/feathericons/key.svg
 create mode 100644 src/apps/qcam/assets/feathericons/layers.svg
 create mode 100644 src/apps/qcam/assets/feathericons/layout.svg
 create mode 100644 src/apps/qcam/assets/feathericons/life-buoy.svg
 create mode 100644 src/apps/qcam/assets/feathericons/link-2.svg
 create mode 100644 src/apps/qcam/assets/feathericons/link.svg
 create mode 100644 src/apps/qcam/assets/feathericons/linkedin.svg
 create mode 100644 src/apps/qcam/assets/feathericons/list.svg
 create mode 100644 src/apps/qcam/assets/feathericons/loader.svg
 create mode 100644 src/apps/qcam/assets/feathericons/lock.svg
 create mode 100644 src/apps/qcam/assets/feathericons/log-in.svg
 create mode 100644 src/apps/qcam/assets/feathericons/log-out.svg
 create mode 100644 src/apps/qcam/assets/feathericons/mail.svg
 create mode 100644 src/apps/qcam/assets/feathericons/map-pin.svg
 create mode 100644 src/apps/qcam/assets/feathericons/map.svg
 create mode 100644 src/apps/qcam/assets/feathericons/maximize-2.svg
 create mode 100644 src/apps/qcam/assets/feathericons/maximize.svg
 create mode 100644 src/apps/qcam/assets/feathericons/meh.svg
 create mode 100644 src/apps/qcam/assets/feathericons/menu.svg
 create mode 100644 src/apps/qcam/assets/feathericons/message-circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/message-square.svg
 create mode 100644 src/apps/qcam/assets/feathericons/mic-off.svg
 create mode 100644 src/apps/qcam/assets/feathericons/mic.svg
 create mode 100644 src/apps/qcam/assets/feathericons/minimize-2.svg
 create mode 100644 src/apps/qcam/assets/feathericons/minimize.svg
 create mode 100644 src/apps/qcam/assets/feathericons/minus-circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/minus-square.svg
 create mode 100644 src/apps/qcam/assets/feathericons/minus.svg
 create mode 100644 src/apps/qcam/assets/feathericons/monitor.svg
 create mode 100644 src/apps/qcam/assets/feathericons/moon.svg
 create mode 100644 src/apps/qcam/assets/feathericons/more-horizontal.svg
 create mode 100644 src/apps/qcam/assets/feathericons/more-vertical.svg
 create mode 100644 src/apps/qcam/assets/feathericons/mouse-pointer.svg
 create mode 100644 src/apps/qcam/assets/feathericons/move.svg
 create mode 100644 src/apps/qcam/assets/feathericons/music.svg
 create mode 100644 src/apps/qcam/assets/feathericons/navigation-2.svg
 create mode 100644 src/apps/qcam/assets/feathericons/navigation.svg
 create mode 100644 src/apps/qcam/assets/feathericons/octagon.svg
 create mode 100644 src/apps/qcam/assets/feathericons/package.svg
 create mode 100644 src/apps/qcam/assets/feathericons/paperclip.svg
 create mode 100644 src/apps/qcam/assets/feathericons/pause-circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/pause.svg
 create mode 100644 src/apps/qcam/assets/feathericons/pen-tool.svg
 create mode 100644 src/apps/qcam/assets/feathericons/percent.svg
 create mode 100644 src/apps/qcam/assets/feathericons/phone-call.svg
 create mode 100644 src/apps/qcam/assets/feathericons/phone-forwarded.svg
 create mode 100644 src/apps/qcam/assets/feathericons/phone-incoming.svg
 create mode 100644 src/apps/qcam/assets/feathericons/phone-missed.svg
 create mode 100644 src/apps/qcam/assets/feathericons/phone-off.svg
 create mode 100644 src/apps/qcam/assets/feathericons/phone-outgoing.svg
 create mode 100644 src/apps/qcam/assets/feathericons/phone.svg
 create mode 100644 src/apps/qcam/assets/feathericons/pie-chart.svg
 create mode 100644 src/apps/qcam/assets/feathericons/play-circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/play.svg
 create mode 100644 src/apps/qcam/assets/feathericons/plus-circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/plus-square.svg
 create mode 100644 src/apps/qcam/assets/feathericons/plus.svg
 create mode 100644 src/apps/qcam/assets/feathericons/pocket.svg
 create mode 100644 src/apps/qcam/assets/feathericons/power.svg
 create mode 100644 src/apps/qcam/assets/feathericons/printer.svg
 create mode 100644 src/apps/qcam/assets/feathericons/radio.svg
 create mode 100644 src/apps/qcam/assets/feathericons/refresh-ccw.svg
 create mode 100644 src/apps/qcam/assets/feathericons/refresh-cw.svg
 create mode 100644 src/apps/qcam/assets/feathericons/repeat.svg
 create mode 100644 src/apps/qcam/assets/feathericons/rewind.svg
 create mode 100644 src/apps/qcam/assets/feathericons/rotate-ccw.svg
 create mode 100644 src/apps/qcam/assets/feathericons/rotate-cw.svg
 create mode 100644 src/apps/qcam/assets/feathericons/rss.svg
 create mode 100644 src/apps/qcam/assets/feathericons/save.svg
 create mode 100644 src/apps/qcam/assets/feathericons/scissors.svg
 create mode 100644 src/apps/qcam/assets/feathericons/search.svg
 create mode 100644 src/apps/qcam/assets/feathericons/send.svg
 create mode 100644 src/apps/qcam/assets/feathericons/server.svg
 create mode 100644 src/apps/qcam/assets/feathericons/settings.svg
 create mode 100644 src/apps/qcam/assets/feathericons/share-2.svg
 create mode 100644 src/apps/qcam/assets/feathericons/share.svg
 create mode 100644 src/apps/qcam/assets/feathericons/shield-off.svg
 create mode 100644 src/apps/qcam/assets/feathericons/shield.svg
 create mode 100644 src/apps/qcam/assets/feathericons/shopping-bag.svg
 create mode 100644 src/apps/qcam/assets/feathericons/shopping-cart.svg
 create mode 100644 src/apps/qcam/assets/feathericons/shuffle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/sidebar.svg
 create mode 100644 src/apps/qcam/assets/feathericons/skip-back.svg
 create mode 100644 src/apps/qcam/assets/feathericons/skip-forward.svg
 create mode 100644 src/apps/qcam/assets/feathericons/slack.svg
 create mode 100644 src/apps/qcam/assets/feathericons/slash.svg
 create mode 100644 src/apps/qcam/assets/feathericons/sliders.svg
 create mode 100644 src/apps/qcam/assets/feathericons/smartphone.svg
 create mode 100644 src/apps/qcam/assets/feathericons/smile.svg
 create mode 100644 src/apps/qcam/assets/feathericons/speaker.svg
 create mode 100644 src/apps/qcam/assets/feathericons/square.svg
 create mode 100644 src/apps/qcam/assets/feathericons/star.svg
 create mode 100644 src/apps/qcam/assets/feathericons/stop-circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/sun.svg
 create mode 100644 src/apps/qcam/assets/feathericons/sunrise.svg
 create mode 100644 src/apps/qcam/assets/feathericons/sunset.svg
 create mode 100644 src/apps/qcam/assets/feathericons/tablet.svg
 create mode 100644 src/apps/qcam/assets/feathericons/tag.svg
 create mode 100644 src/apps/qcam/assets/feathericons/target.svg
 create mode 100644 src/apps/qcam/assets/feathericons/terminal.svg
 create mode 100644 src/apps/qcam/assets/feathericons/thermometer.svg
 create mode 100644 src/apps/qcam/assets/feathericons/thumbs-down.svg
 create mode 100644 src/apps/qcam/assets/feathericons/thumbs-up.svg
 create mode 100644 src/apps/qcam/assets/feathericons/toggle-left.svg
 create mode 100644 src/apps/qcam/assets/feathericons/toggle-right.svg
 create mode 100644 src/apps/qcam/assets/feathericons/tool.svg
 create mode 100644 src/apps/qcam/assets/feathericons/trash-2.svg
 create mode 100644 src/apps/qcam/assets/feathericons/trash.svg
 create mode 100644 src/apps/qcam/assets/feathericons/trello.svg
 create mode 100644 src/apps/qcam/assets/feathericons/trending-down.svg
 create mode 100644 src/apps/qcam/assets/feathericons/trending-up.svg
 create mode 100644 src/apps/qcam/assets/feathericons/triangle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/truck.svg
 create mode 100644 src/apps/qcam/assets/feathericons/tv.svg
 create mode 100644 src/apps/qcam/assets/feathericons/twitch.svg
 create mode 100644 src/apps/qcam/assets/feathericons/twitter.svg
 create mode 100644 src/apps/qcam/assets/feathericons/type.svg
 create mode 100644 src/apps/qcam/assets/feathericons/umbrella.svg
 create mode 100644 src/apps/qcam/assets/feathericons/underline.svg
 create mode 100644 src/apps/qcam/assets/feathericons/unlock.svg
 create mode 100644 src/apps/qcam/assets/feathericons/upload-cloud.svg
 create mode 100644 src/apps/qcam/assets/feathericons/upload.svg
 create mode 100644 src/apps/qcam/assets/feathericons/user-check.svg
 create mode 100644 src/apps/qcam/assets/feathericons/user-minus.svg
 create mode 100644 src/apps/qcam/assets/feathericons/user-plus.svg
 create mode 100644 src/apps/qcam/assets/feathericons/user-x.svg
 create mode 100644 src/apps/qcam/assets/feathericons/user.svg
 create mode 100644 src/apps/qcam/assets/feathericons/users.svg
 create mode 100644 src/apps/qcam/assets/feathericons/video-off.svg
 create mode 100644 src/apps/qcam/assets/feathericons/video.svg
 create mode 100644 src/apps/qcam/assets/feathericons/voicemail.svg
 create mode 100644 src/apps/qcam/assets/feathericons/volume-1.svg
 create mode 100644 src/apps/qcam/assets/feathericons/volume-2.svg
 create mode 100644 src/apps/qcam/assets/feathericons/volume-x.svg
 create mode 100644 src/apps/qcam/assets/feathericons/volume.svg
 create mode 100644 src/apps/qcam/assets/feathericons/watch.svg
 create mode 100644 src/apps/qcam/assets/feathericons/wifi-off.svg
 create mode 100644 src/apps/qcam/assets/feathericons/wifi.svg
 create mode 100644 src/apps/qcam/assets/feathericons/wind.svg
 create mode 100644 src/apps/qcam/assets/feathericons/x-circle.svg
 create mode 100644 src/apps/qcam/assets/feathericons/x-octagon.svg
 create mode 100644 src/apps/qcam/assets/feathericons/x-square.svg
 create mode 100644 src/apps/qcam/assets/feathericons/x.svg
 create mode 100644 src/apps/qcam/assets/feathericons/youtube.svg
 create mode 100644 src/apps/qcam/assets/feathericons/zap-off.svg
 create mode 100644 src/apps/qcam/assets/feathericons/zap.svg
 create mode 100644 src/apps/qcam/assets/feathericons/zoom-in.svg
 create mode 100644 src/apps/qcam/assets/feathericons/zoom-out.svg
 create mode 100644 src/apps/qcam/assets/shader/RGB.frag
 create mode 100644 src/apps/qcam/assets/shader/YUV_2_planes.frag
 create mode 100644 src/apps/qcam/assets/shader/YUV_3_planes.frag
 create mode 100644 src/apps/qcam/assets/shader/YUV_packed.frag
 create mode 100644 src/apps/qcam/assets/shader/bayer_1x_packed.frag
 create mode 100644 src/apps/qcam/assets/shader/bayer_8.frag
 create mode 100644 src/apps/qcam/assets/shader/bayer_8.vert
 create mode 100644 src/apps/qcam/assets/shader/identity.vert
 create mode 100644 src/apps/qcam/assets/shader/shaders.qrc
 create mode 100644 src/apps/qcam/cam_select_dialog.cpp
 create mode 100644 src/apps/qcam/cam_select_dialog.h
 create mode 100644 src/apps/qcam/format_converter.cpp
 create mode 100644 src/apps/qcam/format_converter.h
 create mode 100644 src/apps/qcam/main.cpp
 create mode 100644 src/apps/qcam/main_window.cpp
 create mode 100644 src/apps/qcam/main_window.h
 create mode 100644 src/apps/qcam/meson.build
 create mode 100644 src/apps/qcam/message_handler.cpp
 create mode 100644 src/apps/qcam/message_handler.h
 create mode 100644 src/apps/qcam/viewfinder.h
 create mode 100644 src/apps/qcam/viewfinder_gl.cpp
 create mode 100644 src/apps/qcam/viewfinder_gl.h
 create mode 100644 src/apps/qcam/viewfinder_qt.cpp
 create mode 100644 src/apps/qcam/viewfinder_qt.h

(limited to 'src/apps/qcam')

diff --git a/src/apps/qcam/assets/feathericons/activity.svg b/src/apps/qcam/assets/feathericons/activity.svg
new file mode 100644
index 00000000..669a57a7
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/activity.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-activity"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/airplay.svg b/src/apps/qcam/assets/feathericons/airplay.svg
new file mode 100644
index 00000000..7ce73022
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/airplay.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-airplay"><path d="M5 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-1"></path><polygon points="12 15 17 21 7 21 12 15"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/alert-circle.svg b/src/apps/qcam/assets/feathericons/alert-circle.svg
new file mode 100644
index 00000000..8d02b7d1
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/alert-circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/alert-octagon.svg b/src/apps/qcam/assets/feathericons/alert-octagon.svg
new file mode 100644
index 00000000..de9b03f2
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/alert-octagon.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-octagon"><polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"></polygon><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/alert-triangle.svg b/src/apps/qcam/assets/feathericons/alert-triangle.svg
new file mode 100644
index 00000000..6dcb0963
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/alert-triangle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-triangle"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/align-center.svg b/src/apps/qcam/assets/feathericons/align-center.svg
new file mode 100644
index 00000000..5b8842ea
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/align-center.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-align-center"><line x1="18" y1="10" x2="6" y2="10"></line><line x1="21" y1="6" x2="3" y2="6"></line><line x1="21" y1="14" x2="3" y2="14"></line><line x1="18" y1="18" x2="6" y2="18"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/align-justify.svg b/src/apps/qcam/assets/feathericons/align-justify.svg
new file mode 100644
index 00000000..0539876f
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/align-justify.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-align-justify"><line x1="21" y1="10" x2="3" y2="10"></line><line x1="21" y1="6" x2="3" y2="6"></line><line x1="21" y1="14" x2="3" y2="14"></line><line x1="21" y1="18" x2="3" y2="18"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/align-left.svg b/src/apps/qcam/assets/feathericons/align-left.svg
new file mode 100644
index 00000000..9ac852a5
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/align-left.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-align-left"><line x1="17" y1="10" x2="3" y2="10"></line><line x1="21" y1="6" x2="3" y2="6"></line><line x1="21" y1="14" x2="3" y2="14"></line><line x1="17" y1="18" x2="3" y2="18"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/align-right.svg b/src/apps/qcam/assets/feathericons/align-right.svg
new file mode 100644
index 00000000..ef139ffa
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/align-right.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-align-right"><line x1="21" y1="10" x2="7" y2="10"></line><line x1="21" y1="6" x2="3" y2="6"></line><line x1="21" y1="14" x2="3" y2="14"></line><line x1="21" y1="18" x2="7" y2="18"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/anchor.svg b/src/apps/qcam/assets/feathericons/anchor.svg
new file mode 100644
index 00000000..e01627a3
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/anchor.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-anchor"><circle cx="12" cy="5" r="3"></circle><line x1="12" y1="22" x2="12" y2="8"></line><path d="M5 12H2a10 10 0 0 0 20 0h-3"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/aperture.svg b/src/apps/qcam/assets/feathericons/aperture.svg
new file mode 100644
index 00000000..9936e868
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/aperture.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-aperture"><circle cx="12" cy="12" r="10"></circle><line x1="14.31" y1="8" x2="20.05" y2="17.94"></line><line x1="9.69" y1="8" x2="21.17" y2="8"></line><line x1="7.38" y1="12" x2="13.12" y2="2.06"></line><line x1="9.69" y1="16" x2="3.95" y2="6.06"></line><line x1="14.31" y1="16" x2="2.83" y2="16"></line><line x1="16.62" y1="12" x2="10.88" y2="21.94"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/archive.svg b/src/apps/qcam/assets/feathericons/archive.svg
new file mode 100644
index 00000000..428882c8
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/archive.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-archive"><polyline points="21 8 21 21 3 21 3 8"></polyline><rect x="1" y="3" width="22" height="5"></rect><line x1="10" y1="12" x2="14" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/arrow-down-circle.svg b/src/apps/qcam/assets/feathericons/arrow-down-circle.svg
new file mode 100644
index 00000000..3238091b
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/arrow-down-circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-down-circle"><circle cx="12" cy="12" r="10"></circle><polyline points="8 12 12 16 16 12"></polyline><line x1="12" y1="8" x2="12" y2="16"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/arrow-down-left.svg b/src/apps/qcam/assets/feathericons/arrow-down-left.svg
new file mode 100644
index 00000000..72483584
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/arrow-down-left.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-down-left"><line x1="17" y1="7" x2="7" y2="17"></line><polyline points="17 17 7 17 7 7"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/arrow-down-right.svg b/src/apps/qcam/assets/feathericons/arrow-down-right.svg
new file mode 100644
index 00000000..81d9822b
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/arrow-down-right.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-down-right"><line x1="7" y1="7" x2="17" y2="17"></line><polyline points="17 7 17 17 7 17"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/arrow-down.svg b/src/apps/qcam/assets/feathericons/arrow-down.svg
new file mode 100644
index 00000000..4f84f627
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/arrow-down.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-down"><line x1="12" y1="5" x2="12" y2="19"></line><polyline points="19 12 12 19 5 12"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/arrow-left-circle.svg b/src/apps/qcam/assets/feathericons/arrow-left-circle.svg
new file mode 100644
index 00000000..3b19ff8a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/arrow-left-circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left-circle"><circle cx="12" cy="12" r="10"></circle><polyline points="12 8 8 12 12 16"></polyline><line x1="16" y1="12" x2="8" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/arrow-left.svg b/src/apps/qcam/assets/feathericons/arrow-left.svg
new file mode 100644
index 00000000..a5058fc7
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/arrow-left.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/arrow-right-circle.svg b/src/apps/qcam/assets/feathericons/arrow-right-circle.svg
new file mode 100644
index 00000000..ff01dd58
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/arrow-right-circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right-circle"><circle cx="12" cy="12" r="10"></circle><polyline points="12 16 16 12 12 8"></polyline><line x1="8" y1="12" x2="16" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/arrow-right.svg b/src/apps/qcam/assets/feathericons/arrow-right.svg
new file mode 100644
index 00000000..939b57c5
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/arrow-right.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/arrow-up-circle.svg b/src/apps/qcam/assets/feathericons/arrow-up-circle.svg
new file mode 100644
index 00000000..044a75d3
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/arrow-up-circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-up-circle"><circle cx="12" cy="12" r="10"></circle><polyline points="16 12 12 8 8 12"></polyline><line x1="12" y1="16" x2="12" y2="8"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/arrow-up-left.svg b/src/apps/qcam/assets/feathericons/arrow-up-left.svg
new file mode 100644
index 00000000..cea55e87
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/arrow-up-left.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-up-left"><line x1="17" y1="17" x2="7" y2="7"></line><polyline points="7 17 7 7 17 7"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/arrow-up-right.svg b/src/apps/qcam/assets/feathericons/arrow-up-right.svg
new file mode 100644
index 00000000..95678e00
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/arrow-up-right.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-up-right"><line x1="7" y1="17" x2="17" y2="7"></line><polyline points="7 7 17 7 17 17"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/arrow-up.svg b/src/apps/qcam/assets/feathericons/arrow-up.svg
new file mode 100644
index 00000000..16b13aba
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/arrow-up.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-up"><line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/at-sign.svg b/src/apps/qcam/assets/feathericons/at-sign.svg
new file mode 100644
index 00000000..5a5e5d0d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/at-sign.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-at-sign"><circle cx="12" cy="12" r="4"></circle><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/award.svg b/src/apps/qcam/assets/feathericons/award.svg
new file mode 100644
index 00000000..be70d5a1
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/award.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-award"><circle cx="12" cy="8" r="7"></circle><polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.88"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/bar-chart-2.svg b/src/apps/qcam/assets/feathericons/bar-chart-2.svg
new file mode 100644
index 00000000..864167a6
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/bar-chart-2.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bar-chart-2"><line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/bar-chart.svg b/src/apps/qcam/assets/feathericons/bar-chart.svg
new file mode 100644
index 00000000..074d7c1a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/bar-chart.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bar-chart"><line x1="12" y1="20" x2="12" y2="10"></line><line x1="18" y1="20" x2="18" y2="4"></line><line x1="6" y1="20" x2="6" y2="16"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/battery-charging.svg b/src/apps/qcam/assets/feathericons/battery-charging.svg
new file mode 100644
index 00000000..644cb59c
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/battery-charging.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-battery-charging"><path d="M5 18H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h3.19M15 6h2a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-3.19"></path><line x1="23" y1="13" x2="23" y2="11"></line><polyline points="11 6 7 12 13 12 9 18"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/battery.svg b/src/apps/qcam/assets/feathericons/battery.svg
new file mode 100644
index 00000000..7fe87710
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/battery.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-battery"><rect x="1" y="6" width="18" height="12" rx="2" ry="2"></rect><line x1="23" y1="13" x2="23" y2="11"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/bell-off.svg b/src/apps/qcam/assets/feathericons/bell-off.svg
new file mode 100644
index 00000000..4b07c848
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/bell-off.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bell-off"><path d="M13.73 21a2 2 0 0 1-3.46 0"></path><path d="M18.63 13A17.89 17.89 0 0 1 18 8"></path><path d="M6.26 6.26A5.86 5.86 0 0 0 6 8c0 7-3 9-3 9h14"></path><path d="M18 8a6 6 0 0 0-9.33-5"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/bell.svg b/src/apps/qcam/assets/feathericons/bell.svg
new file mode 100644
index 00000000..bba561c1
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/bell.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bell"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/bluetooth.svg b/src/apps/qcam/assets/feathericons/bluetooth.svg
new file mode 100644
index 00000000..cebed7b1
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/bluetooth.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bluetooth"><polyline points="6.5 6.5 17.5 17.5 12 23 12 1 17.5 6.5 6.5 17.5"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/bold.svg b/src/apps/qcam/assets/feathericons/bold.svg
new file mode 100644
index 00000000..d1a4efd3
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/bold.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bold"><path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/book-open.svg b/src/apps/qcam/assets/feathericons/book-open.svg
new file mode 100644
index 00000000..5e0ca0ab
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/book-open.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-book-open"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/book.svg b/src/apps/qcam/assets/feathericons/book.svg
new file mode 100644
index 00000000..12ffcbc4
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/book.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-book"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/bookmark.svg b/src/apps/qcam/assets/feathericons/bookmark.svg
new file mode 100644
index 00000000..2239cc58
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/bookmark.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bookmark"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/box.svg b/src/apps/qcam/assets/feathericons/box.svg
new file mode 100644
index 00000000..d89be30f
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/box.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-box"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/briefcase.svg b/src/apps/qcam/assets/feathericons/briefcase.svg
new file mode 100644
index 00000000..e3af0506
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/briefcase.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-briefcase"><rect x="2" y="7" width="20" height="14" rx="2" ry="2"></rect><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/calendar.svg b/src/apps/qcam/assets/feathericons/calendar.svg
new file mode 100644
index 00000000..6c7fd870
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/calendar.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-calendar"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/camera-off.svg b/src/apps/qcam/assets/feathericons/camera-off.svg
new file mode 100644
index 00000000..daa3e25f
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/camera-off.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-camera-off"><line x1="1" y1="1" x2="23" y2="23"></line><path d="M21 21H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h3m3-3h6l2 3h4a2 2 0 0 1 2 2v9.34m-7.72-2.06a4 4 0 1 1-5.56-5.56"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/camera.svg b/src/apps/qcam/assets/feathericons/camera.svg
new file mode 100644
index 00000000..0e7f0603
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/camera.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-camera"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path><circle cx="12" cy="13" r="4"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/cast.svg b/src/apps/qcam/assets/feathericons/cast.svg
new file mode 100644
index 00000000..63c954d9
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/cast.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-cast"><path d="M2 16.1A5 5 0 0 1 5.9 20M2 12.05A9 9 0 0 1 9.95 20M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6"></path><line x1="2" y1="20" x2="2.01" y2="20"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/check-circle.svg b/src/apps/qcam/assets/feathericons/check-circle.svg
new file mode 100644
index 00000000..f2f4fd1a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/check-circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check-circle"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/check-square.svg b/src/apps/qcam/assets/feathericons/check-square.svg
new file mode 100644
index 00000000..72ab7a80
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/check-square.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check-square"><polyline points="9 11 12 14 22 4"></polyline><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/check.svg b/src/apps/qcam/assets/feathericons/check.svg
new file mode 100644
index 00000000..1c209899
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/check.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/chevron-down.svg b/src/apps/qcam/assets/feathericons/chevron-down.svg
new file mode 100644
index 00000000..278c6a31
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/chevron-down.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/chevron-left.svg b/src/apps/qcam/assets/feathericons/chevron-left.svg
new file mode 100644
index 00000000..747d46d9
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/chevron-left.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-left"><polyline points="15 18 9 12 15 6"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/chevron-right.svg b/src/apps/qcam/assets/feathericons/chevron-right.svg
new file mode 100644
index 00000000..258de414
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/chevron-right.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-right"><polyline points="9 18 15 12 9 6"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/chevron-up.svg b/src/apps/qcam/assets/feathericons/chevron-up.svg
new file mode 100644
index 00000000..4eb5ecc3
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/chevron-up.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-up"><polyline points="18 15 12 9 6 15"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/chevrons-down.svg b/src/apps/qcam/assets/feathericons/chevrons-down.svg
new file mode 100644
index 00000000..e67ef2fb
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/chevrons-down.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevrons-down"><polyline points="7 13 12 18 17 13"></polyline><polyline points="7 6 12 11 17 6"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/chevrons-left.svg b/src/apps/qcam/assets/feathericons/chevrons-left.svg
new file mode 100644
index 00000000..c32e3983
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/chevrons-left.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevrons-left"><polyline points="11 17 6 12 11 7"></polyline><polyline points="18 17 13 12 18 7"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/chevrons-right.svg b/src/apps/qcam/assets/feathericons/chevrons-right.svg
new file mode 100644
index 00000000..f5068145
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/chevrons-right.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevrons-right"><polyline points="13 17 18 12 13 7"></polyline><polyline points="6 17 11 12 6 7"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/chevrons-up.svg b/src/apps/qcam/assets/feathericons/chevrons-up.svg
new file mode 100644
index 00000000..0eaf5183
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/chevrons-up.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevrons-up"><polyline points="17 11 12 6 7 11"></polyline><polyline points="17 18 12 13 7 18"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/chrome.svg b/src/apps/qcam/assets/feathericons/chrome.svg
new file mode 100644
index 00000000..9189815e
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/chrome.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chrome"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="4"></circle><line x1="21.17" y1="8" x2="12" y2="8"></line><line x1="3.95" y1="6.06" x2="8.54" y2="14"></line><line x1="10.88" y1="21.94" x2="15.46" y2="14"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/circle.svg b/src/apps/qcam/assets/feathericons/circle.svg
new file mode 100644
index 00000000..b0090882
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-circle"><circle cx="12" cy="12" r="10"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/clipboard.svg b/src/apps/qcam/assets/feathericons/clipboard.svg
new file mode 100644
index 00000000..ccee454d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/clipboard.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-clipboard"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/clock.svg b/src/apps/qcam/assets/feathericons/clock.svg
new file mode 100644
index 00000000..ea3f5e50
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/clock.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-clock"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/cloud-drizzle.svg b/src/apps/qcam/assets/feathericons/cloud-drizzle.svg
new file mode 100644
index 00000000..13af6bb5
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/cloud-drizzle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-cloud-drizzle"><line x1="8" y1="19" x2="8" y2="21"></line><line x1="8" y1="13" x2="8" y2="15"></line><line x1="16" y1="19" x2="16" y2="21"></line><line x1="16" y1="13" x2="16" y2="15"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="12" y1="15" x2="12" y2="17"></line><path d="M20 16.58A5 5 0 0 0 18 7h-1.26A8 8 0 1 0 4 15.25"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/cloud-lightning.svg b/src/apps/qcam/assets/feathericons/cloud-lightning.svg
new file mode 100644
index 00000000..32d154cc
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/cloud-lightning.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-cloud-lightning"><path d="M19 16.9A5 5 0 0 0 18 7h-1.26a8 8 0 1 0-11.62 9"></path><polyline points="13 11 9 17 15 17 11 23"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/cloud-off.svg b/src/apps/qcam/assets/feathericons/cloud-off.svg
new file mode 100644
index 00000000..1e1e7d60
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/cloud-off.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-cloud-off"><path d="M22.61 16.95A5 5 0 0 0 18 10h-1.26a8 8 0 0 0-7.05-6M5 5a8 8 0 0 0 4 15h9a5 5 0 0 0 1.7-.3"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/cloud-rain.svg b/src/apps/qcam/assets/feathericons/cloud-rain.svg
new file mode 100644
index 00000000..3e0b85b0
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/cloud-rain.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-cloud-rain"><line x1="16" y1="13" x2="16" y2="21"></line><line x1="8" y1="13" x2="8" y2="21"></line><line x1="12" y1="15" x2="12" y2="23"></line><path d="M20 16.58A5 5 0 0 0 18 7h-1.26A8 8 0 1 0 4 15.25"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/cloud-snow.svg b/src/apps/qcam/assets/feathericons/cloud-snow.svg
new file mode 100644
index 00000000..e4eb8207
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/cloud-snow.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-cloud-snow"><path d="M20 17.58A5 5 0 0 0 18 8h-1.26A8 8 0 1 0 4 16.25"></path><line x1="8" y1="16" x2="8.01" y2="16"></line><line x1="8" y1="20" x2="8.01" y2="20"></line><line x1="12" y1="18" x2="12.01" y2="18"></line><line x1="12" y1="22" x2="12.01" y2="22"></line><line x1="16" y1="16" x2="16.01" y2="16"></line><line x1="16" y1="20" x2="16.01" y2="20"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/cloud.svg b/src/apps/qcam/assets/feathericons/cloud.svg
new file mode 100644
index 00000000..0ee0c632
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/cloud.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-cloud"><path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/code.svg b/src/apps/qcam/assets/feathericons/code.svg
new file mode 100644
index 00000000..c4954b55
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/code.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-code"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/codepen.svg b/src/apps/qcam/assets/feathericons/codepen.svg
new file mode 100644
index 00000000..ab2a815a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/codepen.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-codepen"><polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2"></polygon><line x1="12" y1="22" x2="12" y2="15.5"></line><polyline points="22 8.5 12 15.5 2 8.5"></polyline><polyline points="2 15.5 12 8.5 22 15.5"></polyline><line x1="12" y1="2" x2="12" y2="8.5"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/codesandbox.svg b/src/apps/qcam/assets/feathericons/codesandbox.svg
new file mode 100644
index 00000000..49848f52
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/codesandbox.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-codesandbox"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="7.5 4.21 12 6.81 16.5 4.21"></polyline><polyline points="7.5 19.79 7.5 14.6 3 12"></polyline><polyline points="21 12 16.5 14.6 16.5 19.79"></polyline><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/coffee.svg b/src/apps/qcam/assets/feathericons/coffee.svg
new file mode 100644
index 00000000..32905e52
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/coffee.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-coffee"><path d="M18 8h1a4 4 0 0 1 0 8h-1"></path><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"></path><line x1="6" y1="1" x2="6" y2="4"></line><line x1="10" y1="1" x2="10" y2="4"></line><line x1="14" y1="1" x2="14" y2="4"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/columns.svg b/src/apps/qcam/assets/feathericons/columns.svg
new file mode 100644
index 00000000..d264b557
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/columns.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-columns"><path d="M12 3h7a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-7m0-18H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h7m0-18v18"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/command.svg b/src/apps/qcam/assets/feathericons/command.svg
new file mode 100644
index 00000000..93f554c3
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/command.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-command"><path d="M18 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 3 3 0 0 0-3-3z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/compass.svg b/src/apps/qcam/assets/feathericons/compass.svg
new file mode 100644
index 00000000..32962608
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/compass.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-compass"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/copy.svg b/src/apps/qcam/assets/feathericons/copy.svg
new file mode 100644
index 00000000..4e0b09f1
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/copy.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-copy"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/corner-down-left.svg b/src/apps/qcam/assets/feathericons/corner-down-left.svg
new file mode 100644
index 00000000..9fffb3e9
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/corner-down-left.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-corner-down-left"><polyline points="9 10 4 15 9 20"></polyline><path d="M20 4v7a4 4 0 0 1-4 4H4"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/corner-down-right.svg b/src/apps/qcam/assets/feathericons/corner-down-right.svg
new file mode 100644
index 00000000..b27d408d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/corner-down-right.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-corner-down-right"><polyline points="15 10 20 15 15 20"></polyline><path d="M4 4v7a4 4 0 0 0 4 4h12"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/corner-left-down.svg b/src/apps/qcam/assets/feathericons/corner-left-down.svg
new file mode 100644
index 00000000..24b8375c
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/corner-left-down.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-corner-left-down"><polyline points="14 15 9 20 4 15"></polyline><path d="M20 4h-7a4 4 0 0 0-4 4v12"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/corner-left-up.svg b/src/apps/qcam/assets/feathericons/corner-left-up.svg
new file mode 100644
index 00000000..e54527cd
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/corner-left-up.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-corner-left-up"><polyline points="14 9 9 4 4 9"></polyline><path d="M20 20h-7a4 4 0 0 1-4-4V4"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/corner-right-down.svg b/src/apps/qcam/assets/feathericons/corner-right-down.svg
new file mode 100644
index 00000000..a49e6d6c
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/corner-right-down.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-corner-right-down"><polyline points="10 15 15 20 20 15"></polyline><path d="M4 4h7a4 4 0 0 1 4 4v12"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/corner-right-up.svg b/src/apps/qcam/assets/feathericons/corner-right-up.svg
new file mode 100644
index 00000000..a5c5dce5
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/corner-right-up.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-corner-right-up"><polyline points="10 9 15 4 20 9"></polyline><path d="M4 20h7a4 4 0 0 0 4-4V4"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/corner-up-left.svg b/src/apps/qcam/assets/feathericons/corner-up-left.svg
new file mode 100644
index 00000000..0a1ffd61
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/corner-up-left.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-corner-up-left"><polyline points="9 14 4 9 9 4"></polyline><path d="M20 20v-7a4 4 0 0 0-4-4H4"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/corner-up-right.svg b/src/apps/qcam/assets/feathericons/corner-up-right.svg
new file mode 100644
index 00000000..0b8f961b
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/corner-up-right.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-corner-up-right"><polyline points="15 14 20 9 15 4"></polyline><path d="M4 20v-7a4 4 0 0 1 4-4h12"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/cpu.svg b/src/apps/qcam/assets/feathericons/cpu.svg
new file mode 100644
index 00000000..2ed16ef7
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/cpu.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-cpu"><rect x="4" y="4" width="16" height="16" rx="2" ry="2"></rect><rect x="9" y="9" width="6" height="6"></rect><line x1="9" y1="1" x2="9" y2="4"></line><line x1="15" y1="1" x2="15" y2="4"></line><line x1="9" y1="20" x2="9" y2="23"></line><line x1="15" y1="20" x2="15" y2="23"></line><line x1="20" y1="9" x2="23" y2="9"></line><line x1="20" y1="14" x2="23" y2="14"></line><line x1="1" y1="9" x2="4" y2="9"></line><line x1="1" y1="14" x2="4" y2="14"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/credit-card.svg b/src/apps/qcam/assets/feathericons/credit-card.svg
new file mode 100644
index 00000000..1b7fd029
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/credit-card.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-credit-card"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect><line x1="1" y1="10" x2="23" y2="10"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/crop.svg b/src/apps/qcam/assets/feathericons/crop.svg
new file mode 100644
index 00000000..ffbfd045
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/crop.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-crop"><path d="M6.13 1L6 16a2 2 0 0 0 2 2h15"></path><path d="M1 6.13L16 6a2 2 0 0 1 2 2v15"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/crosshair.svg b/src/apps/qcam/assets/feathericons/crosshair.svg
new file mode 100644
index 00000000..ba394015
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/crosshair.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-crosshair"><circle cx="12" cy="12" r="10"></circle><line x1="22" y1="12" x2="18" y2="12"></line><line x1="6" y1="12" x2="2" y2="12"></line><line x1="12" y1="6" x2="12" y2="2"></line><line x1="12" y1="22" x2="12" y2="18"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/database.svg b/src/apps/qcam/assets/feathericons/database.svg
new file mode 100644
index 00000000..c296fbcf
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/database.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-database"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/delete.svg b/src/apps/qcam/assets/feathericons/delete.svg
new file mode 100644
index 00000000..8c6074b9
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/delete.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-delete"><path d="M21 4H8l-7 8 7 8h13a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z"></path><line x1="18" y1="9" x2="12" y2="15"></line><line x1="12" y1="9" x2="18" y2="15"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/disc.svg b/src/apps/qcam/assets/feathericons/disc.svg
new file mode 100644
index 00000000..2595b444
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/disc.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-disc"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="3"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/dollar-sign.svg b/src/apps/qcam/assets/feathericons/dollar-sign.svg
new file mode 100644
index 00000000..1a124d26
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/dollar-sign.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-dollar-sign"><line x1="12" y1="1" x2="12" y2="23"></line><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/download-cloud.svg b/src/apps/qcam/assets/feathericons/download-cloud.svg
new file mode 100644
index 00000000..f3126fc3
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/download-cloud.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-download-cloud"><polyline points="8 17 12 21 16 17"></polyline><line x1="12" y1="12" x2="12" y2="21"></line><path d="M20.88 18.09A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.29"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/download.svg b/src/apps/qcam/assets/feathericons/download.svg
new file mode 100644
index 00000000..76767a92
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/download.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/droplet.svg b/src/apps/qcam/assets/feathericons/droplet.svg
new file mode 100644
index 00000000..ca093014
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/droplet.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-droplet"><path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/edit-2.svg b/src/apps/qcam/assets/feathericons/edit-2.svg
new file mode 100644
index 00000000..06830c9d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/edit-2.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit-2"><path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/edit-3.svg b/src/apps/qcam/assets/feathericons/edit-3.svg
new file mode 100644
index 00000000..d728efcc
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/edit-3.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit-3"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/edit.svg b/src/apps/qcam/assets/feathericons/edit.svg
new file mode 100644
index 00000000..ec7b4ca2
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/edit.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/external-link.svg b/src/apps/qcam/assets/feathericons/external-link.svg
new file mode 100644
index 00000000..6236df3e
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/external-link.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-external-link"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/eye-off.svg b/src/apps/qcam/assets/feathericons/eye-off.svg
new file mode 100644
index 00000000..77c54cb4
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/eye-off.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye-off"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/eye.svg b/src/apps/qcam/assets/feathericons/eye.svg
new file mode 100644
index 00000000..9cde2437
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/eye.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/facebook.svg b/src/apps/qcam/assets/feathericons/facebook.svg
new file mode 100644
index 00000000..2570f56a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/facebook.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-facebook"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/fast-forward.svg b/src/apps/qcam/assets/feathericons/fast-forward.svg
new file mode 100644
index 00000000..fa39877a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/fast-forward.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-fast-forward"><polygon points="13 19 22 12 13 5 13 19"></polygon><polygon points="2 19 11 12 2 5 2 19"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/feather.svg b/src/apps/qcam/assets/feathericons/feather.svg
new file mode 100644
index 00000000..ac3b868d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/feather.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-feather"><path d="M20.24 12.24a6 6 0 0 0-8.49-8.49L5 10.5V19h8.5z"></path><line x1="16" y1="8" x2="2" y2="22"></line><line x1="17.5" y1="15" x2="9" y2="15"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/feathericons.qrc b/src/apps/qcam/assets/feathericons/feathericons.qrc
new file mode 100644
index 00000000..c5302040
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/feathericons.qrc
@@ -0,0 +1,11 @@
+<!-- SPDX-License-Identifier: GPL-2.0-or-later -->
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+	<file>aperture.svg</file>
+	<file>camera-off.svg</file>
+	<file>play-circle.svg</file>
+	<file>save.svg</file>
+	<file>stop-circle.svg</file>
+	<file>x-circle.svg</file>
+</qresource>
+</RCC>
diff --git a/src/apps/qcam/assets/feathericons/figma.svg b/src/apps/qcam/assets/feathericons/figma.svg
new file mode 100644
index 00000000..66fd2178
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/figma.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-figma"><path d="M5 5.5A3.5 3.5 0 0 1 8.5 2H12v7H8.5A3.5 3.5 0 0 1 5 5.5z"></path><path d="M12 2h3.5a3.5 3.5 0 1 1 0 7H12V2z"></path><path d="M12 12.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 1 1-7 0z"></path><path d="M5 19.5A3.5 3.5 0 0 1 8.5 16H12v3.5a3.5 3.5 0 1 1-7 0z"></path><path d="M5 12.5A3.5 3.5 0 0 1 8.5 9H12v7H8.5A3.5 3.5 0 0 1 5 12.5z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/file-minus.svg b/src/apps/qcam/assets/feathericons/file-minus.svg
new file mode 100644
index 00000000..345756ef
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/file-minus.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-minus"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="9" y1="15" x2="15" y2="15"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/file-plus.svg b/src/apps/qcam/assets/feathericons/file-plus.svg
new file mode 100644
index 00000000..eed12004
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/file-plus.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-plus"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="12" y1="18" x2="12" y2="12"></line><line x1="9" y1="15" x2="15" y2="15"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/file-text.svg b/src/apps/qcam/assets/feathericons/file-text.svg
new file mode 100644
index 00000000..4197ddd4
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/file-text.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/file.svg b/src/apps/qcam/assets/feathericons/file.svg
new file mode 100644
index 00000000..378519ab
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/file.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/film.svg b/src/apps/qcam/assets/feathericons/film.svg
new file mode 100644
index 00000000..ac46360d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/film.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-film"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"></rect><line x1="7" y1="2" x2="7" y2="22"></line><line x1="17" y1="2" x2="17" y2="22"></line><line x1="2" y1="12" x2="22" y2="12"></line><line x1="2" y1="7" x2="7" y2="7"></line><line x1="2" y1="17" x2="7" y2="17"></line><line x1="17" y1="17" x2="22" y2="17"></line><line x1="17" y1="7" x2="22" y2="7"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/filter.svg b/src/apps/qcam/assets/feathericons/filter.svg
new file mode 100644
index 00000000..38a47e04
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/filter.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-filter"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/flag.svg b/src/apps/qcam/assets/feathericons/flag.svg
new file mode 100644
index 00000000..037737cb
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/flag.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-flag"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path><line x1="4" y1="22" x2="4" y2="15"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/folder-minus.svg b/src/apps/qcam/assets/feathericons/folder-minus.svg
new file mode 100644
index 00000000..d5b7af65
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/folder-minus.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-folder-minus"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path><line x1="9" y1="14" x2="15" y2="14"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/folder-plus.svg b/src/apps/qcam/assets/feathericons/folder-plus.svg
new file mode 100644
index 00000000..898f2fc9
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/folder-plus.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-folder-plus"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path><line x1="12" y1="11" x2="12" y2="17"></line><line x1="9" y1="14" x2="15" y2="14"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/folder.svg b/src/apps/qcam/assets/feathericons/folder.svg
new file mode 100644
index 00000000..134458b9
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/folder.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-folder"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/framer.svg b/src/apps/qcam/assets/feathericons/framer.svg
new file mode 100644
index 00000000..3e663478
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/framer.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-framer"><path d="M5 16V9h14V2H5l14 14h-7m-7 0l7 7v-7m-7 0h7"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/frown.svg b/src/apps/qcam/assets/feathericons/frown.svg
new file mode 100644
index 00000000..f3122547
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/frown.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-frown"><circle cx="12" cy="12" r="10"></circle><path d="M16 16s-1.5-2-4-2-4 2-4 2"></path><line x1="9" y1="9" x2="9.01" y2="9"></line><line x1="15" y1="9" x2="15.01" y2="9"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/gift.svg b/src/apps/qcam/assets/feathericons/gift.svg
new file mode 100644
index 00000000..d2c14bd6
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/gift.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-gift"><polyline points="20 12 20 22 4 22 4 12"></polyline><rect x="2" y="7" width="20" height="5"></rect><line x1="12" y1="22" x2="12" y2="7"></line><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/git-branch.svg b/src/apps/qcam/assets/feathericons/git-branch.svg
new file mode 100644
index 00000000..44003726
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/git-branch.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-git-branch"><line x1="6" y1="3" x2="6" y2="15"></line><circle cx="18" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><path d="M18 9a9 9 0 0 1-9 9"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/git-commit.svg b/src/apps/qcam/assets/feathericons/git-commit.svg
new file mode 100644
index 00000000..e959d725
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/git-commit.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-git-commit"><circle cx="12" cy="12" r="4"></circle><line x1="1.05" y1="12" x2="7" y2="12"></line><line x1="17.01" y1="12" x2="22.96" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/git-merge.svg b/src/apps/qcam/assets/feathericons/git-merge.svg
new file mode 100644
index 00000000..c65fffdd
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/git-merge.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-git-merge"><circle cx="18" cy="18" r="3"></circle><circle cx="6" cy="6" r="3"></circle><path d="M6 21V9a9 9 0 0 0 9 9"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/git-pull-request.svg b/src/apps/qcam/assets/feathericons/git-pull-request.svg
new file mode 100644
index 00000000..fc80bdfd
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/git-pull-request.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-git-pull-request"><circle cx="18" cy="18" r="3"></circle><circle cx="6" cy="6" r="3"></circle><path d="M13 6h3a2 2 0 0 1 2 2v7"></path><line x1="6" y1="9" x2="6" y2="21"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/github.svg b/src/apps/qcam/assets/feathericons/github.svg
new file mode 100644
index 00000000..ff0af481
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/github.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-github"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/gitlab.svg b/src/apps/qcam/assets/feathericons/gitlab.svg
new file mode 100644
index 00000000..85d54a1e
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/gitlab.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-gitlab"><path d="M22.65 14.39L12 22.13 1.35 14.39a.84.84 0 0 1-.3-.94l1.22-3.78 2.44-7.51A.42.42 0 0 1 4.82 2a.43.43 0 0 1 .58 0 .42.42 0 0 1 .11.18l2.44 7.49h8.1l2.44-7.51A.42.42 0 0 1 18.6 2a.43.43 0 0 1 .58 0 .42.42 0 0 1 .11.18l2.44 7.51L23 13.45a.84.84 0 0 1-.35.94z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/globe.svg b/src/apps/qcam/assets/feathericons/globe.svg
new file mode 100644
index 00000000..0a0586d3
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/globe.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/grid.svg b/src/apps/qcam/assets/feathericons/grid.svg
new file mode 100644
index 00000000..8ef2e9d8
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/grid.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-grid"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/hard-drive.svg b/src/apps/qcam/assets/feathericons/hard-drive.svg
new file mode 100644
index 00000000..8e90fa1b
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/hard-drive.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-hard-drive"><line x1="22" y1="12" x2="2" y2="12"></line><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path><line x1="6" y1="16" x2="6.01" y2="16"></line><line x1="10" y1="16" x2="10.01" y2="16"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/hash.svg b/src/apps/qcam/assets/feathericons/hash.svg
new file mode 100644
index 00000000..c9c8d41f
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/hash.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-hash"><line x1="4" y1="9" x2="20" y2="9"></line><line x1="4" y1="15" x2="20" y2="15"></line><line x1="10" y1="3" x2="8" y2="21"></line><line x1="16" y1="3" x2="14" y2="21"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/headphones.svg b/src/apps/qcam/assets/feathericons/headphones.svg
new file mode 100644
index 00000000..fd8915b4
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/headphones.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-headphones"><path d="M3 18v-6a9 9 0 0 1 18 0v6"></path><path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/heart.svg b/src/apps/qcam/assets/feathericons/heart.svg
new file mode 100644
index 00000000..a083b7e2
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/heart.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-heart"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/help-circle.svg b/src/apps/qcam/assets/feathericons/help-circle.svg
new file mode 100644
index 00000000..51fddd80
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/help-circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/hexagon.svg b/src/apps/qcam/assets/feathericons/hexagon.svg
new file mode 100644
index 00000000..eae7f255
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/hexagon.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-hexagon"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/home.svg b/src/apps/qcam/assets/feathericons/home.svg
new file mode 100644
index 00000000..7bb31b23
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/home.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/image.svg b/src/apps/qcam/assets/feathericons/image.svg
new file mode 100644
index 00000000..a7d84b98
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/image.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-image"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/inbox.svg b/src/apps/qcam/assets/feathericons/inbox.svg
new file mode 100644
index 00000000..03a13b4e
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/inbox.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-inbox"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12"></polyline><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/info.svg b/src/apps/qcam/assets/feathericons/info.svg
new file mode 100644
index 00000000..a09fa5f1
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/info.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-info"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/instagram.svg b/src/apps/qcam/assets/feathericons/instagram.svg
new file mode 100644
index 00000000..9fdb8e35
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/instagram.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-instagram"><rect x="2" y="2" width="20" height="20" rx="5" ry="5"></rect><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path><line x1="17.5" y1="6.5" x2="17.51" y2="6.5"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/italic.svg b/src/apps/qcam/assets/feathericons/italic.svg
new file mode 100644
index 00000000..a123d371
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/italic.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-italic"><line x1="19" y1="4" x2="10" y2="4"></line><line x1="14" y1="20" x2="5" y2="20"></line><line x1="15" y1="4" x2="9" y2="20"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/key.svg b/src/apps/qcam/assets/feathericons/key.svg
new file mode 100644
index 00000000..e778e74e
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/key.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-key"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/layers.svg b/src/apps/qcam/assets/feathericons/layers.svg
new file mode 100644
index 00000000..ea788c22
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/layers.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-layers"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/layout.svg b/src/apps/qcam/assets/feathericons/layout.svg
new file mode 100644
index 00000000..28743d92
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/layout.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-layout"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/life-buoy.svg b/src/apps/qcam/assets/feathericons/life-buoy.svg
new file mode 100644
index 00000000..54c2bd7d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/life-buoy.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-life-buoy"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="4"></circle><line x1="4.93" y1="4.93" x2="9.17" y2="9.17"></line><line x1="14.83" y1="14.83" x2="19.07" y2="19.07"></line><line x1="14.83" y1="9.17" x2="19.07" y2="4.93"></line><line x1="14.83" y1="9.17" x2="18.36" y2="5.64"></line><line x1="4.93" y1="19.07" x2="9.17" y2="14.83"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/link-2.svg b/src/apps/qcam/assets/feathericons/link-2.svg
new file mode 100644
index 00000000..8cc7f6dd
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/link-2.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link-2"><path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path><line x1="8" y1="12" x2="16" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/link.svg b/src/apps/qcam/assets/feathericons/link.svg
new file mode 100644
index 00000000..c89dd41c
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/link.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/linkedin.svg b/src/apps/qcam/assets/feathericons/linkedin.svg
new file mode 100644
index 00000000..39531094
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/linkedin.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-linkedin"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/list.svg b/src/apps/qcam/assets/feathericons/list.svg
new file mode 100644
index 00000000..5ce38eaa
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/list.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-list"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/loader.svg b/src/apps/qcam/assets/feathericons/loader.svg
new file mode 100644
index 00000000..e1a70c12
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/loader.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-loader"><line x1="12" y1="2" x2="12" y2="6"></line><line x1="12" y1="18" x2="12" y2="22"></line><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line><line x1="2" y1="12" x2="6" y2="12"></line><line x1="18" y1="12" x2="22" y2="12"></line><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/lock.svg b/src/apps/qcam/assets/feathericons/lock.svg
new file mode 100644
index 00000000..de09d9db
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/lock.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-lock"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/log-in.svg b/src/apps/qcam/assets/feathericons/log-in.svg
new file mode 100644
index 00000000..ba0da59a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/log-in.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-log-in"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path><polyline points="10 17 15 12 10 7"></polyline><line x1="15" y1="12" x2="3" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/log-out.svg b/src/apps/qcam/assets/feathericons/log-out.svg
new file mode 100644
index 00000000..c9002c90
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/log-out.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-log-out"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/mail.svg b/src/apps/qcam/assets/feathericons/mail.svg
new file mode 100644
index 00000000..2af169e8
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/mail.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-mail"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/map-pin.svg b/src/apps/qcam/assets/feathericons/map-pin.svg
new file mode 100644
index 00000000..d5548e92
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/map-pin.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-map-pin"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/map.svg b/src/apps/qcam/assets/feathericons/map.svg
new file mode 100644
index 00000000..ecebd7bf
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/map.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-map"><polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"></polygon><line x1="8" y1="2" x2="8" y2="18"></line><line x1="16" y1="6" x2="16" y2="22"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/maximize-2.svg b/src/apps/qcam/assets/feathericons/maximize-2.svg
new file mode 100644
index 00000000..e41fc0b7
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/maximize-2.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" y1="3" x2="14" y2="10"></line><line x1="3" y1="21" x2="10" y2="14"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/maximize.svg b/src/apps/qcam/assets/feathericons/maximize.svg
new file mode 100644
index 00000000..fc305189
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/maximize.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-maximize"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/meh.svg b/src/apps/qcam/assets/feathericons/meh.svg
new file mode 100644
index 00000000..6f57fff2
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/meh.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-meh"><circle cx="12" cy="12" r="10"></circle><line x1="8" y1="15" x2="16" y2="15"></line><line x1="9" y1="9" x2="9.01" y2="9"></line><line x1="15" y1="9" x2="15.01" y2="9"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/menu.svg b/src/apps/qcam/assets/feathericons/menu.svg
new file mode 100644
index 00000000..e8a84a95
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/menu.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-menu"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/message-circle.svg b/src/apps/qcam/assets/feathericons/message-circle.svg
new file mode 100644
index 00000000..4b21b32b
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/message-circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-message-circle"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/message-square.svg b/src/apps/qcam/assets/feathericons/message-square.svg
new file mode 100644
index 00000000..6a2e4e59
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/message-square.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-message-square"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/mic-off.svg b/src/apps/qcam/assets/feathericons/mic-off.svg
new file mode 100644
index 00000000..0786219c
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/mic-off.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-mic-off"><line x1="1" y1="1" x2="23" y2="23"></line><path d="M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6"></path><path d="M17 16.95A7 7 0 0 1 5 12v-2m14 0v2a7 7 0 0 1-.11 1.23"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/mic.svg b/src/apps/qcam/assets/feathericons/mic.svg
new file mode 100644
index 00000000..dc5f780c
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/mic.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-mic"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/minimize-2.svg b/src/apps/qcam/assets/feathericons/minimize-2.svg
new file mode 100644
index 00000000..a720fa6c
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/minimize-2.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-minimize-2"><polyline points="4 14 10 14 10 20"></polyline><polyline points="20 10 14 10 14 4"></polyline><line x1="14" y1="10" x2="21" y2="3"></line><line x1="3" y1="21" x2="10" y2="14"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/minimize.svg b/src/apps/qcam/assets/feathericons/minimize.svg
new file mode 100644
index 00000000..46d61196
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/minimize.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-minimize"><path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/minus-circle.svg b/src/apps/qcam/assets/feathericons/minus-circle.svg
new file mode 100644
index 00000000..80c0de1e
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/minus-circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-minus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="8" y1="12" x2="16" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/minus-square.svg b/src/apps/qcam/assets/feathericons/minus-square.svg
new file mode 100644
index 00000000..4862832a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/minus-square.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-minus-square"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="8" y1="12" x2="16" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/minus.svg b/src/apps/qcam/assets/feathericons/minus.svg
new file mode 100644
index 00000000..93cc7340
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/minus.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-minus"><line x1="5" y1="12" x2="19" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/monitor.svg b/src/apps/qcam/assets/feathericons/monitor.svg
new file mode 100644
index 00000000..6c3556db
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/monitor.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-monitor"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/moon.svg b/src/apps/qcam/assets/feathericons/moon.svg
new file mode 100644
index 00000000..dbf7c6cf
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/moon.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-moon"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/more-horizontal.svg b/src/apps/qcam/assets/feathericons/more-horizontal.svg
new file mode 100644
index 00000000..dc6a8556
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/more-horizontal.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-horizontal"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/more-vertical.svg b/src/apps/qcam/assets/feathericons/more-vertical.svg
new file mode 100644
index 00000000..cba6958f
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/more-vertical.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-vertical"><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/mouse-pointer.svg b/src/apps/qcam/assets/feathericons/mouse-pointer.svg
new file mode 100644
index 00000000..f5af5591
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/mouse-pointer.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-mouse-pointer"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"></path><path d="M13 13l6 6"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/move.svg b/src/apps/qcam/assets/feathericons/move.svg
new file mode 100644
index 00000000..4e251b56
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/move.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-move"><polyline points="5 9 2 12 5 15"></polyline><polyline points="9 5 12 2 15 5"></polyline><polyline points="15 19 12 22 9 19"></polyline><polyline points="19 9 22 12 19 15"></polyline><line x1="2" y1="12" x2="22" y2="12"></line><line x1="12" y1="2" x2="12" y2="22"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/music.svg b/src/apps/qcam/assets/feathericons/music.svg
new file mode 100644
index 00000000..7bee2f7e
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/music.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-music"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/navigation-2.svg b/src/apps/qcam/assets/feathericons/navigation-2.svg
new file mode 100644
index 00000000..ae31db96
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/navigation-2.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-navigation-2"><polygon points="12 2 19 21 12 17 5 21 12 2"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/navigation.svg b/src/apps/qcam/assets/feathericons/navigation.svg
new file mode 100644
index 00000000..f600a414
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/navigation.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-navigation"><polygon points="3 11 22 2 13 21 11 13 3 11"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/octagon.svg b/src/apps/qcam/assets/feathericons/octagon.svg
new file mode 100644
index 00000000..124c5483
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/octagon.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-octagon"><polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/package.svg b/src/apps/qcam/assets/feathericons/package.svg
new file mode 100644
index 00000000..f1e09eec
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/package.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-package"><line x1="16.5" y1="9.4" x2="7.5" y2="4.21"></line><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/paperclip.svg b/src/apps/qcam/assets/feathericons/paperclip.svg
new file mode 100644
index 00000000..b1f69b7a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/paperclip.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-paperclip"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/pause-circle.svg b/src/apps/qcam/assets/feathericons/pause-circle.svg
new file mode 100644
index 00000000..f6b1a8df
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/pause-circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-pause-circle"><circle cx="12" cy="12" r="10"></circle><line x1="10" y1="15" x2="10" y2="9"></line><line x1="14" y1="15" x2="14" y2="9"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/pause.svg b/src/apps/qcam/assets/feathericons/pause.svg
new file mode 100644
index 00000000..4e78038d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/pause.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-pause"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/pen-tool.svg b/src/apps/qcam/assets/feathericons/pen-tool.svg
new file mode 100644
index 00000000..0d26fa1e
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/pen-tool.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-pen-tool"><path d="M12 19l7-7 3 3-7 7-3-3z"></path><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"></path><path d="M2 2l7.586 7.586"></path><circle cx="11" cy="11" r="2"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/percent.svg b/src/apps/qcam/assets/feathericons/percent.svg
new file mode 100644
index 00000000..2cb9719d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/percent.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-percent"><line x1="19" y1="5" x2="5" y2="19"></line><circle cx="6.5" cy="6.5" r="2.5"></circle><circle cx="17.5" cy="17.5" r="2.5"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/phone-call.svg b/src/apps/qcam/assets/feathericons/phone-call.svg
new file mode 100644
index 00000000..8b866602
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/phone-call.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-phone-call"><path d="M15.05 5A5 5 0 0 1 19 8.95M15.05 1A9 9 0 0 1 23 8.94m-1 7.98v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/phone-forwarded.svg b/src/apps/qcam/assets/feathericons/phone-forwarded.svg
new file mode 100644
index 00000000..aa21befc
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/phone-forwarded.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-phone-forwarded"><polyline points="19 1 23 5 19 9"></polyline><line x1="15" y1="5" x2="23" y2="5"></line><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/phone-incoming.svg b/src/apps/qcam/assets/feathericons/phone-incoming.svg
new file mode 100644
index 00000000..b2d523a8
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/phone-incoming.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-phone-incoming"><polyline points="16 2 16 8 22 8"></polyline><line x1="23" y1="1" x2="16" y2="8"></line><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/phone-missed.svg b/src/apps/qcam/assets/feathericons/phone-missed.svg
new file mode 100644
index 00000000..4950f09f
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/phone-missed.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-phone-missed"><line x1="23" y1="1" x2="17" y2="7"></line><line x1="17" y1="1" x2="23" y2="7"></line><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/phone-off.svg b/src/apps/qcam/assets/feathericons/phone-off.svg
new file mode 100644
index 00000000..4d00fb3d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/phone-off.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-phone-off"><path d="M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-3.33-2.67m-2.67-3.34a19.79 19.79 0 0 1-3.07-8.63A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91"></path><line x1="23" y1="1" x2="1" y2="23"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/phone-outgoing.svg b/src/apps/qcam/assets/feathericons/phone-outgoing.svg
new file mode 100644
index 00000000..fea27a37
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/phone-outgoing.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-phone-outgoing"><polyline points="23 7 23 1 17 1"></polyline><line x1="16" y1="8" x2="23" y2="1"></line><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/phone.svg b/src/apps/qcam/assets/feathericons/phone.svg
new file mode 100644
index 00000000..2a35154a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/phone.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-phone"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/pie-chart.svg b/src/apps/qcam/assets/feathericons/pie-chart.svg
new file mode 100644
index 00000000..b5bbe67c
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/pie-chart.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-pie-chart"><path d="M21.21 15.89A10 10 0 1 1 8 2.83"></path><path d="M22 12A10 10 0 0 0 12 2v10z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/play-circle.svg b/src/apps/qcam/assets/feathericons/play-circle.svg
new file mode 100644
index 00000000..8766dc7b
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/play-circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-play-circle"><circle cx="12" cy="12" r="10"></circle><polygon points="10 8 16 12 10 16 10 8"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/play.svg b/src/apps/qcam/assets/feathericons/play.svg
new file mode 100644
index 00000000..fd76e30d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/play.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-play"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/plus-circle.svg b/src/apps/qcam/assets/feathericons/plus-circle.svg
new file mode 100644
index 00000000..4291ff05
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/plus-circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/plus-square.svg b/src/apps/qcam/assets/feathericons/plus-square.svg
new file mode 100644
index 00000000..c380e24b
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/plus-square.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-square"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/plus.svg b/src/apps/qcam/assets/feathericons/plus.svg
new file mode 100644
index 00000000..703c5b7b
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/plus.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/pocket.svg b/src/apps/qcam/assets/feathericons/pocket.svg
new file mode 100644
index 00000000..a3b25619
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/pocket.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-pocket"><path d="M4 3h16a2 2 0 0 1 2 2v6a10 10 0 0 1-10 10A10 10 0 0 1 2 11V5a2 2 0 0 1 2-2z"></path><polyline points="8 10 12 14 16 10"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/power.svg b/src/apps/qcam/assets/feathericons/power.svg
new file mode 100644
index 00000000..598308fc
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/power.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-power"><path d="M18.36 6.64a9 9 0 1 1-12.73 0"></path><line x1="12" y1="2" x2="12" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/printer.svg b/src/apps/qcam/assets/feathericons/printer.svg
new file mode 100644
index 00000000..8a9a7ace
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/printer.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-printer"><polyline points="6 9 6 2 18 2 18 9"></polyline><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path><rect x="6" y="14" width="12" height="8"></rect></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/radio.svg b/src/apps/qcam/assets/feathericons/radio.svg
new file mode 100644
index 00000000..5abfcd13
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/radio.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-radio"><circle cx="12" cy="12" r="2"></circle><path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/refresh-ccw.svg b/src/apps/qcam/assets/feathericons/refresh-ccw.svg
new file mode 100644
index 00000000..10cff0ec
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/refresh-ccw.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-refresh-ccw"><polyline points="1 4 1 10 7 10"></polyline><polyline points="23 20 23 14 17 14"></polyline><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/refresh-cw.svg b/src/apps/qcam/assets/feathericons/refresh-cw.svg
new file mode 100644
index 00000000..06c358dd
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/refresh-cw.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-refresh-cw"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/repeat.svg b/src/apps/qcam/assets/feathericons/repeat.svg
new file mode 100644
index 00000000..c7657b08
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/repeat.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-repeat"><polyline points="17 1 21 5 17 9"></polyline><path d="M3 11V9a4 4 0 0 1 4-4h14"></path><polyline points="7 23 3 19 7 15"></polyline><path d="M21 13v2a4 4 0 0 1-4 4H3"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/rewind.svg b/src/apps/qcam/assets/feathericons/rewind.svg
new file mode 100644
index 00000000..7b0fa3d5
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/rewind.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-rewind"><polygon points="11 19 2 12 11 5 11 19"></polygon><polygon points="22 19 13 12 22 5 22 19"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/rotate-ccw.svg b/src/apps/qcam/assets/feathericons/rotate-ccw.svg
new file mode 100644
index 00000000..ade5dc42
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/rotate-ccw.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-rotate-ccw"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/rotate-cw.svg b/src/apps/qcam/assets/feathericons/rotate-cw.svg
new file mode 100644
index 00000000..83dca351
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/rotate-cw.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-rotate-cw"><polyline points="23 4 23 10 17 10"></polyline><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/rss.svg b/src/apps/qcam/assets/feathericons/rss.svg
new file mode 100644
index 00000000..c9a13684
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/rss.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-rss"><path d="M4 11a9 9 0 0 1 9 9"></path><path d="M4 4a16 16 0 0 1 16 16"></path><circle cx="5" cy="19" r="1"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/save.svg b/src/apps/qcam/assets/feathericons/save.svg
new file mode 100644
index 00000000..46c72990
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/save.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-save"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/scissors.svg b/src/apps/qcam/assets/feathericons/scissors.svg
new file mode 100644
index 00000000..fd0647ff
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/scissors.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-scissors"><circle cx="6" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><line x1="20" y1="4" x2="8.12" y2="15.88"></line><line x1="14.47" y1="14.48" x2="20" y2="20"></line><line x1="8.12" y1="8.12" x2="12" y2="12"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/search.svg b/src/apps/qcam/assets/feathericons/search.svg
new file mode 100644
index 00000000..8710306d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/search.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/send.svg b/src/apps/qcam/assets/feathericons/send.svg
new file mode 100644
index 00000000..42ef2a24
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/send.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-send"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/server.svg b/src/apps/qcam/assets/feathericons/server.svg
new file mode 100644
index 00000000..54ce094a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/server.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-server"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect><rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect><line x1="6" y1="6" x2="6.01" y2="6"></line><line x1="6" y1="18" x2="6.01" y2="18"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/settings.svg b/src/apps/qcam/assets/feathericons/settings.svg
new file mode 100644
index 00000000..19c27265
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/settings.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-settings"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/share-2.svg b/src/apps/qcam/assets/feathericons/share-2.svg
new file mode 100644
index 00000000..09b1c7bc
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/share-2.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-share-2"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/share.svg b/src/apps/qcam/assets/feathericons/share.svg
new file mode 100644
index 00000000..df38c14d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/share.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-share"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path><polyline points="16 6 12 2 8 6"></polyline><line x1="12" y1="2" x2="12" y2="15"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/shield-off.svg b/src/apps/qcam/assets/feathericons/shield-off.svg
new file mode 100644
index 00000000..18692ddd
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/shield-off.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shield-off"><path d="M19.69 14a6.9 6.9 0 0 0 .31-2V5l-8-3-3.16 1.18"></path><path d="M4.73 4.73L4 5v7c0 6 8 10 8 10a20.29 20.29 0 0 0 5.62-4.38"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/shield.svg b/src/apps/qcam/assets/feathericons/shield.svg
new file mode 100644
index 00000000..c7c48413
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/shield.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shield"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/shopping-bag.svg b/src/apps/qcam/assets/feathericons/shopping-bag.svg
new file mode 100644
index 00000000..eaa39e81
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/shopping-bag.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shopping-bag"><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path><line x1="3" y1="6" x2="21" y2="6"></line><path d="M16 10a4 4 0 0 1-8 0"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/shopping-cart.svg b/src/apps/qcam/assets/feathericons/shopping-cart.svg
new file mode 100644
index 00000000..17a40bf4
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/shopping-cart.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shopping-cart"><circle cx="9" cy="21" r="1"></circle><circle cx="20" cy="21" r="1"></circle><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/shuffle.svg b/src/apps/qcam/assets/feathericons/shuffle.svg
new file mode 100644
index 00000000..8cfb5db5
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/shuffle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shuffle"><polyline points="16 3 21 3 21 8"></polyline><line x1="4" y1="20" x2="21" y2="3"></line><polyline points="21 16 21 21 16 21"></polyline><line x1="15" y1="15" x2="21" y2="21"></line><line x1="4" y1="4" x2="9" y2="9"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/sidebar.svg b/src/apps/qcam/assets/feathericons/sidebar.svg
new file mode 100644
index 00000000..8ba817e6
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/sidebar.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sidebar"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="9" y1="3" x2="9" y2="21"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/skip-back.svg b/src/apps/qcam/assets/feathericons/skip-back.svg
new file mode 100644
index 00000000..88d024e2
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/skip-back.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-skip-back"><polygon points="19 20 9 12 19 4 19 20"></polygon><line x1="5" y1="19" x2="5" y2="5"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/skip-forward.svg b/src/apps/qcam/assets/feathericons/skip-forward.svg
new file mode 100644
index 00000000..f3fdac3a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/skip-forward.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-skip-forward"><polygon points="5 4 15 12 5 20 5 4"></polygon><line x1="19" y1="5" x2="19" y2="19"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/slack.svg b/src/apps/qcam/assets/feathericons/slack.svg
new file mode 100644
index 00000000..5d973466
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/slack.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-slack"><path d="M14.5 10c-.83 0-1.5-.67-1.5-1.5v-5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5v5c0 .83-.67 1.5-1.5 1.5z"></path><path d="M20.5 10H19V8.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path><path d="M9.5 14c.83 0 1.5.67 1.5 1.5v5c0 .83-.67 1.5-1.5 1.5S8 21.33 8 20.5v-5c0-.83.67-1.5 1.5-1.5z"></path><path d="M3.5 14H5v1.5c0 .83-.67 1.5-1.5 1.5S2 16.33 2 15.5 2.67 14 3.5 14z"></path><path d="M14 14.5c0-.83.67-1.5 1.5-1.5h5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-5c-.83 0-1.5-.67-1.5-1.5z"></path><path d="M15.5 19H14v1.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5-.67-1.5-1.5-1.5z"></path><path d="M10 9.5C10 8.67 9.33 8 8.5 8h-5C2.67 8 2 8.67 2 9.5S2.67 11 3.5 11h5c.83 0 1.5-.67 1.5-1.5z"></path><path d="M8.5 5H10V3.5C10 2.67 9.33 2 8.5 2S7 2.67 7 3.5 7.67 5 8.5 5z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/slash.svg b/src/apps/qcam/assets/feathericons/slash.svg
new file mode 100644
index 00000000..f4131b85
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/slash.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-slash"><circle cx="12" cy="12" r="10"></circle><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/sliders.svg b/src/apps/qcam/assets/feathericons/sliders.svg
new file mode 100644
index 00000000..19c93852
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/sliders.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sliders"><line x1="4" y1="21" x2="4" y2="14"></line><line x1="4" y1="10" x2="4" y2="3"></line><line x1="12" y1="21" x2="12" y2="12"></line><line x1="12" y1="8" x2="12" y2="3"></line><line x1="20" y1="21" x2="20" y2="16"></line><line x1="20" y1="12" x2="20" y2="3"></line><line x1="1" y1="14" x2="7" y2="14"></line><line x1="9" y1="8" x2="15" y2="8"></line><line x1="17" y1="16" x2="23" y2="16"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/smartphone.svg b/src/apps/qcam/assets/feathericons/smartphone.svg
new file mode 100644
index 00000000..0171a95a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/smartphone.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-smartphone"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"></rect><line x1="12" y1="18" x2="12.01" y2="18"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/smile.svg b/src/apps/qcam/assets/feathericons/smile.svg
new file mode 100644
index 00000000..24dc8a26
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/smile.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-smile"><circle cx="12" cy="12" r="10"></circle><path d="M8 14s1.5 2 4 2 4-2 4-2"></path><line x1="9" y1="9" x2="9.01" y2="9"></line><line x1="15" y1="9" x2="15.01" y2="9"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/speaker.svg b/src/apps/qcam/assets/feathericons/speaker.svg
new file mode 100644
index 00000000..75d5ff9c
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/speaker.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-speaker"><rect x="4" y="2" width="16" height="20" rx="2" ry="2"></rect><circle cx="12" cy="14" r="4"></circle><line x1="12" y1="6" x2="12.01" y2="6"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/square.svg b/src/apps/qcam/assets/feathericons/square.svg
new file mode 100644
index 00000000..6eabc77d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/square.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-square"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/star.svg b/src/apps/qcam/assets/feathericons/star.svg
new file mode 100644
index 00000000..bcdc31aa
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/star.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/stop-circle.svg b/src/apps/qcam/assets/feathericons/stop-circle.svg
new file mode 100644
index 00000000..c10d9d47
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/stop-circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-stop-circle"><circle cx="12" cy="12" r="10"></circle><rect x="9" y="9" width="6" height="6"></rect></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/sun.svg b/src/apps/qcam/assets/feathericons/sun.svg
new file mode 100644
index 00000000..7f51b94d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/sun.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sun"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/sunrise.svg b/src/apps/qcam/assets/feathericons/sunrise.svg
new file mode 100644
index 00000000..eff4b1e4
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/sunrise.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sunrise"><path d="M17 18a5 5 0 0 0-10 0"></path><line x1="12" y1="2" x2="12" y2="9"></line><line x1="4.22" y1="10.22" x2="5.64" y2="11.64"></line><line x1="1" y1="18" x2="3" y2="18"></line><line x1="21" y1="18" x2="23" y2="18"></line><line x1="18.36" y1="11.64" x2="19.78" y2="10.22"></line><line x1="23" y1="22" x2="1" y2="22"></line><polyline points="8 6 12 2 16 6"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/sunset.svg b/src/apps/qcam/assets/feathericons/sunset.svg
new file mode 100644
index 00000000..a5a22215
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/sunset.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sunset"><path d="M17 18a5 5 0 0 0-10 0"></path><line x1="12" y1="9" x2="12" y2="2"></line><line x1="4.22" y1="10.22" x2="5.64" y2="11.64"></line><line x1="1" y1="18" x2="3" y2="18"></line><line x1="21" y1="18" x2="23" y2="18"></line><line x1="18.36" y1="11.64" x2="19.78" y2="10.22"></line><line x1="23" y1="22" x2="1" y2="22"></line><polyline points="16 5 12 9 8 5"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/tablet.svg b/src/apps/qcam/assets/feathericons/tablet.svg
new file mode 100644
index 00000000..9c80b40a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/tablet.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-tablet"><rect x="4" y="2" width="16" height="20" rx="2" ry="2"></rect><line x1="12" y1="18" x2="12.01" y2="18"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/tag.svg b/src/apps/qcam/assets/feathericons/tag.svg
new file mode 100644
index 00000000..7219b15f
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/tag.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-tag"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/target.svg b/src/apps/qcam/assets/feathericons/target.svg
new file mode 100644
index 00000000..be84b17c
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/target.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-target"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="6"></circle><circle cx="12" cy="12" r="2"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/terminal.svg b/src/apps/qcam/assets/feathericons/terminal.svg
new file mode 100644
index 00000000..af459c04
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/terminal.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-terminal"><polyline points="4 17 10 11 4 5"></polyline><line x1="12" y1="19" x2="20" y2="19"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/thermometer.svg b/src/apps/qcam/assets/feathericons/thermometer.svg
new file mode 100644
index 00000000..33142ccc
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/thermometer.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-thermometer"><path d="M14 14.76V3.5a2.5 2.5 0 0 0-5 0v11.26a4.5 4.5 0 1 0 5 0z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/thumbs-down.svg b/src/apps/qcam/assets/feathericons/thumbs-down.svg
new file mode 100644
index 00000000..3e7bcd6d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/thumbs-down.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-thumbs-down"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/thumbs-up.svg b/src/apps/qcam/assets/feathericons/thumbs-up.svg
new file mode 100644
index 00000000..226c44d8
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/thumbs-up.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-thumbs-up"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/toggle-left.svg b/src/apps/qcam/assets/feathericons/toggle-left.svg
new file mode 100644
index 00000000..240be290
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/toggle-left.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-toggle-left"><rect x="1" y="5" width="22" height="14" rx="7" ry="7"></rect><circle cx="8" cy="12" r="3"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/toggle-right.svg b/src/apps/qcam/assets/feathericons/toggle-right.svg
new file mode 100644
index 00000000..fc6e81c1
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/toggle-right.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-toggle-right"><rect x="1" y="5" width="22" height="14" rx="7" ry="7"></rect><circle cx="16" cy="12" r="3"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/tool.svg b/src/apps/qcam/assets/feathericons/tool.svg
new file mode 100644
index 00000000..f3cbf3d9
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/tool.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-tool"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/trash-2.svg b/src/apps/qcam/assets/feathericons/trash-2.svg
new file mode 100644
index 00000000..f24d55bf
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/trash-2.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-trash-2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/trash.svg b/src/apps/qcam/assets/feathericons/trash.svg
new file mode 100644
index 00000000..55650bd4
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/trash.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-trash"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/trello.svg b/src/apps/qcam/assets/feathericons/trello.svg
new file mode 100644
index 00000000..b2f599b6
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/trello.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-trello"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><rect x="7" y="7" width="3" height="9"></rect><rect x="14" y="7" width="3" height="5"></rect></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/trending-down.svg b/src/apps/qcam/assets/feathericons/trending-down.svg
new file mode 100644
index 00000000..a9d4cfa5
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/trending-down.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-trending-down"><polyline points="23 18 13.5 8.5 8.5 13.5 1 6"></polyline><polyline points="17 18 23 18 23 12"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/trending-up.svg b/src/apps/qcam/assets/feathericons/trending-up.svg
new file mode 100644
index 00000000..52026a4d
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/trending-up.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-trending-up"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline><polyline points="17 6 23 6 23 12"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/triangle.svg b/src/apps/qcam/assets/feathericons/triangle.svg
new file mode 100644
index 00000000..274b6528
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/triangle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-triangle"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/truck.svg b/src/apps/qcam/assets/feathericons/truck.svg
new file mode 100644
index 00000000..33898373
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/truck.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-truck"><rect x="1" y="3" width="15" height="13"></rect><polygon points="16 8 20 8 23 11 23 16 16 16 16 8"></polygon><circle cx="5.5" cy="18.5" r="2.5"></circle><circle cx="18.5" cy="18.5" r="2.5"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/tv.svg b/src/apps/qcam/assets/feathericons/tv.svg
new file mode 100644
index 00000000..955bbfff
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/tv.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-tv"><rect x="2" y="7" width="20" height="15" rx="2" ry="2"></rect><polyline points="17 2 12 7 7 2"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/twitch.svg b/src/apps/qcam/assets/feathericons/twitch.svg
new file mode 100644
index 00000000..17062495
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/twitch.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-twitch"><path d="M21 2H3v16h5v4l4-4h5l4-4V2zm-10 9V7m5 4V7"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/twitter.svg b/src/apps/qcam/assets/feathericons/twitter.svg
new file mode 100644
index 00000000..f8886eca
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/twitter.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-twitter"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/type.svg b/src/apps/qcam/assets/feathericons/type.svg
new file mode 100644
index 00000000..c6b2de33
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/type.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-type"><polyline points="4 7 4 4 20 4 20 7"></polyline><line x1="9" y1="20" x2="15" y2="20"></line><line x1="12" y1="4" x2="12" y2="20"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/umbrella.svg b/src/apps/qcam/assets/feathericons/umbrella.svg
new file mode 100644
index 00000000..dc77c0cb
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/umbrella.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-umbrella"><path d="M23 12a11.05 11.05 0 0 0-22 0zm-5 7a3 3 0 0 1-6 0v-7"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/underline.svg b/src/apps/qcam/assets/feathericons/underline.svg
new file mode 100644
index 00000000..044945d4
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/underline.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-underline"><path d="M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3"></path><line x1="4" y1="21" x2="20" y2="21"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/unlock.svg b/src/apps/qcam/assets/feathericons/unlock.svg
new file mode 100644
index 00000000..01dc3597
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/unlock.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-unlock"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 9.9-1"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/upload-cloud.svg b/src/apps/qcam/assets/feathericons/upload-cloud.svg
new file mode 100644
index 00000000..a1db297c
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/upload-cloud.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-upload-cloud"><polyline points="16 16 12 12 8 16"></polyline><line x1="12" y1="12" x2="12" y2="21"></line><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"></path><polyline points="16 16 12 12 8 16"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/upload.svg b/src/apps/qcam/assets/feathericons/upload.svg
new file mode 100644
index 00000000..91eaff75
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/upload.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-upload"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/user-check.svg b/src/apps/qcam/assets/feathericons/user-check.svg
new file mode 100644
index 00000000..42f91b29
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/user-check.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user-check"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><polyline points="17 11 19 13 23 9"></polyline></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/user-minus.svg b/src/apps/qcam/assets/feathericons/user-minus.svg
new file mode 100644
index 00000000..44b75f5a
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/user-minus.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user-minus"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="23" y1="11" x2="17" y2="11"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/user-plus.svg b/src/apps/qcam/assets/feathericons/user-plus.svg
new file mode 100644
index 00000000..21460f6e
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/user-plus.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user-plus"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="20" y1="8" x2="20" y2="14"></line><line x1="23" y1="11" x2="17" y2="11"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/user-x.svg b/src/apps/qcam/assets/feathericons/user-x.svg
new file mode 100644
index 00000000..0c41a481
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/user-x.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user-x"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="18" y1="8" x2="23" y2="13"></line><line x1="23" y1="8" x2="18" y2="13"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/user.svg b/src/apps/qcam/assets/feathericons/user.svg
new file mode 100644
index 00000000..7bb5f291
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/user.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/users.svg b/src/apps/qcam/assets/feathericons/users.svg
new file mode 100644
index 00000000..aacf6b08
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/users.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/video-off.svg b/src/apps/qcam/assets/feathericons/video-off.svg
new file mode 100644
index 00000000..08ec6973
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/video-off.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-video-off"><path d="M16 16v1a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2m5.66 0H14a2 2 0 0 1 2 2v3.34l1 1L23 7v10"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/video.svg b/src/apps/qcam/assets/feathericons/video.svg
new file mode 100644
index 00000000..8ff156aa
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/video.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-video"><polygon points="23 7 16 12 23 17 23 7"></polygon><rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/voicemail.svg b/src/apps/qcam/assets/feathericons/voicemail.svg
new file mode 100644
index 00000000..5d78a8e7
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/voicemail.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-voicemail"><circle cx="5.5" cy="11.5" r="4.5"></circle><circle cx="18.5" cy="11.5" r="4.5"></circle><line x1="5.5" y1="16" x2="18.5" y2="16"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/volume-1.svg b/src/apps/qcam/assets/feathericons/volume-1.svg
new file mode 100644
index 00000000..150e875f
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/volume-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume-1"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/volume-2.svg b/src/apps/qcam/assets/feathericons/volume-2.svg
new file mode 100644
index 00000000..03d521c7
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/volume-2.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume-2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/volume-x.svg b/src/apps/qcam/assets/feathericons/volume-x.svg
new file mode 100644
index 00000000..be442406
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/volume-x.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume-x"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><line x1="23" y1="9" x2="17" y2="15"></line><line x1="17" y1="9" x2="23" y2="15"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/volume.svg b/src/apps/qcam/assets/feathericons/volume.svg
new file mode 100644
index 00000000..53bfe15e
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/volume.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/watch.svg b/src/apps/qcam/assets/feathericons/watch.svg
new file mode 100644
index 00000000..a1099da3
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/watch.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-watch"><circle cx="12" cy="12" r="7"></circle><polyline points="12 9 12 12 13.5 13.5"></polyline><path d="M16.51 17.35l-.35 3.83a2 2 0 0 1-2 1.82H9.83a2 2 0 0 1-2-1.82l-.35-3.83m.01-10.7l.35-3.83A2 2 0 0 1 9.83 1h4.35a2 2 0 0 1 2 1.82l.35 3.83"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/wifi-off.svg b/src/apps/qcam/assets/feathericons/wifi-off.svg
new file mode 100644
index 00000000..35eae43b
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/wifi-off.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-wifi-off"><line x1="1" y1="1" x2="23" y2="23"></line><path d="M16.72 11.06A10.94 10.94 0 0 1 19 12.55"></path><path d="M5 12.55a10.94 10.94 0 0 1 5.17-2.39"></path><path d="M10.71 5.05A16 16 0 0 1 22.58 9"></path><path d="M1.42 9a15.91 15.91 0 0 1 4.7-2.88"></path><path d="M8.53 16.11a6 6 0 0 1 6.95 0"></path><line x1="12" y1="20" x2="12.01" y2="20"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/wifi.svg b/src/apps/qcam/assets/feathericons/wifi.svg
new file mode 100644
index 00000000..748c285e
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/wifi.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-wifi"><path d="M5 12.55a11 11 0 0 1 14.08 0"></path><path d="M1.42 9a16 16 0 0 1 21.16 0"></path><path d="M8.53 16.11a6 6 0 0 1 6.95 0"></path><line x1="12" y1="20" x2="12.01" y2="20"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/wind.svg b/src/apps/qcam/assets/feathericons/wind.svg
new file mode 100644
index 00000000..82b36468
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/wind.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-wind"><path d="M9.59 4.59A2 2 0 1 1 11 8H2m10.59 11.41A2 2 0 1 0 14 16H2m15.73-8.27A2.5 2.5 0 1 1 19.5 12H2"></path></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/x-circle.svg b/src/apps/qcam/assets/feathericons/x-circle.svg
new file mode 100644
index 00000000..94aad5e5
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/x-circle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x-circle"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/x-octagon.svg b/src/apps/qcam/assets/feathericons/x-octagon.svg
new file mode 100644
index 00000000..85431985
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/x-octagon.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x-octagon"><polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"></polygon><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/x-square.svg b/src/apps/qcam/assets/feathericons/x-square.svg
new file mode 100644
index 00000000..7677c387
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/x-square.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x-square"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="9" y1="9" x2="15" y2="15"></line><line x1="15" y1="9" x2="9" y2="15"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/x.svg b/src/apps/qcam/assets/feathericons/x.svg
new file mode 100644
index 00000000..7d5875ca
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/x.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/youtube.svg b/src/apps/qcam/assets/feathericons/youtube.svg
new file mode 100644
index 00000000..c4824385
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/youtube.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-youtube"><path d="M22.54 6.42a2.78 2.78 0 0 0-1.94-2C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 2A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33A2.78 2.78 0 0 0 3.4 19c1.72.46 8.6.46 8.6.46s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-2 29 29 0 0 0 .46-5.25 29 29 0 0 0-.46-5.33z"></path><polygon points="9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/zap-off.svg b/src/apps/qcam/assets/feathericons/zap-off.svg
new file mode 100644
index 00000000..c636f8bb
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/zap-off.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-zap-off"><polyline points="12.41 6.75 13 2 10.57 4.92"></polyline><polyline points="18.57 12.91 21 10 15.66 10"></polyline><polyline points="8 8 3 14 12 14 11 22 16 16"></polyline><line x1="1" y1="1" x2="23" y2="23"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/zap.svg b/src/apps/qcam/assets/feathericons/zap.svg
new file mode 100644
index 00000000..8fdafa93
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/zap.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-zap"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/zoom-in.svg b/src/apps/qcam/assets/feathericons/zoom-in.svg
new file mode 100644
index 00000000..da4572d2
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/zoom-in.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-zoom-in"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="11" y1="8" x2="11" y2="14"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/feathericons/zoom-out.svg b/src/apps/qcam/assets/feathericons/zoom-out.svg
new file mode 100644
index 00000000..fd678d72
--- /dev/null
+++ b/src/apps/qcam/assets/feathericons/zoom-out.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-zoom-out"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>
\ No newline at end of file
diff --git a/src/apps/qcam/assets/shader/RGB.frag b/src/apps/qcam/assets/shader/RGB.frag
new file mode 100644
index 00000000..4c374ac9
--- /dev/null
+++ b/src/apps/qcam/assets/shader/RGB.frag
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Laurent Pinchart
+ *
+ * RGB.frag - Fragment shader code for RGB formats
+ */
+
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+varying vec2 textureOut;
+uniform sampler2D tex_y;
+
+void main(void)
+{
+	vec3 rgb;
+
+	rgb = texture2D(tex_y, textureOut).RGB_PATTERN;
+
+	gl_FragColor = vec4(rgb, 1.0);
+}
diff --git a/src/apps/qcam/assets/shader/YUV_2_planes.frag b/src/apps/qcam/assets/shader/YUV_2_planes.frag
new file mode 100644
index 00000000..1d5d1206
--- /dev/null
+++ b/src/apps/qcam/assets/shader/YUV_2_planes.frag
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Linaro
+ *
+ * YUV_2_planes.frag - Fragment shader code for NV12, NV16 and NV24 formats
+ */
+
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+varying vec2 textureOut;
+uniform sampler2D tex_y;
+uniform sampler2D tex_u;
+
+const mat3 yuv2rgb_matrix = mat3(
+	YUV2RGB_MATRIX
+);
+
+const vec3 yuv2rgb_offset = vec3(
+	YUV2RGB_Y_OFFSET / 255.0, 128.0 / 255.0, 128.0 / 255.0
+);
+
+void main(void)
+{
+	vec3 yuv;
+
+	yuv.x = texture2D(tex_y, textureOut).r;
+#if defined(YUV_PATTERN_UV)
+	yuv.y = texture2D(tex_u, textureOut).r;
+	yuv.z = texture2D(tex_u, textureOut).a;
+#elif defined(YUV_PATTERN_VU)
+	yuv.y = texture2D(tex_u, textureOut).a;
+	yuv.z = texture2D(tex_u, textureOut).r;
+#else
+#error Invalid pattern
+#endif
+
+	vec3 rgb = yuv2rgb_matrix * (yuv - yuv2rgb_offset);
+
+	gl_FragColor = vec4(rgb, 1.0);
+}
diff --git a/src/apps/qcam/assets/shader/YUV_3_planes.frag b/src/apps/qcam/assets/shader/YUV_3_planes.frag
new file mode 100644
index 00000000..8f788e90
--- /dev/null
+++ b/src/apps/qcam/assets/shader/YUV_3_planes.frag
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Linaro
+ *
+ * YUV_3_planes_UV.frag - Fragment shader code for YUV420 format
+ */
+
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+varying vec2 textureOut;
+uniform sampler2D tex_y;
+uniform sampler2D tex_u;
+uniform sampler2D tex_v;
+
+const mat3 yuv2rgb_matrix = mat3(
+	YUV2RGB_MATRIX
+);
+
+const vec3 yuv2rgb_offset = vec3(
+	YUV2RGB_Y_OFFSET / 255.0, 128.0 / 255.0, 128.0 / 255.0
+);
+
+void main(void)
+{
+	vec3 yuv;
+
+	yuv.x = texture2D(tex_y, textureOut).r;
+	yuv.y = texture2D(tex_u, textureOut).r;
+	yuv.z = texture2D(tex_v, textureOut).r;
+
+	vec3 rgb = yuv2rgb_matrix * (yuv - yuv2rgb_offset);
+
+	gl_FragColor = vec4(rgb, 1.0);
+}
diff --git a/src/apps/qcam/assets/shader/YUV_packed.frag b/src/apps/qcam/assets/shader/YUV_packed.frag
new file mode 100644
index 00000000..b9ef9d41
--- /dev/null
+++ b/src/apps/qcam/assets/shader/YUV_packed.frag
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * YUV_packed.frag - Fragment shader code for YUYV packed formats
+ */
+
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+varying vec2 textureOut;
+
+uniform sampler2D tex_y;
+uniform vec2 tex_step;
+
+const mat3 yuv2rgb_matrix = mat3(
+	YUV2RGB_MATRIX
+);
+
+const vec3 yuv2rgb_offset = vec3(
+	YUV2RGB_Y_OFFSET / 255.0, 128.0 / 255.0, 128.0 / 255.0
+);
+
+void main(void)
+{
+	/*
+	 * The sampler won't interpolate the texture correctly along the X axis,
+	 * as each RGBA pixel effectively stores two pixels. We thus need to
+	 * interpolate manually.
+	 *
+	 * In integer texture coordinates, the Y values are layed out in the
+	 * texture memory as follows:
+	 *
+	 * ...| Y  U  Y  V | Y  U  Y  V | Y  U  Y  V |...
+	 * ...| R  G  B  A | R  G  B  A | R  G  B  A |...
+	 *      ^     ^      ^     ^      ^     ^
+	 *      |     |      |     |      |     |
+	 *     n-1  n-0.5    n   n+0.5   n+1  n+1.5
+	 *
+	 * For a texture location x in the interval [n, n+1[, sample the left
+	 * and right pixels at n and n+1, and interpolate them with
+	 *
+	 * left.r * (1 - a) + left.b * a	if fract(x) < 0.5
+	 * left.b * (1 - a) + right.r * a	if fract(x) >= 0.5
+	 *
+	 * with a = fract(x * 2) which can also be written
+	 *
+	 * a = fract(x) * 2			if fract(x) < 0.5
+	 * a = fract(x) * 2 - 1			if fract(x) >= 0.5
+	 */
+	vec2 pos = textureOut;
+	float f_x = fract(pos.x / tex_step.x);
+
+	vec4 left = texture2D(tex_y, vec2(pos.x - f_x * tex_step.x, pos.y));
+	vec4 right = texture2D(tex_y, vec2(pos.x + (1.0 - f_x) * tex_step.x , pos.y));
+
+#if defined(YUV_PATTERN_UYVY)
+	float y_left = mix(left.g, left.a, f_x * 2.0);
+	float y_right = mix(left.a, right.g, f_x * 2.0 - 1.0);
+	vec2 uv = mix(left.rb, right.rb, f_x);
+#elif defined(YUV_PATTERN_VYUY)
+	float y_left = mix(left.g, left.a, f_x * 2.0);
+	float y_right = mix(left.a, right.g, f_x * 2.0 - 1.0);
+	vec2 uv = mix(left.br, right.br, f_x);
+#elif defined(YUV_PATTERN_YUYV)
+	float y_left = mix(left.r, left.b, f_x * 2.0);
+	float y_right = mix(left.b, right.r, f_x * 2.0 - 1.0);
+	vec2 uv = mix(left.ga, right.ga, f_x);
+#elif defined(YUV_PATTERN_YVYU)
+	float y_left = mix(left.r, left.b, f_x * 2.0);
+	float y_right = mix(left.b, right.r, f_x * 2.0 - 1.0);
+	vec2 uv = mix(left.ag, right.ag, f_x);
+#else
+#error Invalid pattern
+#endif
+
+	float y = mix(y_left, y_right, step(0.5, f_x));
+
+	vec3 rgb = yuv2rgb_matrix * (vec3(y, uv) - yuv2rgb_offset);
+
+	gl_FragColor = vec4(rgb, 1.0);
+}
diff --git a/src/apps/qcam/assets/shader/bayer_1x_packed.frag b/src/apps/qcam/assets/shader/bayer_1x_packed.frag
new file mode 100644
index 00000000..f53f5575
--- /dev/null
+++ b/src/apps/qcam/assets/shader/bayer_1x_packed.frag
@@ -0,0 +1,216 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Based on the code from http://jgt.akpeters.com/papers/McGuire08/
+ *
+ * Efficient, High-Quality Bayer Demosaic Filtering on GPUs
+ *
+ * Morgan McGuire
+ *
+ * This paper appears in issue Volume 13, Number 4.
+ * ---------------------------------------------------------
+ * Copyright (c) 2008, Morgan McGuire. All rights reserved.
+ *
+ *
+ * Modified by Linaro Ltd for 10/12-bit packed vs 8-bit raw Bayer format,
+ * and for simpler demosaic algorithm.
+ * Copyright (C) 2020, Linaro
+ *
+ * bayer_1x_packed.frag - Fragment shader code for raw Bayer 10-bit and 12-bit
+ * packed formats
+ */
+
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+/*
+ * These constants are used to select the bytes containing the HS part of
+ * the pixel value:
+ * BPP - bytes per pixel,
+ * THRESHOLD_L = fract(BPP) * 0.5 + 0.02
+ * THRESHOLD_H = 1.0 - fract(BPP) * 1.5 + 0.02
+ * Let X is the x coordinate in the texture measured in bytes (so that the
+ * range is from 0 to (stride_-1)) aligned on the nearest pixel.
+ * E.g. for RAW10P:
+ * -------------+-------------------+-------------------+--
+ *  pixel No    |  0   1    2   3   |  4   5    6   7   | ...
+ * -------------+-------------------+-------------------+--
+ *  byte offset | 0   1   2   3   4 | 5   6   7   8   9 | ...
+ * -------------+-------------------+-------------------+--
+ *      X       | 0.0 1.25 2.5 3.75 | 5.0 6.25 7.5 8.75 | ...
+ * -------------+-------------------+-------------------+--
+ * If fract(X) < THRESHOLD_L then the previous byte contains the LS
+ * bits of the pixel values and needs to be skipped.
+ * If fract(X) > THRESHOLD_H then the next byte contains the LS bits
+ * of the pixel values and needs to be skipped.
+ */
+#if defined(RAW10P)
+#define BPP		1.25
+#define THRESHOLD_L	0.14
+#define THRESHOLD_H	0.64
+#elif defined(RAW12P)
+#define BPP		1.5
+#define THRESHOLD_L	0.27
+#define THRESHOLD_H	0.27
+#else
+#error Invalid raw format
+#endif
+
+
+varying vec2 textureOut;
+
+/* the texture size in pixels */
+uniform vec2 tex_size;
+uniform vec2 tex_step;
+uniform vec2 tex_bayer_first_red;
+
+uniform sampler2D tex_y;
+
+void main(void)
+{
+	vec3 rgb;
+
+	/*
+	 * center_bytes holds the coordinates of the MS byte of the pixel
+	 * being sampled on the [0, stride-1/height-1] range.
+	 * center_pixel holds the coordinates of the pixel being sampled
+	 * on the [0, width/height-1] range.
+	 */
+	vec2 center_bytes;
+	vec2 center_pixel;
+
+	/*
+	 * x- and y-positions of the adjacent pixels on the [0, 1] range.
+	 */
+	vec2 xcoords;
+	vec2 ycoords;
+
+	/*
+	 * The coordinates passed to the shader in textureOut may point
+	 * to a place in between the pixels if the texture format doesn't
+	 * match the image format. In particular, MIPI packed raw Bayer
+	 * formats don't have a matching texture format.
+	 * In this case align the coordinates to the left nearest pixel
+	 * by hand.
+	 */
+	center_pixel = floor(textureOut * tex_size);
+	center_bytes.y = center_pixel.y;
+
+	/*
+	 * Add a small number (a few mantissa's LSBs) to avoid float
+	 * representation issues. Maybe paranoic.
+	 */
+	center_bytes.x = BPP * center_pixel.x + 0.02;
+
+	float fract_x = fract(center_bytes.x);
+
+	/*
+	 * The below floor() call ensures that center_bytes.x points
+	 * at one of the bytes representing the 8 higher bits of
+	 * the pixel value, not at the byte containing the LS bits
+	 * of the group of the pixels.
+	 */
+	center_bytes.x = floor(center_bytes.x);
+	center_bytes *= tex_step;
+
+	xcoords = center_bytes.x + vec2(-tex_step.x, tex_step.x);
+	ycoords = center_bytes.y + vec2(-tex_step.y, tex_step.y);
+
+	/*
+	 * If xcoords[0] points at the byte containing the LS bits
+	 * of the previous group of the pixels, move xcoords[0] one
+	 * byte back.
+	 */
+	xcoords[0] += (fract_x < THRESHOLD_L) ? -tex_step.x : 0.0;
+
+	/*
+	 * If xcoords[1] points at the byte containing the LS bits
+	 * of the current group of the pixels, move xcoords[1] one
+	 * byte forward.
+	 */
+	xcoords[1] += (fract_x > THRESHOLD_H) ? tex_step.x : 0.0;
+
+	vec2 alternate = mod(center_pixel.xy + tex_bayer_first_red, 2.0);
+	bool even_col = alternate.x < 1.0;
+	bool even_row = alternate.y < 1.0;
+
+	/*
+	 * We need to sample the central pixel and the ones with offset
+	 * of -1 to +1 pixel in both X and Y directions. Let's name these
+	 * pixels as below, where C is the central pixel:
+	 *
+	 *   +----+----+----+----+
+	 *   | \ x|    |    |    |
+	 *   |y \ | -1 |  0 | +1 |
+	 *   +----+----+----+----+
+	 *   | +1 | D2 | A1 | D3 |
+	 *   +----+----+----+----+
+	 *   |  0 | B0 |  C | B1 |
+	 *   +----+----+----+----+
+	 *   | -1 | D0 | A0 | D1 |
+	 *   +----+----+----+----+
+	 *
+	 * In the below equations (0,-1).r means "r component of the texel
+	 * shifted by -tex_step.y from the center_bytes one" etc.
+	 *
+	 * In the even row / even column (EE) case the colour values are:
+	 *   R = C = (0,0).r,
+	 *   G = (A0 + A1 + B0 + B1) / 4.0 =
+	 *       ( (0,-1).r + (0,1).r + (-1,0).r + (1,0).r ) / 4.0,
+	 *   B = (D0 + D1 + D2 + D3) / 4.0 =
+	 *       ( (-1,-1).r + (1,-1).r + (-1,1).r + (1,1).r ) / 4.0
+	 *
+	 * For even row / odd column (EO):
+	 *   R = (B0 + B1) / 2.0 = ( (-1,0).r + (1,0).r ) / 2.0,
+	 *   G = C = (0,0).r,
+	 *   B = (A0 + A1) / 2.0 = ( (0,-1).r + (0,1).r ) / 2.0
+	 *
+	 * For odd row / even column (OE):
+	 *   R = (A0 + A1) / 2.0 = ( (0,-1).r + (0,1).r ) / 2.0,
+	 *   G = C = (0,0).r,
+	 *   B = (B0 + B1) / 2.0 = ( (-1,0).r + (1,0).r ) / 2.0
+	 *
+	 * For odd row / odd column (OO):
+	 *   R = (D0 + D1 + D2 + D3) / 4.0 =
+	 *       ( (-1,-1).r + (1,-1).r + (-1,1).r + (1,1).r ) / 4.0,
+	 *   G = (A0 + A1 + B0 + B1) / 4.0 =
+	 *       ( (0,-1).r + (0,1).r + (-1,0).r + (1,0).r ) / 4.0,
+	 *   B = C = (0,0).r
+	 */
+
+	/*
+	 * Fetch the values and precalculate the terms:
+	 *   patterns.x = (A0 + A1) / 2.0
+	 *   patterns.y = (B0 + B1) / 2.0
+	 *   patterns.z = (A0 + A1 + B0 + B1) / 4.0
+	 *   patterns.w = (D0 + D1 + D2 + D3) / 4.0
+	 */
+	#define fetch(x, y) texture2D(tex_y, vec2(x, y)).r
+
+	float C = texture2D(tex_y, center_bytes).r;
+	vec4 patterns = vec4(
+		fetch(center_bytes.x, ycoords[0]),	/* A0: (0,-1) */
+		fetch(xcoords[0], center_bytes.y),	/* B0: (-1,0) */
+		fetch(xcoords[0], ycoords[0]),		/* D0: (-1,-1) */
+		fetch(xcoords[1], ycoords[0]));		/* D1: (1,-1) */
+	vec4 temp = vec4(
+		fetch(center_bytes.x, ycoords[1]),	/* A1: (0,1) */
+		fetch(xcoords[1], center_bytes.y),	/* B1: (1,0) */
+		fetch(xcoords[1], ycoords[1]),		/* D3: (1,1) */
+		fetch(xcoords[0], ycoords[1]));		/* D2: (-1,1) */
+	patterns = (patterns + temp) * 0.5;
+		/* .x = (A0 + A1) / 2.0, .y = (B0 + B1) / 2.0 */
+		/* .z = (D0 + D3) / 2.0, .w = (D1 + D2) / 2.0 */
+	patterns.w = (patterns.z + patterns.w) * 0.5;
+	patterns.z = (patterns.x + patterns.y) * 0.5;
+
+	rgb = even_col ?
+		(even_row ?
+			vec3(C, patterns.zw) :
+			vec3(patterns.x, C, patterns.y)) :
+		(even_row ?
+			vec3(patterns.y, C, patterns.x) :
+			vec3(patterns.wz, C));
+
+	gl_FragColor = vec4(rgb, 1.0);
+}
diff --git a/src/apps/qcam/assets/shader/bayer_8.frag b/src/apps/qcam/assets/shader/bayer_8.frag
new file mode 100644
index 00000000..7e35ca88
--- /dev/null
+++ b/src/apps/qcam/assets/shader/bayer_8.frag
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+From http://jgt.akpeters.com/papers/McGuire08/
+
+Efficient, High-Quality Bayer Demosaic Filtering on GPUs
+
+Morgan McGuire
+
+This paper appears in issue Volume 13, Number 4.
+---------------------------------------------------------
+Copyright (c) 2008, Morgan McGuire. All rights reserved.
+
+Modified by Linaro Ltd to integrate it into libcamera.
+Copyright (C) 2021, Linaro
+*/
+
+//Pixel Shader
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+/** Monochrome RGBA or GL_LUMINANCE Bayer encoded texture.*/
+uniform sampler2D       tex_y;
+varying vec4            center;
+varying vec4            yCoord;
+varying vec4            xCoord;
+
+void main(void) {
+    #define fetch(x, y) texture2D(tex_y, vec2(x, y)).r
+
+    float C = texture2D(tex_y, center.xy).r; // ( 0, 0)
+    const vec4 kC = vec4( 4.0,  6.0,  5.0,  5.0) / 8.0;
+
+    // Determine which of four types of pixels we are on.
+    vec2 alternate = mod(floor(center.zw), 2.0);
+
+    vec4 Dvec = vec4(
+        fetch(xCoord[1], yCoord[1]),  // (-1,-1)
+        fetch(xCoord[1], yCoord[2]),  // (-1, 1)
+        fetch(xCoord[2], yCoord[1]),  // ( 1,-1)
+        fetch(xCoord[2], yCoord[2])); // ( 1, 1)
+
+    vec4 PATTERN = (kC.xyz * C).xyzz;
+
+    // Can also be a dot product with (1,1,1,1) on hardware where that is
+    // specially optimized.
+    // Equivalent to: D = Dvec[0] + Dvec[1] + Dvec[2] + Dvec[3];
+    Dvec.xy += Dvec.zw;
+    Dvec.x  += Dvec.y;
+
+    vec4 value = vec4(
+        fetch(center.x, yCoord[0]),   // ( 0,-2)
+        fetch(center.x, yCoord[1]),   // ( 0,-1)
+        fetch(xCoord[0], center.y),   // (-2, 0)
+        fetch(xCoord[1], center.y));  // (-1, 0)
+
+    vec4 temp = vec4(
+        fetch(center.x, yCoord[3]),   // ( 0, 2)
+        fetch(center.x, yCoord[2]),   // ( 0, 1)
+        fetch(xCoord[3], center.y),   // ( 2, 0)
+        fetch(xCoord[2], center.y));  // ( 1, 0)
+
+    // Even the simplest compilers should be able to constant-fold these to
+    // avoid the division.
+    // Note that on scalar processors these constants force computation of some
+    // identical products twice.
+    const vec4 kA = vec4(-1.0, -1.5,  0.5, -1.0) / 8.0;
+    const vec4 kB = vec4( 2.0,  0.0,  0.0,  4.0) / 8.0;
+    const vec4 kD = vec4( 0.0,  2.0, -1.0, -1.0) / 8.0;
+
+    // Conserve constant registers and take advantage of free swizzle on load
+    #define kE (kA.xywz)
+    #define kF (kB.xywz)
+
+    value += temp;
+
+    // There are five filter patterns (identity, cross, checker,
+    // theta, phi).  Precompute the terms from all of them and then
+    // use swizzles to assign to color channels.
+    //
+    // Channel   Matches
+    //   x       cross   (e.g., EE G)
+    //   y       checker (e.g., EE B)
+    //   z       theta   (e.g., EO R)
+    //   w       phi     (e.g., EO R)
+    #define A (value[0])
+    #define B (value[1])
+    #define D (Dvec.x)
+    #define E (value[2])
+    #define F (value[3])
+
+    // Avoid zero elements. On a scalar processor this saves two MADDs
+    // and it has no effect on a vector processor.
+    PATTERN.yzw += (kD.yz * D).xyy;
+
+    PATTERN += (kA.xyz * A).xyzx + (kE.xyw * E).xyxz;
+    PATTERN.xw  += kB.xw * B;
+    PATTERN.xz  += kF.xz * F;
+
+    gl_FragColor.rgb = (alternate.y == 0.0) ?
+        ((alternate.x == 0.0) ?
+            vec3(C, PATTERN.xy) :
+            vec3(PATTERN.z, C, PATTERN.w)) :
+        ((alternate.x == 0.0) ?
+            vec3(PATTERN.w, C, PATTERN.z) :
+            vec3(PATTERN.yx, C));
+}
diff --git a/src/apps/qcam/assets/shader/bayer_8.vert b/src/apps/qcam/assets/shader/bayer_8.vert
new file mode 100644
index 00000000..3695a5e9
--- /dev/null
+++ b/src/apps/qcam/assets/shader/bayer_8.vert
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+From http://jgt.akpeters.com/papers/McGuire08/
+
+Efficient, High-Quality Bayer Demosaic Filtering on GPUs
+
+Morgan McGuire
+
+This paper appears in issue Volume 13, Number 4.
+---------------------------------------------------------
+Copyright (c) 2008, Morgan McGuire. All rights reserved.
+
+Modified by Linaro Ltd to integrate it into libcamera.
+Copyright (C) 2021, Linaro
+*/
+
+//Vertex Shader
+
+attribute vec4 vertexIn;
+attribute vec2 textureIn;
+
+uniform vec2 tex_size;	/* The texture size in pixels */
+uniform vec2 tex_step;
+
+/** Pixel position of the first red pixel in the */
+/**  Bayer pattern.  [{0,1}, {0, 1}]*/
+uniform vec2            tex_bayer_first_red;
+
+/** .xy = Pixel being sampled in the fragment shader on the range [0, 1]
+    .zw = ...on the range [0, sourceSize], offset by firstRed */
+varying vec4            center;
+
+/** center.x + (-2/w, -1/w, 1/w, 2/w); These are the x-positions */
+/** of the adjacent pixels.*/
+varying vec4            xCoord;
+
+/** center.y + (-2/h, -1/h, 1/h, 2/h); These are the y-positions */
+/** of the adjacent pixels.*/
+varying vec4            yCoord;
+
+void main(void) {
+    center.xy = textureIn;
+    center.zw = textureIn * tex_size + tex_bayer_first_red;
+
+    xCoord = center.x + vec4(-2.0 * tex_step.x,
+                             -tex_step.x, tex_step.x, 2.0 * tex_step.x);
+    yCoord = center.y + vec4(-2.0 * tex_step.y,
+                              -tex_step.y, tex_step.y, 2.0 * tex_step.y);
+
+    gl_Position = vertexIn;
+}
diff --git a/src/apps/qcam/assets/shader/identity.vert b/src/apps/qcam/assets/shader/identity.vert
new file mode 100644
index 00000000..12c41377
--- /dev/null
+++ b/src/apps/qcam/assets/shader/identity.vert
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Linaro
+ *
+ * identity.vert - Identity vertex shader for pixel format conversion
+ */
+
+attribute vec4 vertexIn;
+attribute vec2 textureIn;
+varying vec2 textureOut;
+
+uniform float stride_factor;
+
+void main(void)
+{
+	gl_Position = vertexIn;
+	textureOut = vec2(textureIn.x * stride_factor, textureIn.y);
+}
diff --git a/src/apps/qcam/assets/shader/shaders.qrc b/src/apps/qcam/assets/shader/shaders.qrc
new file mode 100644
index 00000000..96c709f9
--- /dev/null
+++ b/src/apps/qcam/assets/shader/shaders.qrc
@@ -0,0 +1,13 @@
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+	<file>RGB.frag</file>
+	<file>YUV_2_planes.frag</file>
+	<file>YUV_3_planes.frag</file>
+	<file>YUV_packed.frag</file>
+	<file>bayer_1x_packed.frag</file>
+	<file>bayer_8.frag</file>
+	<file>bayer_8.vert</file>
+	<file>identity.vert</file>
+</qresource>
+</RCC>
diff --git a/src/apps/qcam/cam_select_dialog.cpp b/src/apps/qcam/cam_select_dialog.cpp
new file mode 100644
index 00000000..3c8b12a9
--- /dev/null
+++ b/src/apps/qcam/cam_select_dialog.cpp
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Utkarsh Tiwari <utkarsh02t@gmail.com>
+ *
+ * cam_select_dialog.cpp - qcam - Camera Selection dialog
+ */
+
+#include "cam_select_dialog.h"
+
+#include <memory>
+
+#include <libcamera/camera.h>
+#include <libcamera/camera_manager.h>
+
+#include <QComboBox>
+#include <QDialogButtonBox>
+#include <QFormLayout>
+#include <QLabel>
+#include <QString>
+
+CameraSelectorDialog::CameraSelectorDialog(libcamera::CameraManager *cameraManager,
+					   QWidget *parent)
+	: QDialog(parent), cm_(cameraManager)
+{
+	/* Use a QFormLayout for the dialog. */
+	QFormLayout *layout = new QFormLayout(this);
+
+	/* Setup the camera id combo-box. */
+	cameraIdComboBox_ = new QComboBox;
+	for (const auto &cam : cm_->cameras())
+		cameraIdComboBox_->addItem(QString::fromStdString(cam->id()));
+
+	/* Set camera information labels. */
+	cameraLocation_ = new QLabel;
+	cameraModel_ = new QLabel;
+
+	updateCameraInfo(cameraIdComboBox_->currentText());
+	connect(cameraIdComboBox_, &QComboBox::currentTextChanged,
+		this, &CameraSelectorDialog::updateCameraInfo);
+
+	/* Setup the QDialogButton Box */
+	QDialogButtonBox *buttonBox =
+		new QDialogButtonBox(QDialogButtonBox::Ok |
+				     QDialogButtonBox::Cancel);
+
+	connect(buttonBox, &QDialogButtonBox::accepted,
+		this, &QDialog::accept);
+	connect(buttonBox, &QDialogButtonBox::rejected,
+		this, &QDialog::reject);
+
+	/* Set the layout. */
+	layout->addRow("Camera:", cameraIdComboBox_);
+	layout->addRow("Location:", cameraLocation_);
+	layout->addRow("Model:", cameraModel_);
+	layout->addWidget(buttonBox);
+}
+
+CameraSelectorDialog::~CameraSelectorDialog() = default;
+
+std::string CameraSelectorDialog::getCameraId()
+{
+	return cameraIdComboBox_->currentText().toStdString();
+}
+
+/* Hotplug / Unplug Support. */
+void CameraSelectorDialog::addCamera(QString cameraId)
+{
+	cameraIdComboBox_->addItem(cameraId);
+}
+
+void CameraSelectorDialog::removeCamera(QString cameraId)
+{
+	int cameraIndex = cameraIdComboBox_->findText(cameraId);
+	cameraIdComboBox_->removeItem(cameraIndex);
+}
+
+/* Camera Information */
+void CameraSelectorDialog::updateCameraInfo(QString cameraId)
+{
+	const std::shared_ptr<libcamera::Camera> &camera =
+		cm_->get(cameraId.toStdString());
+
+	if (!camera)
+		return;
+
+	const libcamera::ControlList &properties = camera->properties();
+
+	const auto &location = properties.get(libcamera::properties::Location);
+	if (location) {
+		switch (*location) {
+		case libcamera::properties::CameraLocationFront:
+			cameraLocation_->setText("Internal front camera");
+			break;
+		case libcamera::properties::CameraLocationBack:
+			cameraLocation_->setText("Internal back camera");
+			break;
+		case libcamera::properties::CameraLocationExternal:
+			cameraLocation_->setText("External camera");
+			break;
+		default:
+			cameraLocation_->setText("Unknown");
+		}
+	} else {
+		cameraLocation_->setText("Unknown");
+	}
+
+	const auto &model = properties.get(libcamera::properties::Model)
+				    .value_or("Unknown");
+
+	cameraModel_->setText(QString::fromStdString(model));
+}
diff --git a/src/apps/qcam/cam_select_dialog.h b/src/apps/qcam/cam_select_dialog.h
new file mode 100644
index 00000000..0b7709ed
--- /dev/null
+++ b/src/apps/qcam/cam_select_dialog.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Utkarsh Tiwari <utkarsh02t@gmail.com>
+ *
+ * cam_select_dialog.h - qcam - Camera Selection dialog
+ */
+
+#pragma once
+
+#include <string>
+
+#include <libcamera/camera.h>
+#include <libcamera/camera_manager.h>
+#include <libcamera/controls.h>
+#include <libcamera/property_ids.h>
+
+#include <QDialog>
+#include <QString>
+
+class QComboBox;
+class QLabel;
+
+class CameraSelectorDialog : public QDialog
+{
+	Q_OBJECT
+public:
+	CameraSelectorDialog(libcamera::CameraManager *cameraManager,
+			     QWidget *parent);
+	~CameraSelectorDialog();
+
+	std::string getCameraId();
+
+	/* Hotplug / Unplug Support. */
+	void addCamera(QString cameraId);
+	void removeCamera(QString cameraId);
+
+	/* Camera Information */
+	void updateCameraInfo(QString cameraId);
+
+private:
+	libcamera::CameraManager *cm_;
+
+	/* UI elements. */
+	QComboBox *cameraIdComboBox_;
+	QLabel *cameraLocation_;
+	QLabel *cameraModel_;
+};
diff --git a/src/apps/qcam/format_converter.cpp b/src/apps/qcam/format_converter.cpp
new file mode 100644
index 00000000..9331da0c
--- /dev/null
+++ b/src/apps/qcam/format_converter.cpp
@@ -0,0 +1,359 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * format_convert.cpp - qcam - Convert buffer to RGB
+ */
+
+#include "format_converter.h"
+
+#include <errno.h>
+#include <utility>
+
+#include <QImage>
+
+#include <libcamera/formats.h>
+
+#include "../cam/image.h"
+
+#define RGBSHIFT		8
+#ifndef MAX
+#define MAX(a,b)		((a)>(b)?(a):(b))
+#endif
+#ifndef MIN
+#define MIN(a,b)		((a)<(b)?(a):(b))
+#endif
+#ifndef CLAMP
+#define CLAMP(a,low,high)	MAX((low),MIN((high),(a)))
+#endif
+#ifndef CLIP
+#define CLIP(x)			CLAMP(x,0,255)
+#endif
+
+int FormatConverter::configure(const libcamera::PixelFormat &format,
+			       const QSize &size, unsigned int stride)
+{
+	switch (format) {
+	case libcamera::formats::NV12:
+		formatFamily_ = YUVSemiPlanar;
+		horzSubSample_ = 2;
+		vertSubSample_ = 2;
+		nvSwap_ = false;
+		break;
+	case libcamera::formats::NV21:
+		formatFamily_ = YUVSemiPlanar;
+		horzSubSample_ = 2;
+		vertSubSample_ = 2;
+		nvSwap_ = true;
+		break;
+	case libcamera::formats::NV16:
+		formatFamily_ = YUVSemiPlanar;
+		horzSubSample_ = 2;
+		vertSubSample_ = 1;
+		nvSwap_ = false;
+		break;
+	case libcamera::formats::NV61:
+		formatFamily_ = YUVSemiPlanar;
+		horzSubSample_ = 2;
+		vertSubSample_ = 1;
+		nvSwap_ = true;
+		break;
+	case libcamera::formats::NV24:
+		formatFamily_ = YUVSemiPlanar;
+		horzSubSample_ = 1;
+		vertSubSample_ = 1;
+		nvSwap_ = false;
+		break;
+	case libcamera::formats::NV42:
+		formatFamily_ = YUVSemiPlanar;
+		horzSubSample_ = 1;
+		vertSubSample_ = 1;
+		nvSwap_ = true;
+		break;
+
+	case libcamera::formats::R8:
+		formatFamily_ = RGB;
+		r_pos_ = 0;
+		g_pos_ = 0;
+		b_pos_ = 0;
+		bpp_ = 1;
+		break;
+	case libcamera::formats::RGB888:
+		formatFamily_ = RGB;
+		r_pos_ = 2;
+		g_pos_ = 1;
+		b_pos_ = 0;
+		bpp_ = 3;
+		break;
+	case libcamera::formats::BGR888:
+		formatFamily_ = RGB;
+		r_pos_ = 0;
+		g_pos_ = 1;
+		b_pos_ = 2;
+		bpp_ = 3;
+		break;
+	case libcamera::formats::ARGB8888:
+	case libcamera::formats::XRGB8888:
+		formatFamily_ = RGB;
+		r_pos_ = 2;
+		g_pos_ = 1;
+		b_pos_ = 0;
+		bpp_ = 4;
+		break;
+	case libcamera::formats::RGBA8888:
+	case libcamera::formats::RGBX8888:
+		formatFamily_ = RGB;
+		r_pos_ = 3;
+		g_pos_ = 2;
+		b_pos_ = 1;
+		bpp_ = 4;
+		break;
+	case libcamera::formats::ABGR8888:
+	case libcamera::formats::XBGR8888:
+		formatFamily_ = RGB;
+		r_pos_ = 0;
+		g_pos_ = 1;
+		b_pos_ = 2;
+		bpp_ = 4;
+		break;
+	case libcamera::formats::BGRA8888:
+	case libcamera::formats::BGRX8888:
+		formatFamily_ = RGB;
+		r_pos_ = 1;
+		g_pos_ = 2;
+		b_pos_ = 3;
+		bpp_ = 4;
+		break;
+
+	case libcamera::formats::VYUY:
+		formatFamily_ = YUVPacked;
+		y_pos_ = 1;
+		cb_pos_ = 2;
+		break;
+	case libcamera::formats::YVYU:
+		formatFamily_ = YUVPacked;
+		y_pos_ = 0;
+		cb_pos_ = 3;
+		break;
+	case libcamera::formats::UYVY:
+		formatFamily_ = YUVPacked;
+		y_pos_ = 1;
+		cb_pos_ = 0;
+		break;
+	case libcamera::formats::YUYV:
+		formatFamily_ = YUVPacked;
+		y_pos_ = 0;
+		cb_pos_ = 1;
+		break;
+
+	case libcamera::formats::YUV420:
+		formatFamily_ = YUVPlanar;
+		horzSubSample_ = 2;
+		vertSubSample_ = 2;
+		nvSwap_ = false;
+		break;
+	case libcamera::formats::YVU420:
+		formatFamily_ = YUVPlanar;
+		horzSubSample_ = 2;
+		vertSubSample_ = 2;
+		nvSwap_ = true;
+		break;
+	case libcamera::formats::YUV422:
+		formatFamily_ = YUVPlanar;
+		horzSubSample_ = 2;
+		vertSubSample_ = 1;
+		nvSwap_ = false;
+		break;
+
+	case libcamera::formats::MJPEG:
+		formatFamily_ = MJPEG;
+		break;
+
+	default:
+		return -EINVAL;
+	};
+
+	format_ = format;
+	width_ = size.width();
+	height_ = size.height();
+	stride_ = stride;
+
+	return 0;
+}
+
+void FormatConverter::convert(const Image *src, size_t size, QImage *dst)
+{
+	switch (formatFamily_) {
+	case MJPEG:
+		dst->loadFromData(src->data(0).data(), size, "JPEG");
+		break;
+	case RGB:
+		convertRGB(src, dst->bits());
+		break;
+	case YUVPacked:
+		convertYUVPacked(src, dst->bits());
+		break;
+	case YUVSemiPlanar:
+		convertYUVSemiPlanar(src, dst->bits());
+		break;
+	case YUVPlanar:
+		convertYUVPlanar(src, dst->bits());
+		break;
+	};
+}
+
+static void yuv_to_rgb(int y, int u, int v, int *r, int *g, int *b)
+{
+	int c = y - 16;
+	int d = u - 128;
+	int e = v - 128;
+	*r = CLIP(( 298 * c           + 409 * e + 128) >> RGBSHIFT);
+	*g = CLIP(( 298 * c - 100 * d - 208 * e + 128) >> RGBSHIFT);
+	*b = CLIP(( 298 * c + 516 * d           + 128) >> RGBSHIFT);
+}
+
+void FormatConverter::convertRGB(const Image *srcImage, unsigned char *dst)
+{
+	const unsigned char *src = srcImage->data(0).data();
+	unsigned int x, y;
+	int r, g, b;
+
+	for (y = 0; y < height_; y++) {
+		for (x = 0; x < width_; x++) {
+			r = src[bpp_ * x + r_pos_];
+			g = src[bpp_ * x + g_pos_];
+			b = src[bpp_ * x + b_pos_];
+
+			dst[4 * x + 0] = b;
+			dst[4 * x + 1] = g;
+			dst[4 * x + 2] = r;
+			dst[4 * x + 3] = 0xff;
+		}
+
+		src += stride_;
+		dst += width_ * 4;
+	}
+}
+
+void FormatConverter::convertYUVPacked(const Image *srcImage, unsigned char *dst)
+{
+	const unsigned char *src = srcImage->data(0).data();
+	unsigned int src_x, src_y, dst_x, dst_y;
+	unsigned int src_stride;
+	unsigned int dst_stride;
+	unsigned int cr_pos;
+	int r, g, b, y, cr, cb;
+
+	cr_pos = (cb_pos_ + 2) % 4;
+	src_stride = stride_;
+	dst_stride = width_ * 4;
+
+	for (src_y = 0, dst_y = 0; dst_y < height_; src_y++, dst_y++) {
+		for (src_x = 0, dst_x = 0; dst_x < width_; ) {
+			cb = src[src_y * src_stride + src_x * 4 + cb_pos_];
+			cr = src[src_y * src_stride + src_x * 4 + cr_pos];
+
+			y = src[src_y * src_stride + src_x * 4 + y_pos_];
+			yuv_to_rgb(y, cb, cr, &r, &g, &b);
+			dst[dst_y * dst_stride + 4 * dst_x + 0] = b;
+			dst[dst_y * dst_stride + 4 * dst_x + 1] = g;
+			dst[dst_y * dst_stride + 4 * dst_x + 2] = r;
+			dst[dst_y * dst_stride + 4 * dst_x + 3] = 0xff;
+			dst_x++;
+
+			y = src[src_y * src_stride + src_x * 4 + y_pos_ + 2];
+			yuv_to_rgb(y, cb, cr, &r, &g, &b);
+			dst[dst_y * dst_stride + 4 * dst_x + 0] = b;
+			dst[dst_y * dst_stride + 4 * dst_x + 1] = g;
+			dst[dst_y * dst_stride + 4 * dst_x + 2] = r;
+			dst[dst_y * dst_stride + 4 * dst_x + 3] = 0xff;
+			dst_x++;
+
+			src_x++;
+		}
+	}
+}
+
+void FormatConverter::convertYUVPlanar(const Image *srcImage, unsigned char *dst)
+{
+	unsigned int c_stride = stride_ / horzSubSample_;
+	unsigned int c_inc = horzSubSample_ == 1 ? 1 : 0;
+	const unsigned char *src_y = srcImage->data(0).data();
+	const unsigned char *src_cb = srcImage->data(1).data();
+	const unsigned char *src_cr = srcImage->data(2).data();
+	int r, g, b;
+
+	if (nvSwap_)
+		std::swap(src_cb, src_cr);
+
+	for (unsigned int y = 0; y < height_; y++) {
+		const unsigned char *line_y = src_y + y * stride_;
+		const unsigned char *line_cb = src_cb + (y / vertSubSample_) *
+					       c_stride;
+		const unsigned char *line_cr = src_cr + (y / vertSubSample_) *
+					       c_stride;
+
+		for (unsigned int x = 0; x < width_; x += 2) {
+			yuv_to_rgb(*line_y, *line_cb, *line_cr, &r, &g, &b);
+			dst[0] = b;
+			dst[1] = g;
+			dst[2] = r;
+			dst[3] = 0xff;
+			line_y++;
+			line_cb += c_inc;
+			line_cr += c_inc;
+			dst += 4;
+
+			yuv_to_rgb(*line_y, *line_cb, *line_cr, &r, &g, &b);
+			dst[0] = b;
+			dst[1] = g;
+			dst[2] = r;
+			dst[3] = 0xff;
+			line_y++;
+			line_cb += 1;
+			line_cr += 1;
+			dst += 4;
+		}
+	}
+}
+
+void FormatConverter::convertYUVSemiPlanar(const Image *srcImage, unsigned char *dst)
+{
+	unsigned int c_stride = stride_ * (2 / horzSubSample_);
+	unsigned int c_inc = horzSubSample_ == 1 ? 2 : 0;
+	unsigned int cb_pos = nvSwap_ ? 1 : 0;
+	unsigned int cr_pos = nvSwap_ ? 0 : 1;
+	const unsigned char *src = srcImage->data(0).data();
+	const unsigned char *src_c = srcImage->data(1).data();
+	int r, g, b;
+
+	for (unsigned int y = 0; y < height_; y++) {
+		const unsigned char *src_y = src + y * stride_;
+		const unsigned char *src_cb = src_c + (y / vertSubSample_) *
+					      c_stride + cb_pos;
+		const unsigned char *src_cr = src_c + (y / vertSubSample_) *
+					      c_stride + cr_pos;
+
+		for (unsigned int x = 0; x < width_; x += 2) {
+			yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
+			dst[0] = b;
+			dst[1] = g;
+			dst[2] = r;
+			dst[3] = 0xff;
+			src_y++;
+			src_cb += c_inc;
+			src_cr += c_inc;
+			dst += 4;
+
+			yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
+			dst[0] = b;
+			dst[1] = g;
+			dst[2] = r;
+			dst[3] = 0xff;
+			src_y++;
+			src_cb += 2;
+			src_cr += 2;
+			dst += 4;
+		}
+	}
+}
diff --git a/src/apps/qcam/format_converter.h b/src/apps/qcam/format_converter.h
new file mode 100644
index 00000000..37dbfae2
--- /dev/null
+++ b/src/apps/qcam/format_converter.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * format_convert.h - qcam - Convert buffer to RGB
+ */
+
+#pragma once
+
+#include <stddef.h>
+
+#include <QSize>
+
+#include <libcamera/pixel_format.h>
+
+class Image;
+class QImage;
+
+class FormatConverter
+{
+public:
+	int configure(const libcamera::PixelFormat &format, const QSize &size,
+		      unsigned int stride);
+
+	void convert(const Image *src, size_t size, QImage *dst);
+
+private:
+	enum FormatFamily {
+		MJPEG,
+		RGB,
+		YUVPacked,
+		YUVPlanar,
+		YUVSemiPlanar,
+	};
+
+	void convertRGB(const Image *src, unsigned char *dst);
+	void convertYUVPacked(const Image *src, unsigned char *dst);
+	void convertYUVPlanar(const Image *src, unsigned char *dst);
+	void convertYUVSemiPlanar(const Image *src, unsigned char *dst);
+
+	libcamera::PixelFormat format_;
+	unsigned int width_;
+	unsigned int height_;
+	unsigned int stride_;
+
+	enum FormatFamily formatFamily_;
+
+	/* NV parameters */
+	unsigned int horzSubSample_;
+	unsigned int vertSubSample_;
+	bool nvSwap_;
+
+	/* RGB parameters */
+	unsigned int bpp_;
+	unsigned int r_pos_;
+	unsigned int g_pos_;
+	unsigned int b_pos_;
+
+	/* YUV parameters */
+	unsigned int y_pos_;
+	unsigned int cb_pos_;
+};
diff --git a/src/apps/qcam/main.cpp b/src/apps/qcam/main.cpp
new file mode 100644
index 00000000..d3f01a85
--- /dev/null
+++ b/src/apps/qcam/main.cpp
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * main.cpp - qcam - The libcamera GUI test application
+ */
+
+#include <signal.h>
+#include <string.h>
+
+#include <QApplication>
+#include <QtDebug>
+
+#include <libcamera/camera_manager.h>
+
+#include "../cam/options.h"
+#include "../cam/stream_options.h"
+#include "main_window.h"
+#include "message_handler.h"
+
+using namespace libcamera;
+
+void signalHandler([[maybe_unused]] int signal)
+{
+	qInfo() << "Exiting";
+	qApp->quit();
+}
+
+OptionsParser::Options parseOptions(int argc, char *argv[])
+{
+	StreamKeyValueParser streamKeyValue;
+
+	OptionsParser parser;
+	parser.addOption(OptCamera, OptionString,
+			 "Specify which camera to operate on", "camera",
+			 ArgumentRequired, "camera");
+	parser.addOption(OptHelp, OptionNone, "Display this help message",
+			 "help");
+	parser.addOption(OptRenderer, OptionString,
+			 "Choose the renderer type {qt,gles} (default: qt)",
+			 "renderer", ArgumentRequired, "renderer");
+	parser.addOption(OptStream, &streamKeyValue,
+			 "Set configuration of a camera stream", "stream", true);
+	parser.addOption(OptVerbose, OptionNone,
+			 "Print verbose log messages", "verbose");
+
+	OptionsParser::Options options = parser.parse(argc, argv);
+	if (options.isSet(OptHelp))
+		parser.usage();
+
+	return options;
+}
+
+int main(int argc, char **argv)
+{
+	QApplication app(argc, argv);
+	int ret;
+
+	OptionsParser::Options options = parseOptions(argc, argv);
+	if (!options.valid())
+		return EXIT_FAILURE;
+	if (options.isSet(OptHelp))
+		return 0;
+
+	MessageHandler msgHandler(options.isSet(OptVerbose));
+
+	struct sigaction sa = {};
+	sa.sa_handler = &signalHandler;
+	sigaction(SIGINT, &sa, nullptr);
+
+	CameraManager *cm = new libcamera::CameraManager();
+
+	ret = cm->start();
+	if (ret) {
+		qInfo() << "Failed to start camera manager:"
+			<< strerror(-ret);
+		return EXIT_FAILURE;
+	}
+
+	MainWindow *mainWindow = new MainWindow(cm, options);
+	mainWindow->show();
+	ret = app.exec();
+	delete mainWindow;
+
+	cm->stop();
+	delete cm;
+
+	return ret;
+}
diff --git a/src/apps/qcam/main_window.cpp b/src/apps/qcam/main_window.cpp
new file mode 100644
index 00000000..f553ccb0
--- /dev/null
+++ b/src/apps/qcam/main_window.cpp
@@ -0,0 +1,790 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * main_window.cpp - qcam - Main application window
+ */
+
+#include "main_window.h"
+
+#include <assert.h>
+#include <iomanip>
+#include <string>
+
+#include <libcamera/camera_manager.h>
+#include <libcamera/version.h>
+
+#include <QCoreApplication>
+#include <QFileDialog>
+#include <QImage>
+#include <QImageWriter>
+#include <QMutexLocker>
+#include <QStandardPaths>
+#include <QStringList>
+#include <QTimer>
+#include <QToolBar>
+#include <QToolButton>
+#include <QtDebug>
+
+#include "../cam/dng_writer.h"
+#include "../cam/image.h"
+
+#include "cam_select_dialog.h"
+#ifndef QT_NO_OPENGL
+#include "viewfinder_gl.h"
+#endif
+#include "viewfinder_qt.h"
+
+using namespace libcamera;
+
+#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
+/*
+ * Qt::fixed was introduced in v5.14, and ::fixed deprecated in v5.15. Allow
+ * usage of Qt::fixed unconditionally.
+ */
+namespace Qt {
+constexpr auto fixed = ::fixed;
+} /* namespace Qt */
+#endif
+
+/**
+ * \brief Custom QEvent to signal capture completion
+ */
+class CaptureEvent : public QEvent
+{
+public:
+	CaptureEvent()
+		: QEvent(type())
+	{
+	}
+
+	static Type type()
+	{
+		static int type = QEvent::registerEventType();
+		return static_cast<Type>(type);
+	}
+};
+
+/**
+ * \brief Custom QEvent to signal hotplug or unplug
+ */
+class HotplugEvent : public QEvent
+{
+public:
+	enum PlugEvent {
+		HotPlug,
+		HotUnplug
+	};
+
+	HotplugEvent(std::shared_ptr<Camera> camera, PlugEvent event)
+		: QEvent(type()), camera_(std::move(camera)), plugEvent_(event)
+	{
+	}
+
+	static Type type()
+	{
+		static int type = QEvent::registerEventType();
+		return static_cast<Type>(type);
+	}
+
+	PlugEvent hotplugEvent() const { return plugEvent_; }
+	Camera *camera() const { return camera_.get(); }
+
+private:
+	std::shared_ptr<Camera> camera_;
+	PlugEvent plugEvent_;
+};
+
+MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
+	: saveRaw_(nullptr), options_(options), cm_(cm), allocator_(nullptr),
+	  isCapturing_(false), captureRaw_(false)
+{
+	int ret;
+
+	/*
+	 * Initialize the UI: Create the toolbar, set the window title and
+	 * create the viewfinder widget.
+	 */
+	createToolbars();
+
+	title_ = "QCam " + QString::fromStdString(CameraManager::version());
+	setWindowTitle(title_);
+	connect(&titleTimer_, SIGNAL(timeout()), this, SLOT(updateTitle()));
+
+	/* Renderer type Qt or GLES, select Qt by default. */
+	std::string renderType = "qt";
+	if (options_.isSet(OptRenderer))
+		renderType = options_[OptRenderer].toString();
+
+	if (renderType == "qt") {
+		ViewFinderQt *viewfinder = new ViewFinderQt(this);
+		connect(viewfinder, &ViewFinderQt::renderComplete,
+			this, &MainWindow::renderComplete);
+		viewfinder_ = viewfinder;
+		setCentralWidget(viewfinder);
+#ifndef QT_NO_OPENGL
+	} else if (renderType == "gles") {
+		ViewFinderGL *viewfinder = new ViewFinderGL(this);
+		connect(viewfinder, &ViewFinderGL::renderComplete,
+			this, &MainWindow::renderComplete);
+		viewfinder_ = viewfinder;
+		setCentralWidget(viewfinder);
+#endif
+	} else {
+		qWarning() << "Invalid render type"
+			   << QString::fromStdString(renderType);
+		quit();
+		return;
+	}
+
+	adjustSize();
+
+	/* Hotplug/unplug support */
+	cm_->cameraAdded.connect(this, &MainWindow::addCamera);
+	cm_->cameraRemoved.connect(this, &MainWindow::removeCamera);
+
+	cameraSelectorDialog_ = new CameraSelectorDialog(cm_, this);
+
+	/* Open the camera and start capture. */
+	ret = openCamera();
+	if (ret < 0) {
+		quit();
+		return;
+	}
+
+	startStopAction_->setChecked(true);
+}
+
+MainWindow::~MainWindow()
+{
+	if (camera_) {
+		stopCapture();
+		camera_->release();
+		camera_.reset();
+	}
+}
+
+bool MainWindow::event(QEvent *e)
+{
+	if (e->type() == CaptureEvent::type()) {
+		processCapture();
+		return true;
+	} else if (e->type() == HotplugEvent::type()) {
+		processHotplug(static_cast<HotplugEvent *>(e));
+		return true;
+	}
+
+	return QMainWindow::event(e);
+}
+
+int MainWindow::createToolbars()
+{
+	QAction *action;
+
+	toolbar_ = addToolBar("Main");
+
+	/* Disable right click context menu. */
+	toolbar_->setContextMenuPolicy(Qt::PreventContextMenu);
+
+	/* Quit action. */
+	action = toolbar_->addAction(QIcon::fromTheme("application-exit",
+						      QIcon(":x-circle.svg")),
+				     "Quit");
+	action->setShortcut(Qt::CTRL | Qt::Key_Q);
+	connect(action, &QAction::triggered, this, &MainWindow::quit);
+
+	/* Camera selector. */
+	cameraSelectButton_ = new QPushButton;
+	connect(cameraSelectButton_, &QPushButton::clicked,
+		this, &MainWindow::switchCamera);
+
+	toolbar_->addWidget(cameraSelectButton_);
+
+	toolbar_->addSeparator();
+
+	/* Start/Stop action. */
+	iconPlay_ = QIcon::fromTheme("media-playback-start",
+				     QIcon(":play-circle.svg"));
+	iconStop_ = QIcon::fromTheme("media-playback-stop",
+				     QIcon(":stop-circle.svg"));
+
+	action = toolbar_->addAction(iconPlay_, "Start Capture");
+	action->setCheckable(true);
+	action->setShortcut(Qt::Key_Space);
+	connect(action, &QAction::toggled, this, &MainWindow::toggleCapture);
+	startStopAction_ = action;
+
+	/* Save As... action. */
+	action = toolbar_->addAction(QIcon::fromTheme("document-save-as",
+						      QIcon(":save.svg")),
+				     "Save As...");
+	action->setShortcut(QKeySequence::SaveAs);
+	connect(action, &QAction::triggered, this, &MainWindow::saveImageAs);
+
+#ifdef HAVE_DNG
+	/* Save Raw action. */
+	action = toolbar_->addAction(QIcon::fromTheme("camera-photo",
+						      QIcon(":aperture.svg")),
+				     "Save Raw");
+	action->setEnabled(false);
+	connect(action, &QAction::triggered, this, &MainWindow::captureRaw);
+	saveRaw_ = action;
+#endif
+
+	return 0;
+}
+
+void MainWindow::quit()
+{
+	QTimer::singleShot(0, QCoreApplication::instance(),
+			   &QCoreApplication::quit);
+}
+
+void MainWindow::updateTitle()
+{
+	/* Calculate the average frame rate over the last period. */
+	unsigned int duration = frameRateInterval_.elapsed();
+	unsigned int frames = framesCaptured_ - previousFrames_;
+	double fps = frames * 1000.0 / duration;
+
+	/* Restart counters. */
+	frameRateInterval_.start();
+	previousFrames_ = framesCaptured_;
+
+	setWindowTitle(title_ + " : " + QString::number(fps, 'f', 2) + " fps");
+}
+
+/* -----------------------------------------------------------------------------
+ * Camera Selection
+ */
+
+void MainWindow::switchCamera()
+{
+	/* Get and acquire the new camera. */
+	std::string newCameraId = chooseCamera();
+
+	if (newCameraId.empty())
+		return;
+
+	if (camera_ && newCameraId == camera_->id())
+		return;
+
+	const std::shared_ptr<Camera> &cam = cm_->get(newCameraId);
+
+	if (cam->acquire()) {
+		qInfo() << "Failed to acquire camera" << cam->id().c_str();
+		return;
+	}
+
+	qInfo() << "Switching to camera" << cam->id().c_str();
+
+	/*
+	 * Stop the capture session, release the current camera, replace it with
+	 * the new camera and start a new capture session.
+	 */
+	startStopAction_->setChecked(false);
+
+	if (camera_)
+		camera_->release();
+
+	camera_ = cam;
+
+	startStopAction_->setChecked(true);
+
+	/* Display the current cameraId in the toolbar .*/
+	cameraSelectButton_->setText(QString::fromStdString(newCameraId));
+}
+
+std::string MainWindow::chooseCamera()
+{
+	if (cameraSelectorDialog_->exec() != QDialog::Accepted)
+		return std::string();
+
+	return cameraSelectorDialog_->getCameraId();
+}
+
+int MainWindow::openCamera()
+{
+	std::string cameraName;
+
+	/*
+	 * Use the camera specified on the command line, if any, or display the
+	 * camera selection dialog box otherwise.
+	 */
+	if (options_.isSet(OptCamera))
+		cameraName = static_cast<std::string>(options_[OptCamera]);
+	else
+		cameraName = chooseCamera();
+
+	if (cameraName == "")
+		return -EINVAL;
+
+	/* Get and acquire the camera. */
+	camera_ = cm_->get(cameraName);
+	if (!camera_) {
+		qInfo() << "Camera" << cameraName.c_str() << "not found";
+		return -ENODEV;
+	}
+
+	if (camera_->acquire()) {
+		qInfo() << "Failed to acquire camera";
+		camera_.reset();
+		return -EBUSY;
+	}
+
+	/* Set the camera switch button with the currently selected Camera id. */
+	cameraSelectButton_->setText(QString::fromStdString(cameraName));
+
+	return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Capture Start & Stop
+ */
+
+void MainWindow::toggleCapture(bool start)
+{
+	if (start) {
+		startCapture();
+		startStopAction_->setIcon(iconStop_);
+		startStopAction_->setText("Stop Capture");
+	} else {
+		stopCapture();
+		startStopAction_->setIcon(iconPlay_);
+		startStopAction_->setText("Start Capture");
+	}
+}
+
+/**
+ * \brief Start capture with the current camera
+ *
+ * This function shall not be called directly, use toggleCapture() instead.
+ */
+int MainWindow::startCapture()
+{
+	StreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);
+	int ret;
+
+	/* Verify roles are supported. */
+	switch (roles.size()) {
+	case 1:
+		if (roles[0] != StreamRole::Viewfinder) {
+			qWarning() << "Only viewfinder supported for single stream";
+			return -EINVAL;
+		}
+		break;
+	case 2:
+		if (roles[0] != StreamRole::Viewfinder ||
+		    roles[1] != StreamRole::Raw) {
+			qWarning() << "Only viewfinder + raw supported for dual streams";
+			return -EINVAL;
+		}
+		break;
+	default:
+		if (roles.size() != 1) {
+			qWarning() << "Unsupported stream configuration";
+			return -EINVAL;
+		}
+		break;
+	}
+
+	/* Configure the camera. */
+	config_ = camera_->generateConfiguration(roles);
+	if (!config_) {
+		qWarning() << "Failed to generate configuration from roles";
+		return -EINVAL;
+	}
+
+	StreamConfiguration &vfConfig = config_->at(0);
+
+	/* Use a format supported by the viewfinder if available. */
+	std::vector<PixelFormat> formats = vfConfig.formats().pixelformats();
+	for (const PixelFormat &format : viewfinder_->nativeFormats()) {
+		auto match = std::find_if(formats.begin(), formats.end(),
+					  [&](const PixelFormat &f) {
+						  return f == format;
+					  });
+		if (match != formats.end()) {
+			vfConfig.pixelFormat = format;
+			break;
+		}
+	}
+
+	/* Allow user to override configuration. */
+	if (StreamKeyValueParser::updateConfiguration(config_.get(),
+						      options_[OptStream])) {
+		qWarning() << "Failed to update configuration";
+		return -EINVAL;
+	}
+
+	CameraConfiguration::Status validation = config_->validate();
+	if (validation == CameraConfiguration::Invalid) {
+		qWarning() << "Failed to create valid camera configuration";
+		return -EINVAL;
+	}
+
+	if (validation == CameraConfiguration::Adjusted)
+		qInfo() << "Stream configuration adjusted to "
+			<< vfConfig.toString().c_str();
+
+	ret = camera_->configure(config_.get());
+	if (ret < 0) {
+		qInfo() << "Failed to configure camera";
+		return ret;
+	}
+
+	/* Store stream allocation. */
+	vfStream_ = config_->at(0).stream();
+	if (config_->size() == 2)
+		rawStream_ = config_->at(1).stream();
+	else
+		rawStream_ = nullptr;
+
+	/*
+	 * Configure the viewfinder. If no color space is reported, default to
+	 * sYCC.
+	 */
+	ret = viewfinder_->setFormat(vfConfig.pixelFormat,
+				     QSize(vfConfig.size.width, vfConfig.size.height),
+				     vfConfig.colorSpace.value_or(ColorSpace::Sycc),
+				     vfConfig.stride);
+	if (ret < 0) {
+		qInfo() << "Failed to set viewfinder format";
+		return ret;
+	}
+
+	adjustSize();
+
+	/* Configure the raw capture button. */
+	if (saveRaw_)
+		saveRaw_->setEnabled(config_->size() == 2);
+
+	/* Allocate and map buffers. */
+	allocator_ = new FrameBufferAllocator(camera_);
+	for (StreamConfiguration &config : *config_) {
+		Stream *stream = config.stream();
+
+		ret = allocator_->allocate(stream);
+		if (ret < 0) {
+			qWarning() << "Failed to allocate capture buffers";
+			goto error;
+		}
+
+		for (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {
+			/* Map memory buffers and cache the mappings. */
+			std::unique_ptr<Image> image =
+				Image::fromFrameBuffer(buffer.get(), Image::MapMode::ReadOnly);
+			assert(image != nullptr);
+			mappedBuffers_[buffer.get()] = std::move(image);
+
+			/* Store buffers on the free list. */
+			freeBuffers_[stream].enqueue(buffer.get());
+		}
+	}
+
+	/* Create requests and fill them with buffers from the viewfinder. */
+	while (!freeBuffers_[vfStream_].isEmpty()) {
+		FrameBuffer *buffer = freeBuffers_[vfStream_].dequeue();
+
+		std::unique_ptr<Request> request = camera_->createRequest();
+		if (!request) {
+			qWarning() << "Can't create request";
+			ret = -ENOMEM;
+			goto error;
+		}
+
+		ret = request->addBuffer(vfStream_, buffer);
+		if (ret < 0) {
+			qWarning() << "Can't set buffer for request";
+			goto error;
+		}
+
+		requests_.push_back(std::move(request));
+	}
+
+	/* Start the title timer and the camera. */
+	titleTimer_.start(2000);
+	frameRateInterval_.start();
+	previousFrames_ = 0;
+	framesCaptured_ = 0;
+	lastBufferTime_ = 0;
+
+	ret = camera_->start();
+	if (ret) {
+		qInfo() << "Failed to start capture";
+		goto error;
+	}
+
+	camera_->requestCompleted.connect(this, &MainWindow::requestComplete);
+
+	/* Queue all requests. */
+	for (std::unique_ptr<Request> &request : requests_) {
+		ret = queueRequest(request.get());
+		if (ret < 0) {
+			qWarning() << "Can't queue request";
+			goto error_disconnect;
+		}
+	}
+
+	isCapturing_ = true;
+
+	return 0;
+
+error_disconnect:
+	camera_->requestCompleted.disconnect(this);
+	camera_->stop();
+
+error:
+	requests_.clear();
+
+	mappedBuffers_.clear();
+
+	freeBuffers_.clear();
+
+	delete allocator_;
+	allocator_ = nullptr;
+
+	return ret;
+}
+
+/**
+ * \brief Stop ongoing capture
+ *
+ * This function may be called directly when tearing down the MainWindow. Use
+ * toggleCapture() instead in all other cases.
+ */
+void MainWindow::stopCapture()
+{
+	if (!isCapturing_)
+		return;
+
+	viewfinder_->stop();
+	if (saveRaw_)
+		saveRaw_->setEnabled(false);
+	captureRaw_ = false;
+
+	int ret = camera_->stop();
+	if (ret)
+		qInfo() << "Failed to stop capture";
+
+	camera_->requestCompleted.disconnect(this);
+
+	mappedBuffers_.clear();
+
+	requests_.clear();
+	freeQueue_.clear();
+
+	delete allocator_;
+
+	isCapturing_ = false;
+
+	config_.reset();
+
+	/*
+	 * A CaptureEvent may have been posted before we stopped the camera,
+	 * but not processed yet. Clear the queue of done buffers to avoid
+	 * racing with the event handler.
+	 */
+	freeBuffers_.clear();
+	doneQueue_.clear();
+
+	titleTimer_.stop();
+	setWindowTitle(title_);
+}
+
+/* -----------------------------------------------------------------------------
+ * Camera hotplugging support
+ */
+
+void MainWindow::processHotplug(HotplugEvent *e)
+{
+	Camera *camera = e->camera();
+	QString cameraId = QString::fromStdString(camera->id());
+	HotplugEvent::PlugEvent event = e->hotplugEvent();
+
+	if (event == HotplugEvent::HotPlug) {
+		cameraSelectorDialog_->addCamera(cameraId);
+	} else if (event == HotplugEvent::HotUnplug) {
+		/* Check if the currently-streaming camera is removed. */
+		if (camera == camera_.get()) {
+			toggleCapture(false);
+			camera_->release();
+			camera_.reset();
+		}
+
+		cameraSelectorDialog_->removeCamera(cameraId);
+	}
+}
+
+void MainWindow::addCamera(std::shared_ptr<Camera> camera)
+{
+	qInfo() << "Adding new camera:" << camera->id().c_str();
+	QCoreApplication::postEvent(this,
+				    new HotplugEvent(std::move(camera),
+						     HotplugEvent::HotPlug));
+}
+
+void MainWindow::removeCamera(std::shared_ptr<Camera> camera)
+{
+	qInfo() << "Removing camera:" << camera->id().c_str();
+	QCoreApplication::postEvent(this,
+				    new HotplugEvent(std::move(camera),
+						     HotplugEvent::HotUnplug));
+}
+
+/* -----------------------------------------------------------------------------
+ * Image Save
+ */
+
+void MainWindow::saveImageAs()
+{
+	QImage image = viewfinder_->getCurrentImage();
+	QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
+
+	QString filename = QFileDialog::getSaveFileName(this, "Save Image", defaultPath,
+							"Image Files (*.png *.jpg *.jpeg)");
+	if (filename.isEmpty())
+		return;
+
+	QImageWriter writer(filename);
+	writer.setQuality(95);
+	writer.write(image);
+}
+
+void MainWindow::captureRaw()
+{
+	captureRaw_ = true;
+}
+
+void MainWindow::processRaw(FrameBuffer *buffer,
+			    [[maybe_unused]] const ControlList &metadata)
+{
+#ifdef HAVE_DNG
+	QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
+	QString filename = QFileDialog::getSaveFileName(this, "Save DNG", defaultPath,
+							"DNG Files (*.dng)");
+
+	if (!filename.isEmpty()) {
+		uint8_t *memory = mappedBuffers_[buffer]->data(0).data();
+		DNGWriter::write(filename.toStdString().c_str(), camera_.get(),
+				 rawStream_->configuration(), metadata, buffer,
+				 memory);
+	}
+#endif
+
+	{
+		QMutexLocker locker(&mutex_);
+		freeBuffers_[rawStream_].enqueue(buffer);
+	}
+}
+
+/* -----------------------------------------------------------------------------
+ * Request Completion Handling
+ */
+
+void MainWindow::requestComplete(Request *request)
+{
+	if (request->status() == Request::RequestCancelled)
+		return;
+
+	/*
+	 * We're running in the libcamera thread context, expensive operations
+	 * are not allowed. Add the buffer to the done queue and post a
+	 * CaptureEvent for the application thread to handle.
+	 */
+	{
+		QMutexLocker locker(&mutex_);
+		doneQueue_.enqueue(request);
+	}
+
+	QCoreApplication::postEvent(this, new CaptureEvent);
+}
+
+void MainWindow::processCapture()
+{
+	/*
+	 * Retrieve the next buffer from the done queue. The queue may be empty
+	 * if stopCapture() has been called while a CaptureEvent was posted but
+	 * not processed yet. Return immediately in that case.
+	 */
+	Request *request;
+	{
+		QMutexLocker locker(&mutex_);
+		if (doneQueue_.isEmpty())
+			return;
+
+		request = doneQueue_.dequeue();
+	}
+
+	/* Process buffers. */
+	if (request->buffers().count(vfStream_))
+		processViewfinder(request->buffers().at(vfStream_));
+
+	if (request->buffers().count(rawStream_))
+		processRaw(request->buffers().at(rawStream_), request->metadata());
+
+	request->reuse();
+	QMutexLocker locker(&mutex_);
+	freeQueue_.enqueue(request);
+}
+
+void MainWindow::processViewfinder(FrameBuffer *buffer)
+{
+	framesCaptured_++;
+
+	const FrameMetadata &metadata = buffer->metadata();
+
+	double fps = metadata.timestamp - lastBufferTime_;
+	fps = lastBufferTime_ && fps ? 1000000000.0 / fps : 0.0;
+	lastBufferTime_ = metadata.timestamp;
+
+	QStringList bytesused;
+	for (const FrameMetadata::Plane &plane : metadata.planes())
+		bytesused << QString::number(plane.bytesused);
+
+	qDebug().noquote()
+		<< QString("seq: %1").arg(metadata.sequence, 6, 10, QLatin1Char('0'))
+		<< "bytesused: {" << bytesused.join(", ")
+		<< "} timestamp:" << metadata.timestamp
+		<< "fps:" << Qt::fixed << qSetRealNumberPrecision(2) << fps;
+
+	/* Render the frame on the viewfinder. */
+	viewfinder_->render(buffer, mappedBuffers_[buffer].get());
+}
+
+void MainWindow::renderComplete(FrameBuffer *buffer)
+{
+	Request *request;
+	{
+		QMutexLocker locker(&mutex_);
+		if (freeQueue_.isEmpty())
+			return;
+
+		request = freeQueue_.dequeue();
+	}
+
+	request->addBuffer(vfStream_, buffer);
+
+	if (captureRaw_) {
+		FrameBuffer *rawBuffer = nullptr;
+
+		{
+			QMutexLocker locker(&mutex_);
+			if (!freeBuffers_[rawStream_].isEmpty())
+				rawBuffer = freeBuffers_[rawStream_].dequeue();
+		}
+
+		if (rawBuffer) {
+			request->addBuffer(rawStream_, rawBuffer);
+			captureRaw_ = false;
+		} else {
+			qWarning() << "No free buffer available for RAW capture";
+		}
+	}
+	queueRequest(request);
+}
+
+int MainWindow::queueRequest(Request *request)
+{
+	return camera_->queueRequest(request);
+}
diff --git a/src/apps/qcam/main_window.h b/src/apps/qcam/main_window.h
new file mode 100644
index 00000000..95b64124
--- /dev/null
+++ b/src/apps/qcam/main_window.h
@@ -0,0 +1,133 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * main_window.h - qcam - Main application window
+ */
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include <libcamera/camera.h>
+#include <libcamera/camera_manager.h>
+#include <libcamera/controls.h>
+#include <libcamera/framebuffer.h>
+#include <libcamera/framebuffer_allocator.h>
+#include <libcamera/request.h>
+#include <libcamera/stream.h>
+
+#include <QElapsedTimer>
+#include <QIcon>
+#include <QMainWindow>
+#include <QMutex>
+#include <QObject>
+#include <QPushButton>
+#include <QQueue>
+#include <QTimer>
+
+#include "../cam/stream_options.h"
+
+#include "viewfinder.h"
+
+class QAction;
+
+class CameraSelectorDialog;
+class Image;
+class HotplugEvent;
+
+enum {
+	OptCamera = 'c',
+	OptHelp = 'h',
+	OptRenderer = 'r',
+	OptStream = 's',
+	OptVerbose = 'v',
+};
+
+class MainWindow : public QMainWindow
+{
+	Q_OBJECT
+
+public:
+	MainWindow(libcamera::CameraManager *cm,
+		   const OptionsParser::Options &options);
+	~MainWindow();
+
+	bool event(QEvent *e) override;
+
+private Q_SLOTS:
+	void quit();
+	void updateTitle();
+
+	void switchCamera();
+	void toggleCapture(bool start);
+
+	void saveImageAs();
+	void captureRaw();
+	void processRaw(libcamera::FrameBuffer *buffer,
+			const libcamera::ControlList &metadata);
+
+	void renderComplete(libcamera::FrameBuffer *buffer);
+
+private:
+	int createToolbars();
+
+	std::string chooseCamera();
+	int openCamera();
+
+	int startCapture();
+	void stopCapture();
+
+	void addCamera(std::shared_ptr<libcamera::Camera> camera);
+	void removeCamera(std::shared_ptr<libcamera::Camera> camera);
+
+	int queueRequest(libcamera::Request *request);
+	void requestComplete(libcamera::Request *request);
+	void processCapture();
+	void processHotplug(HotplugEvent *e);
+	void processViewfinder(libcamera::FrameBuffer *buffer);
+
+	/* UI elements */
+	QToolBar *toolbar_;
+	QAction *startStopAction_;
+	QPushButton *cameraSelectButton_;
+	QAction *saveRaw_;
+	ViewFinder *viewfinder_;
+
+	QIcon iconPlay_;
+	QIcon iconStop_;
+
+	QString title_;
+	QTimer titleTimer_;
+
+	CameraSelectorDialog *cameraSelectorDialog_;
+
+	/* Options */
+	const OptionsParser::Options &options_;
+
+	/* Camera manager, camera, configuration and buffers */
+	libcamera::CameraManager *cm_;
+	std::shared_ptr<libcamera::Camera> camera_;
+	libcamera::FrameBufferAllocator *allocator_;
+
+	std::unique_ptr<libcamera::CameraConfiguration> config_;
+	std::map<libcamera::FrameBuffer *, std::unique_ptr<Image>> mappedBuffers_;
+
+	/* Capture state, buffers queue and statistics */
+	bool isCapturing_;
+	bool captureRaw_;
+	libcamera::Stream *vfStream_;
+	libcamera::Stream *rawStream_;
+	std::map<const libcamera::Stream *, QQueue<libcamera::FrameBuffer *>> freeBuffers_;
+	QQueue<libcamera::Request *> doneQueue_;
+	QQueue<libcamera::Request *> freeQueue_;
+	QMutex mutex_; /* Protects freeBuffers_, doneQueue_, and freeQueue_ */
+
+	uint64_t lastBufferTime_;
+	QElapsedTimer frameRateInterval_;
+	uint32_t previousFrames_;
+	uint32_t framesCaptured_;
+
+	std::vector<std::unique_ptr<libcamera::Request>> requests_;
+};
diff --git a/src/apps/qcam/meson.build b/src/apps/qcam/meson.build
new file mode 100644
index 00000000..d5916d0d
--- /dev/null
+++ b/src/apps/qcam/meson.build
@@ -0,0 +1,83 @@
+# SPDX-License-Identifier: CC0-1.0
+
+qt5 = import('qt5')
+qt5_dep = dependency('qt5',
+                     method : 'pkg-config',
+                     modules : ['Core', 'Gui', 'Widgets'],
+                     required : get_option('qcam'),
+                     version : '>=5.4')
+
+if not qt5_dep.found()
+    qcam_enabled = false
+    subdir_done()
+endif
+
+qcam_enabled = true
+
+qcam_sources = files([
+    '../cam/image.cpp',
+    '../cam/options.cpp',
+    '../cam/stream_options.cpp',
+    'cam_select_dialog.cpp',
+    'format_converter.cpp',
+    'main.cpp',
+    'main_window.cpp',
+    'message_handler.cpp',
+    'viewfinder_qt.cpp',
+])
+
+qcam_moc_headers = files([
+    'cam_select_dialog.h',
+    'main_window.h',
+    'viewfinder_qt.h',
+])
+
+qcam_resources = files([
+    'assets/feathericons/feathericons.qrc',
+])
+
+qt5_cpp_args = ['-DQT_NO_KEYWORDS']
+
+tiff_dep = dependency('libtiff-4', required : false)
+if tiff_dep.found()
+    qt5_cpp_args += ['-DHAVE_TIFF']
+    qcam_sources += files([
+        '../cam/dng_writer.cpp',
+    ])
+endif
+
+if cxx.has_header_symbol('QOpenGLWidget', 'QOpenGLWidget',
+                         dependencies : qt5_dep, args : '-fPIC')
+    qcam_sources += files([
+        'viewfinder_gl.cpp',
+    ])
+    qcam_moc_headers += files([
+        'viewfinder_gl.h',
+    ])
+    qcam_resources += files([
+        'assets/shader/shaders.qrc'
+    ])
+endif
+
+# gcc 9 introduced a deprecated-copy warning that is triggered by Qt until
+# Qt 5.13. clang 10 introduced the same warning, but detects more issues
+# that are not fixed in Qt yet. Disable the warning manually in both cases.
+if ((cc.get_id() == 'gcc' and cc.version().version_compare('>=9.0') and
+     qt5_dep.version().version_compare('<5.13')) or
+    (cc.get_id() == 'clang' and cc.version().version_compare('>=10.0')))
+    qt5_cpp_args += ['-Wno-deprecated-copy']
+endif
+
+resources = qt5.preprocess(moc_headers: qcam_moc_headers,
+                           qresources : qcam_resources,
+                           dependencies: qt5_dep)
+
+qcam  = executable('qcam', qcam_sources, resources,
+                   install : true,
+                   dependencies : [
+                       libatomic,
+                       libcamera_public,
+                       qt5_dep,
+                       tiff_dep,
+                   ],
+                   cpp_args : qt5_cpp_args)
diff --git a/src/apps/qcam/message_handler.cpp b/src/apps/qcam/message_handler.cpp
new file mode 100644
index 00000000..261623e1
--- /dev/null
+++ b/src/apps/qcam/message_handler.cpp
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * message_handler.cpp - qcam - Log message handling
+ */
+
+#include "message_handler.h"
+
+QtMessageHandler MessageHandler::handler_ = nullptr;
+bool MessageHandler::verbose_ = false;
+
+MessageHandler::MessageHandler(bool verbose)
+{
+	verbose_ = verbose;
+	handler_ = qInstallMessageHandler(&MessageHandler::handleMessage);
+}
+
+void MessageHandler::handleMessage(QtMsgType type,
+				   const QMessageLogContext &context,
+				   const QString &msg)
+{
+	if (type == QtDebugMsg && !verbose_)
+		return;
+
+	handler_(type, context, msg);
+}
diff --git a/src/apps/qcam/message_handler.h b/src/apps/qcam/message_handler.h
new file mode 100644
index 00000000..56294d37
--- /dev/null
+++ b/src/apps/qcam/message_handler.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * message_handler.cpp - qcam - Log message handling
+ */
+
+#pragma once
+
+#include <QtGlobal>
+
+class MessageHandler
+{
+public:
+	MessageHandler(bool verbose);
+
+private:
+	static void handleMessage(QtMsgType type,
+				  const QMessageLogContext &context,
+				  const QString &msg);
+
+	static QtMessageHandler handler_;
+	static bool verbose_;
+};
diff --git a/src/apps/qcam/viewfinder.h b/src/apps/qcam/viewfinder.h
new file mode 100644
index 00000000..a57446e8
--- /dev/null
+++ b/src/apps/qcam/viewfinder.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * viewfinder.h - qcam - Viewfinder base class
+ */
+
+#pragma once
+
+#include <QImage>
+#include <QList>
+#include <QSize>
+
+#include <libcamera/color_space.h>
+#include <libcamera/formats.h>
+#include <libcamera/framebuffer.h>
+
+class Image;
+
+class ViewFinder
+{
+public:
+	virtual ~ViewFinder() = default;
+
+	virtual const QList<libcamera::PixelFormat> &nativeFormats() const = 0;
+
+	virtual int setFormat(const libcamera::PixelFormat &format, const QSize &size,
+			      const libcamera::ColorSpace &colorSpace,
+			      unsigned int stride) = 0;
+	virtual void render(libcamera::FrameBuffer *buffer, Image *image) = 0;
+	virtual void stop() = 0;
+
+	virtual QImage getCurrentImage() = 0;
+};
diff --git a/src/apps/qcam/viewfinder_gl.cpp b/src/apps/qcam/viewfinder_gl.cpp
new file mode 100644
index 00000000..38ddad58
--- /dev/null
+++ b/src/apps/qcam/viewfinder_gl.cpp
@@ -0,0 +1,835 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Linaro
+ *
+ * viewfinderGL.cpp - OpenGL Viewfinder for rendering by OpenGL shader
+ */
+
+#include "viewfinder_gl.h"
+
+#include <array>
+
+#include <QByteArray>
+#include <QFile>
+#include <QImage>
+#include <QStringList>
+
+#include <libcamera/formats.h>
+
+#include "../cam/image.h"
+
+static const QList<libcamera::PixelFormat> supportedFormats{
+	/* YUV - packed (single plane) */
+	libcamera::formats::UYVY,
+	libcamera::formats::VYUY,
+	libcamera::formats::YUYV,
+	libcamera::formats::YVYU,
+	/* YUV - semi planar (two planes) */
+	libcamera::formats::NV12,
+	libcamera::formats::NV21,
+	libcamera::formats::NV16,
+	libcamera::formats::NV61,
+	libcamera::formats::NV24,
+	libcamera::formats::NV42,
+	/* YUV - fully planar (three planes) */
+	libcamera::formats::YUV420,
+	libcamera::formats::YVU420,
+	/* RGB */
+	libcamera::formats::ABGR8888,
+	libcamera::formats::ARGB8888,
+	libcamera::formats::BGRA8888,
+	libcamera::formats::RGBA8888,
+	libcamera::formats::BGR888,
+	libcamera::formats::RGB888,
+	/* Raw Bayer 8-bit */
+	libcamera::formats::SBGGR8,
+	libcamera::formats::SGBRG8,
+	libcamera::formats::SGRBG8,
+	libcamera::formats::SRGGB8,
+	/* Raw Bayer 10-bit packed */
+	libcamera::formats::SBGGR10_CSI2P,
+	libcamera::formats::SGBRG10_CSI2P,
+	libcamera::formats::SGRBG10_CSI2P,
+	libcamera::formats::SRGGB10_CSI2P,
+	/* Raw Bayer 12-bit packed */
+	libcamera::formats::SBGGR12_CSI2P,
+	libcamera::formats::SGBRG12_CSI2P,
+	libcamera::formats::SGRBG12_CSI2P,
+	libcamera::formats::SRGGB12_CSI2P,
+};
+
+ViewFinderGL::ViewFinderGL(QWidget *parent)
+	: QOpenGLWidget(parent), buffer_(nullptr),
+	  colorSpace_(libcamera::ColorSpace::Raw), image_(nullptr),
+	  vertexBuffer_(QOpenGLBuffer::VertexBuffer)
+{
+}
+
+ViewFinderGL::~ViewFinderGL()
+{
+	removeShader();
+}
+
+const QList<libcamera::PixelFormat> &ViewFinderGL::nativeFormats() const
+{
+	return supportedFormats;
+}
+
+int ViewFinderGL::setFormat(const libcamera::PixelFormat &format, const QSize &size,
+			    const libcamera::ColorSpace &colorSpace,
+			    unsigned int stride)
+{
+	if (format != format_ || colorSpace != colorSpace_) {
+		/*
+		 * If the fragment already exists, remove it and create a new
+		 * one for the new format.
+		 */
+		if (shaderProgram_.isLinked()) {
+			shaderProgram_.release();
+			shaderProgram_.removeShader(fragmentShader_.get());
+			fragmentShader_.reset();
+		}
+
+		if (!selectFormat(format))
+			return -1;
+
+		selectColorSpace(colorSpace);
+
+		format_ = format;
+		colorSpace_ = colorSpace;
+	}
+
+	size_ = size;
+	stride_ = stride;
+
+	updateGeometry();
+	return 0;
+}
+
+void ViewFinderGL::stop()
+{
+	if (buffer_) {
+		renderComplete(buffer_);
+		buffer_ = nullptr;
+		image_ = nullptr;
+	}
+}
+
+QImage ViewFinderGL::getCurrentImage()
+{
+	QMutexLocker locker(&mutex_);
+
+	return grabFramebuffer();
+}
+
+void ViewFinderGL::render(libcamera::FrameBuffer *buffer, Image *image)
+{
+	if (buffer_)
+		renderComplete(buffer_);
+
+	image_ = image;
+	update();
+	buffer_ = buffer;
+}
+
+bool ViewFinderGL::selectFormat(const libcamera::PixelFormat &format)
+{
+	bool ret = true;
+
+	/* Set min/mag filters to GL_LINEAR by default. */
+	textureMinMagFilters_ = GL_LINEAR;
+
+	/* Use identity.vert as the default vertex shader. */
+	vertexShaderFile_ = ":identity.vert";
+
+	fragmentShaderDefines_.clear();
+
+	switch (format) {
+	case libcamera::formats::NV12:
+		horzSubSample_ = 2;
+		vertSubSample_ = 2;
+		fragmentShaderDefines_.append("#define YUV_PATTERN_UV");
+		fragmentShaderFile_ = ":YUV_2_planes.frag";
+		break;
+	case libcamera::formats::NV21:
+		horzSubSample_ = 2;
+		vertSubSample_ = 2;
+		fragmentShaderDefines_.append("#define YUV_PATTERN_VU");
+		fragmentShaderFile_ = ":YUV_2_planes.frag";
+		break;
+	case libcamera::formats::NV16:
+		horzSubSample_ = 2;
+		vertSubSample_ = 1;
+		fragmentShaderDefines_.append("#define YUV_PATTERN_UV");
+		fragmentShaderFile_ = ":YUV_2_planes.frag";
+		break;
+	case libcamera::formats::NV61:
+		horzSubSample_ = 2;
+		vertSubSample_ = 1;
+		fragmentShaderDefines_.append("#define YUV_PATTERN_VU");
+		fragmentShaderFile_ = ":YUV_2_planes.frag";
+		break;
+	case libcamera::formats::NV24:
+		horzSubSample_ = 1;
+		vertSubSample_ = 1;
+		fragmentShaderDefines_.append("#define YUV_PATTERN_UV");
+		fragmentShaderFile_ = ":YUV_2_planes.frag";
+		break;
+	case libcamera::formats::NV42:
+		horzSubSample_ = 1;
+		vertSubSample_ = 1;
+		fragmentShaderDefines_.append("#define YUV_PATTERN_VU");
+		fragmentShaderFile_ = ":YUV_2_planes.frag";
+		break;
+	case libcamera::formats::YUV420:
+		horzSubSample_ = 2;
+		vertSubSample_ = 2;
+		fragmentShaderFile_ = ":YUV_3_planes.frag";
+		break;
+	case libcamera::formats::YVU420:
+		horzSubSample_ = 2;
+		vertSubSample_ = 2;
+		fragmentShaderFile_ = ":YUV_3_planes.frag";
+		break;
+	case libcamera::formats::UYVY:
+		fragmentShaderDefines_.append("#define YUV_PATTERN_UYVY");
+		fragmentShaderFile_ = ":YUV_packed.frag";
+		break;
+	case libcamera::formats::VYUY:
+		fragmentShaderDefines_.append("#define YUV_PATTERN_VYUY");
+		fragmentShaderFile_ = ":YUV_packed.frag";
+		break;
+	case libcamera::formats::YUYV:
+		fragmentShaderDefines_.append("#define YUV_PATTERN_YUYV");
+		fragmentShaderFile_ = ":YUV_packed.frag";
+		break;
+	case libcamera::formats::YVYU:
+		fragmentShaderDefines_.append("#define YUV_PATTERN_YVYU");
+		fragmentShaderFile_ = ":YUV_packed.frag";
+		break;
+	case libcamera::formats::ABGR8888:
+		fragmentShaderDefines_.append("#define RGB_PATTERN rgb");
+		fragmentShaderFile_ = ":RGB.frag";
+		break;
+	case libcamera::formats::ARGB8888:
+		fragmentShaderDefines_.append("#define RGB_PATTERN bgr");
+		fragmentShaderFile_ = ":RGB.frag";
+		break;
+	case libcamera::formats::BGRA8888:
+		fragmentShaderDefines_.append("#define RGB_PATTERN gba");
+		fragmentShaderFile_ = ":RGB.frag";
+		break;
+	case libcamera::formats::RGBA8888:
+		fragmentShaderDefines_.append("#define RGB_PATTERN abg");
+		fragmentShaderFile_ = ":RGB.frag";
+		break;
+	case libcamera::formats::BGR888:
+		fragmentShaderDefines_.append("#define RGB_PATTERN rgb");
+		fragmentShaderFile_ = ":RGB.frag";
+		break;
+	case libcamera::formats::RGB888:
+		fragmentShaderDefines_.append("#define RGB_PATTERN bgr");
+		fragmentShaderFile_ = ":RGB.frag";
+		break;
+	case libcamera::formats::SBGGR8:
+		firstRed_.setX(1.0);
+		firstRed_.setY(1.0);
+		vertexShaderFile_ = ":bayer_8.vert";
+		fragmentShaderFile_ = ":bayer_8.frag";
+		textureMinMagFilters_ = GL_NEAREST;
+		break;
+	case libcamera::formats::SGBRG8:
+		firstRed_.setX(0.0);
+		firstRed_.setY(1.0);
+		vertexShaderFile_ = ":bayer_8.vert";
+		fragmentShaderFile_ = ":bayer_8.frag";
+		textureMinMagFilters_ = GL_NEAREST;
+		break;
+	case libcamera::formats::SGRBG8:
+		firstRed_.setX(1.0);
+		firstRed_.setY(0.0);
+		vertexShaderFile_ = ":bayer_8.vert";
+		fragmentShaderFile_ = ":bayer_8.frag";
+		textureMinMagFilters_ = GL_NEAREST;
+		break;
+	case libcamera::formats::SRGGB8:
+		firstRed_.setX(0.0);
+		firstRed_.setY(0.0);
+		vertexShaderFile_ = ":bayer_8.vert";
+		fragmentShaderFile_ = ":bayer_8.frag";
+		textureMinMagFilters_ = GL_NEAREST;
+		break;
+	case libcamera::formats::SBGGR10_CSI2P:
+		firstRed_.setX(1.0);
+		firstRed_.setY(1.0);
+		fragmentShaderDefines_.append("#define RAW10P");
+		fragmentShaderFile_ = ":bayer_1x_packed.frag";
+		textureMinMagFilters_ = GL_NEAREST;
+		break;
+	case libcamera::formats::SGBRG10_CSI2P:
+		firstRed_.setX(0.0);
+		firstRed_.setY(1.0);
+		fragmentShaderDefines_.append("#define RAW10P");
+		fragmentShaderFile_ = ":bayer_1x_packed.frag";
+		textureMinMagFilters_ = GL_NEAREST;
+		break;
+	case libcamera::formats::SGRBG10_CSI2P:
+		firstRed_.setX(1.0);
+		firstRed_.setY(0.0);
+		fragmentShaderDefines_.append("#define RAW10P");
+		fragmentShaderFile_ = ":bayer_1x_packed.frag";
+		textureMinMagFilters_ = GL_NEAREST;
+		break;
+	case libcamera::formats::SRGGB10_CSI2P:
+		firstRed_.setX(0.0);
+		firstRed_.setY(0.0);
+		fragmentShaderDefines_.append("#define RAW10P");
+		fragmentShaderFile_ = ":bayer_1x_packed.frag";
+		textureMinMagFilters_ = GL_NEAREST;
+		break;
+	case libcamera::formats::SBGGR12_CSI2P:
+		firstRed_.setX(1.0);
+		firstRed_.setY(1.0);
+		fragmentShaderDefines_.append("#define RAW12P");
+		fragmentShaderFile_ = ":bayer_1x_packed.frag";
+		textureMinMagFilters_ = GL_NEAREST;
+		break;
+	case libcamera::formats::SGBRG12_CSI2P:
+		firstRed_.setX(0.0);
+		firstRed_.setY(1.0);
+		fragmentShaderDefines_.append("#define RAW12P");
+		fragmentShaderFile_ = ":bayer_1x_packed.frag";
+		textureMinMagFilters_ = GL_NEAREST;
+		break;
+	case libcamera::formats::SGRBG12_CSI2P:
+		firstRed_.setX(1.0);
+		firstRed_.setY(0.0);
+		fragmentShaderDefines_.append("#define RAW12P");
+		fragmentShaderFile_ = ":bayer_1x_packed.frag";
+		textureMinMagFilters_ = GL_NEAREST;
+		break;
+	case libcamera::formats::SRGGB12_CSI2P:
+		firstRed_.setX(0.0);
+		firstRed_.setY(0.0);
+		fragmentShaderDefines_.append("#define RAW12P");
+		fragmentShaderFile_ = ":bayer_1x_packed.frag";
+		textureMinMagFilters_ = GL_NEAREST;
+		break;
+	default:
+		ret = false;
+		qWarning() << "[ViewFinderGL]:"
+			   << "format not supported.";
+		break;
+	};
+
+	return ret;
+}
+
+void ViewFinderGL::selectColorSpace(const libcamera::ColorSpace &colorSpace)
+{
+	std::array<double, 9> yuv2rgb;
+
+	/* OpenGL stores arrays in column-major order. */
+	switch (colorSpace.ycbcrEncoding) {
+	case libcamera::ColorSpace::YcbcrEncoding::None:
+	default:
+		yuv2rgb = {
+			1.0000,  0.0000,  0.0000,
+			0.0000,  1.0000,  0.0000,
+			0.0000,  0.0000,  1.0000,
+		};
+		break;
+
+	case libcamera::ColorSpace::YcbcrEncoding::Rec601:
+		yuv2rgb = {
+			1.0000,  1.0000,  1.0000,
+			0.0000, -0.3441,  1.7720,
+			1.4020, -0.7141,  0.0000,
+		};
+		break;
+
+	case libcamera::ColorSpace::YcbcrEncoding::Rec709:
+		yuv2rgb = {
+			1.0000,  1.0000,  1.0000,
+			0.0000, -0.1873,  1.8856,
+			1.5748, -0.4681,  0.0000,
+		};
+		break;
+
+	case libcamera::ColorSpace::YcbcrEncoding::Rec2020:
+		yuv2rgb = {
+			1.0000,  1.0000,  1.0000,
+			0.0000, -0.1646,  1.8814,
+			1.4746, -0.5714,  0.0000,
+		};
+		break;
+	}
+
+	double offset;
+
+	switch (colorSpace.range) {
+	case libcamera::ColorSpace::Range::Full:
+	default:
+		offset = 0.0;
+		break;
+
+	case libcamera::ColorSpace::Range::Limited:
+		offset = 16.0;
+
+		for (unsigned int i = 0; i < 3; ++i)
+			yuv2rgb[i] *= 255.0 / 219.0;
+		for (unsigned int i = 4; i < 9; ++i)
+			yuv2rgb[i] *= 255.0 / 224.0;
+		break;
+	}
+
+	QStringList matrix;
+
+	for (double coeff : yuv2rgb)
+		matrix.append(QString::number(coeff, 'f'));
+
+	fragmentShaderDefines_.append("#define YUV2RGB_MATRIX " + matrix.join(", "));
+	fragmentShaderDefines_.append(QString("#define YUV2RGB_Y_OFFSET %1")
+		.arg(offset, 0, 'f', 1));
+}
+
+bool ViewFinderGL::createVertexShader()
+{
+	/* Create Vertex Shader */
+	vertexShader_ = std::make_unique<QOpenGLShader>(QOpenGLShader::Vertex, this);
+
+	/* Compile the vertex shader */
+	if (!vertexShader_->compileSourceFile(vertexShaderFile_)) {
+		qWarning() << "[ViewFinderGL]:" << vertexShader_->log();
+		return false;
+	}
+
+	shaderProgram_.addShader(vertexShader_.get());
+	return true;
+}
+
+bool ViewFinderGL::createFragmentShader()
+{
+	int attributeVertex;
+	int attributeTexture;
+
+	/*
+	 * Create the fragment shader, compile it, and add it to the shader
+	 * program. The #define macros stored in fragmentShaderDefines_, if
+	 * any, are prepended to the source code.
+	 */
+	fragmentShader_ = std::make_unique<QOpenGLShader>(QOpenGLShader::Fragment, this);
+
+	QFile file(fragmentShaderFile_);
+	if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+		qWarning() << "Shader" << fragmentShaderFile_ << "not found";
+		return false;
+	}
+
+	QString defines = fragmentShaderDefines_.join('\n') + "\n";
+	QByteArray src = file.readAll();
+	src.prepend(defines.toUtf8());
+
+	if (!fragmentShader_->compileSourceCode(src)) {
+		qWarning() << "[ViewFinderGL]:" << fragmentShader_->log();
+		return false;
+	}
+
+	shaderProgram_.addShader(fragmentShader_.get());
+
+	/* Link shader pipeline */
+	if (!shaderProgram_.link()) {
+		qWarning() << "[ViewFinderGL]:" << shaderProgram_.log();
+		close();
+	}
+
+	/* Bind shader pipeline for use */
+	if (!shaderProgram_.bind()) {
+		qWarning() << "[ViewFinderGL]:" << shaderProgram_.log();
+		close();
+	}
+
+	attributeVertex = shaderProgram_.attributeLocation("vertexIn");
+	attributeTexture = shaderProgram_.attributeLocation("textureIn");
+
+	shaderProgram_.enableAttributeArray(attributeVertex);
+	shaderProgram_.setAttributeBuffer(attributeVertex,
+					  GL_FLOAT,
+					  0,
+					  2,
+					  2 * sizeof(GLfloat));
+
+	shaderProgram_.enableAttributeArray(attributeTexture);
+	shaderProgram_.setAttributeBuffer(attributeTexture,
+					  GL_FLOAT,
+					  8 * sizeof(GLfloat),
+					  2,
+					  2 * sizeof(GLfloat));
+
+	textureUniformY_ = shaderProgram_.uniformLocation("tex_y");
+	textureUniformU_ = shaderProgram_.uniformLocation("tex_u");
+	textureUniformV_ = shaderProgram_.uniformLocation("tex_v");
+	textureUniformStep_ = shaderProgram_.uniformLocation("tex_step");
+	textureUniformSize_ = shaderProgram_.uniformLocation("tex_size");
+	textureUniformStrideFactor_ = shaderProgram_.uniformLocation("stride_factor");
+	textureUniformBayerFirstRed_ = shaderProgram_.uniformLocation("tex_bayer_first_red");
+
+	/* Create the textures. */
+	for (std::unique_ptr<QOpenGLTexture> &texture : textures_) {
+		if (texture)
+			continue;
+
+		texture = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target2D);
+		texture->create();
+	}
+
+	return true;
+}
+
+void ViewFinderGL::configureTexture(QOpenGLTexture &texture)
+{
+	glBindTexture(GL_TEXTURE_2D, texture.textureId());
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
+			textureMinMagFilters_);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
+			textureMinMagFilters_);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+}
+
+void ViewFinderGL::removeShader()
+{
+	if (shaderProgram_.isLinked()) {
+		shaderProgram_.release();
+		shaderProgram_.removeAllShaders();
+	}
+}
+
+void ViewFinderGL::initializeGL()
+{
+	initializeOpenGLFunctions();
+	glEnable(GL_TEXTURE_2D);
+	glDisable(GL_DEPTH_TEST);
+
+	static const GLfloat coordinates[2][4][2]{
+		{
+			/* Vertex coordinates */
+			{ -1.0f, -1.0f },
+			{ -1.0f, +1.0f },
+			{ +1.0f, +1.0f },
+			{ +1.0f, -1.0f },
+		},
+		{
+			/* Texture coordinates */
+			{ 0.0f, 1.0f },
+			{ 0.0f, 0.0f },
+			{ 1.0f, 0.0f },
+			{ 1.0f, 1.0f },
+		},
+	};
+
+	vertexBuffer_.create();
+	vertexBuffer_.bind();
+	vertexBuffer_.allocate(coordinates, sizeof(coordinates));
+
+	/* Create Vertex Shader */
+	if (!createVertexShader())
+		qWarning() << "[ViewFinderGL]: create vertex shader failed.";
+
+	glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
+}
+
+void ViewFinderGL::doRender()
+{
+	/* Stride of the first plane, in pixels. */
+	unsigned int stridePixels;
+
+	switch (format_) {
+	case libcamera::formats::NV12:
+	case libcamera::formats::NV21:
+	case libcamera::formats::NV16:
+	case libcamera::formats::NV61:
+	case libcamera::formats::NV24:
+	case libcamera::formats::NV42:
+		/* Activate texture Y */
+		glActiveTexture(GL_TEXTURE0);
+		configureTexture(*textures_[0]);
+		glTexImage2D(GL_TEXTURE_2D,
+			     0,
+			     GL_LUMINANCE,
+			     stride_,
+			     size_.height(),
+			     0,
+			     GL_LUMINANCE,
+			     GL_UNSIGNED_BYTE,
+			     image_->data(0).data());
+		shaderProgram_.setUniformValue(textureUniformY_, 0);
+
+		/* Activate texture UV/VU */
+		glActiveTexture(GL_TEXTURE1);
+		configureTexture(*textures_[1]);
+		glTexImage2D(GL_TEXTURE_2D,
+			     0,
+			     GL_LUMINANCE_ALPHA,
+			     stride_ / horzSubSample_,
+			     size_.height() / vertSubSample_,
+			     0,
+			     GL_LUMINANCE_ALPHA,
+			     GL_UNSIGNED_BYTE,
+			     image_->data(1).data());
+		shaderProgram_.setUniformValue(textureUniformU_, 1);
+
+		stridePixels = stride_;
+		break;
+
+	case libcamera::formats::YUV420:
+		/* Activate texture Y */
+		glActiveTexture(GL_TEXTURE0);
+		configureTexture(*textures_[0]);
+		glTexImage2D(GL_TEXTURE_2D,
+			     0,
+			     GL_LUMINANCE,
+			     stride_,
+			     size_.height(),
+			     0,
+			     GL_LUMINANCE,
+			     GL_UNSIGNED_BYTE,
+			     image_->data(0).data());
+		shaderProgram_.setUniformValue(textureUniformY_, 0);
+
+		/* Activate texture U */
+		glActiveTexture(GL_TEXTURE1);
+		configureTexture(*textures_[1]);
+		glTexImage2D(GL_TEXTURE_2D,
+			     0,
+			     GL_LUMINANCE,
+			     stride_ / horzSubSample_,
+			     size_.height() / vertSubSample_,
+			     0,
+			     GL_LUMINANCE,
+			     GL_UNSIGNED_BYTE,
+			     image_->data(1).data());
+		shaderProgram_.setUniformValue(textureUniformU_, 1);
+
+		/* Activate texture V */
+		glActiveTexture(GL_TEXTURE2);
+		configureTexture(*textures_[2]);
+		glTexImage2D(GL_TEXTURE_2D,
+			     0,
+			     GL_LUMINANCE,
+			     stride_ / horzSubSample_,
+			     size_.height() / vertSubSample_,
+			     0,
+			     GL_LUMINANCE,
+			     GL_UNSIGNED_BYTE,
+			     image_->data(2).data());
+		shaderProgram_.setUniformValue(textureUniformV_, 2);
+
+		stridePixels = stride_;
+		break;
+
+	case libcamera::formats::YVU420:
+		/* Activate texture Y */
+		glActiveTexture(GL_TEXTURE0);
+		configureTexture(*textures_[0]);
+		glTexImage2D(GL_TEXTURE_2D,
+			     0,
+			     GL_LUMINANCE,
+			     stride_,
+			     size_.height(),
+			     0,
+			     GL_LUMINANCE,
+			     GL_UNSIGNED_BYTE,
+			     image_->data(0).data());
+		shaderProgram_.setUniformValue(textureUniformY_, 0);
+
+		/* Activate texture V */
+		glActiveTexture(GL_TEXTURE2);
+		configureTexture(*textures_[2]);
+		glTexImage2D(GL_TEXTURE_2D,
+			     0,
+			     GL_LUMINANCE,
+			     stride_ / horzSubSample_,
+			     size_.height() / vertSubSample_,
+			     0,
+			     GL_LUMINANCE,
+			     GL_UNSIGNED_BYTE,
+			     image_->data(1).data());
+		shaderProgram_.setUniformValue(textureUniformV_, 2);
+
+		/* Activate texture U */
+		glActiveTexture(GL_TEXTURE1);
+		configureTexture(*textures_[1]);
+		glTexImage2D(GL_TEXTURE_2D,
+			     0,
+			     GL_LUMINANCE,
+			     stride_ / horzSubSample_,
+			     size_.height() / vertSubSample_,
+			     0,
+			     GL_LUMINANCE,
+			     GL_UNSIGNED_BYTE,
+			     image_->data(2).data());
+		shaderProgram_.setUniformValue(textureUniformU_, 1);
+
+		stridePixels = stride_;
+		break;
+
+	case libcamera::formats::UYVY:
+	case libcamera::formats::VYUY:
+	case libcamera::formats::YUYV:
+	case libcamera::formats::YVYU:
+		/*
+		 * Packed YUV formats are stored in a RGBA texture to match the
+		 * OpenGL texel size with the 4 bytes repeating pattern in YUV.
+		 * The texture width is thus half of the image_ with.
+		 */
+		glActiveTexture(GL_TEXTURE0);
+		configureTexture(*textures_[0]);
+		glTexImage2D(GL_TEXTURE_2D,
+			     0,
+			     GL_RGBA,
+			     stride_ / 4,
+			     size_.height(),
+			     0,
+			     GL_RGBA,
+			     GL_UNSIGNED_BYTE,
+			     image_->data(0).data());
+		shaderProgram_.setUniformValue(textureUniformY_, 0);
+
+		/*
+		 * The shader needs the step between two texture pixels in the
+		 * horizontal direction, expressed in texture coordinate units
+		 * ([0, 1]). There are exactly width - 1 steps between the
+		 * leftmost and rightmost texels.
+		 */
+		shaderProgram_.setUniformValue(textureUniformStep_,
+					       1.0f / (size_.width() / 2 - 1),
+					       1.0f /* not used */);
+
+		stridePixels = stride_ / 2;
+		break;
+
+	case libcamera::formats::ABGR8888:
+	case libcamera::formats::ARGB8888:
+	case libcamera::formats::BGRA8888:
+	case libcamera::formats::RGBA8888:
+		glActiveTexture(GL_TEXTURE0);
+		configureTexture(*textures_[0]);
+		glTexImage2D(GL_TEXTURE_2D,
+			     0,
+			     GL_RGBA,
+			     stride_ / 4,
+			     size_.height(),
+			     0,
+			     GL_RGBA,
+			     GL_UNSIGNED_BYTE,
+			     image_->data(0).data());
+		shaderProgram_.setUniformValue(textureUniformY_, 0);
+
+		stridePixels = stride_ / 4;
+		break;
+
+	case libcamera::formats::BGR888:
+	case libcamera::formats::RGB888:
+		glActiveTexture(GL_TEXTURE0);
+		configureTexture(*textures_[0]);
+		glTexImage2D(GL_TEXTURE_2D,
+			     0,
+			     GL_RGB,
+			     stride_ / 3,
+			     size_.height(),
+			     0,
+			     GL_RGB,
+			     GL_UNSIGNED_BYTE,
+			     image_->data(0).data());
+		shaderProgram_.setUniformValue(textureUniformY_, 0);
+
+		stridePixels = stride_ / 3;
+		break;
+
+	case libcamera::formats::SBGGR8:
+	case libcamera::formats::SGBRG8:
+	case libcamera::formats::SGRBG8:
+	case libcamera::formats::SRGGB8:
+	case libcamera::formats::SBGGR10_CSI2P:
+	case libcamera::formats::SGBRG10_CSI2P:
+	case libcamera::formats::SGRBG10_CSI2P:
+	case libcamera::formats::SRGGB10_CSI2P:
+	case libcamera::formats::SBGGR12_CSI2P:
+	case libcamera::formats::SGBRG12_CSI2P:
+	case libcamera::formats::SGRBG12_CSI2P:
+	case libcamera::formats::SRGGB12_CSI2P:
+		/*
+		 * Raw Bayer 8-bit, and packed raw Bayer 10-bit/12-bit formats
+		 * are stored in a GL_LUMINANCE texture. The texture width is
+		 * equal to the stride.
+		 */
+		glActiveTexture(GL_TEXTURE0);
+		configureTexture(*textures_[0]);
+		glTexImage2D(GL_TEXTURE_2D,
+			     0,
+			     GL_LUMINANCE,
+			     stride_,
+			     size_.height(),
+			     0,
+			     GL_LUMINANCE,
+			     GL_UNSIGNED_BYTE,
+			     image_->data(0).data());
+		shaderProgram_.setUniformValue(textureUniformY_, 0);
+		shaderProgram_.setUniformValue(textureUniformBayerFirstRed_,
+					       firstRed_);
+		shaderProgram_.setUniformValue(textureUniformSize_,
+					       size_.width(), /* in pixels */
+					       size_.height());
+		shaderProgram_.setUniformValue(textureUniformStep_,
+					       1.0f / (stride_ - 1),
+					       1.0f / (size_.height() - 1));
+
+		/*
+		 * The stride is already taken into account in the shaders, set
+		 * the generic stride factor to 1.0.
+		 */
+		stridePixels = size_.width();
+		break;
+
+	default:
+		stridePixels = size_.width();
+		break;
+	};
+
+	/*
+	 * Compute the stride factor for the vertex shader, to map the
+	 * horizontal texture coordinate range [0.0, 1.0] to the active portion
+	 * of the image.
+	 */
+	shaderProgram_.setUniformValue(textureUniformStrideFactor_,
+				       static_cast<float>(size_.width() - 1) /
+				       (stridePixels - 1));
+}
+
+void ViewFinderGL::paintGL()
+{
+	if (!fragmentShader_)
+		if (!createFragmentShader()) {
+			qWarning() << "[ViewFinderGL]:"
+				   << "create fragment shader failed.";
+		}
+
+	if (image_) {
+		glClearColor(0.0, 0.0, 0.0, 1.0);
+		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+		doRender();
+		glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+	}
+}
+
+void ViewFinderGL::resizeGL(int w, int h)
+{
+	glViewport(0, 0, w, h);
+}
+
+QSize ViewFinderGL::sizeHint() const
+{
+	return size_.isValid() ? size_ : QSize(640, 480);
+}
diff --git a/src/apps/qcam/viewfinder_gl.h b/src/apps/qcam/viewfinder_gl.h
new file mode 100644
index 00000000..68c2912d
--- /dev/null
+++ b/src/apps/qcam/viewfinder_gl.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Linaro
+ *
+ * viewfinder_GL.h - OpenGL Viewfinder for rendering by OpenGL shader
+ *
+ */
+
+#pragma once
+
+#include <array>
+#include <memory>
+
+#include <QImage>
+#include <QMutex>
+#include <QOpenGLBuffer>
+#include <QOpenGLFunctions>
+#include <QOpenGLShader>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLTexture>
+#include <QOpenGLWidget>
+#include <QSize>
+
+#include <libcamera/formats.h>
+#include <libcamera/framebuffer.h>
+
+#include "viewfinder.h"
+
+class ViewFinderGL : public QOpenGLWidget,
+		     public ViewFinder,
+		     protected QOpenGLFunctions
+{
+	Q_OBJECT
+
+public:
+	ViewFinderGL(QWidget *parent = nullptr);
+	~ViewFinderGL();
+
+	const QList<libcamera::PixelFormat> &nativeFormats() const override;
+
+	int setFormat(const libcamera::PixelFormat &format, const QSize &size,
+		      const libcamera::ColorSpace &colorSpace,
+		      unsigned int stride) override;
+	void render(libcamera::FrameBuffer *buffer, Image *image) override;
+	void stop() override;
+
+	QImage getCurrentImage() override;
+
+Q_SIGNALS:
+	void renderComplete(libcamera::FrameBuffer *buffer);
+
+protected:
+	void initializeGL() override;
+	void paintGL() override;
+	void resizeGL(int w, int h) override;
+	QSize sizeHint() const override;
+
+private:
+	bool selectFormat(const libcamera::PixelFormat &format);
+	void selectColorSpace(const libcamera::ColorSpace &colorSpace);
+
+	void configureTexture(QOpenGLTexture &texture);
+	bool createFragmentShader();
+	bool createVertexShader();
+	void removeShader();
+	void doRender();
+
+	/* Captured image size, format and buffer */
+	libcamera::FrameBuffer *buffer_;
+	libcamera::PixelFormat format_;
+	libcamera::ColorSpace colorSpace_;
+	QSize size_;
+	unsigned int stride_;
+	Image *image_;
+
+	/* Shaders */
+	QOpenGLShaderProgram shaderProgram_;
+	std::unique_ptr<QOpenGLShader> vertexShader_;
+	std::unique_ptr<QOpenGLShader> fragmentShader_;
+	QString vertexShaderFile_;
+	QString fragmentShaderFile_;
+	QStringList fragmentShaderDefines_;
+
+	/* Vertex buffer */
+	QOpenGLBuffer vertexBuffer_;
+
+	/* Textures */
+	std::array<std::unique_ptr<QOpenGLTexture>, 3> textures_;
+
+	/* Common texture parameters */
+	GLuint textureMinMagFilters_;
+
+	/* YUV texture parameters */
+	GLuint textureUniformU_;
+	GLuint textureUniformV_;
+	GLuint textureUniformY_;
+	GLuint textureUniformStep_;
+	unsigned int horzSubSample_;
+	unsigned int vertSubSample_;
+
+	/* Raw Bayer texture parameters */
+	GLuint textureUniformSize_;
+	GLuint textureUniformStrideFactor_;
+	GLuint textureUniformBayerFirstRed_;
+	QPointF firstRed_;
+
+	QMutex mutex_; /* Prevent concurrent access to image_ */
+};
diff --git a/src/apps/qcam/viewfinder_qt.cpp b/src/apps/qcam/viewfinder_qt.cpp
new file mode 100644
index 00000000..c20fd6bc
--- /dev/null
+++ b/src/apps/qcam/viewfinder_qt.cpp
@@ -0,0 +1,181 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * viewfinder_qt.cpp - qcam - QPainter-based ViewFinder
+ */
+
+#include "viewfinder_qt.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <utility>
+
+#include <libcamera/formats.h>
+
+#include <QImage>
+#include <QImageWriter>
+#include <QMap>
+#include <QMutexLocker>
+#include <QPainter>
+#include <QtDebug>
+
+#include "../cam/image.h"
+
+#include "format_converter.h"
+
+static const QMap<libcamera::PixelFormat, QImage::Format> nativeFormats
+{
+#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
+	{ libcamera::formats::ABGR8888, QImage::Format_RGBX8888 },
+	{ libcamera::formats::XBGR8888, QImage::Format_RGBX8888 },
+#endif
+	{ libcamera::formats::ARGB8888, QImage::Format_RGB32 },
+	{ libcamera::formats::XRGB8888, QImage::Format_RGB32 },
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+	{ libcamera::formats::RGB888, QImage::Format_BGR888 },
+#endif
+	{ libcamera::formats::BGR888, QImage::Format_RGB888 },
+};
+
+ViewFinderQt::ViewFinderQt(QWidget *parent)
+	: QWidget(parent), buffer_(nullptr)
+{
+	icon_ = QIcon(":camera-off.svg");
+}
+
+ViewFinderQt::~ViewFinderQt()
+{
+}
+
+const QList<libcamera::PixelFormat> &ViewFinderQt::nativeFormats() const
+{
+	static const QList<libcamera::PixelFormat> formats = ::nativeFormats.keys();
+	return formats;
+}
+
+int ViewFinderQt::setFormat(const libcamera::PixelFormat &format, const QSize &size,
+			    [[maybe_unused]] const libcamera::ColorSpace &colorSpace,
+			    unsigned int stride)
+{
+	image_ = QImage();
+
+	/*
+	 * If format conversion is needed, configure the converter and allocate
+	 * the destination image.
+	 */
+	if (!::nativeFormats.contains(format)) {
+		int ret = converter_.configure(format, size, stride);
+		if (ret < 0)
+			return ret;
+
+		image_ = QImage(size, QImage::Format_RGB32);
+
+		qInfo() << "Using software format conversion from" << format;
+	} else {
+		qInfo() << "Zero-copy enabled";
+	}
+
+	format_ = format;
+	size_ = size;
+
+	updateGeometry();
+	return 0;
+}
+
+void ViewFinderQt::render(libcamera::FrameBuffer *buffer, Image *image)
+{
+	size_t size = buffer->metadata().planes()[0].bytesused;
+
+	{
+		QMutexLocker locker(&mutex_);
+
+		if (::nativeFormats.contains(format_)) {
+			/*
+			 * If the frame format is identical to the display
+			 * format, create a QImage that references the frame
+			 * and store a reference to the frame buffer. The
+			 * previously stored frame buffer, if any, will be
+			 * released.
+			 *
+			 * \todo Get the stride from the buffer instead of
+			 * computing it naively
+			 */
+			assert(buffer->planes().size() == 1);
+			image_ = QImage(image->data(0).data(), size_.width(),
+					size_.height(), size / size_.height(),
+					::nativeFormats[format_]);
+			std::swap(buffer, buffer_);
+		} else {
+			/*
+			 * Otherwise, convert the format and release the frame
+			 * buffer immediately.
+			 */
+			converter_.convert(image, size, &image_);
+		}
+	}
+
+	update();
+
+	if (buffer)
+		renderComplete(buffer);
+}
+
+void ViewFinderQt::stop()
+{
+	image_ = QImage();
+
+	if (buffer_) {
+		renderComplete(buffer_);
+		buffer_ = nullptr;
+	}
+
+	update();
+}
+
+QImage ViewFinderQt::getCurrentImage()
+{
+	QMutexLocker locker(&mutex_);
+
+	return image_.copy();
+}
+
+void ViewFinderQt::paintEvent(QPaintEvent *)
+{
+	QPainter painter(this);
+
+	/* If we have an image, draw it. */
+	if (!image_.isNull()) {
+		painter.drawImage(rect(), image_, image_.rect());
+		return;
+	}
+
+	/*
+	 * Otherwise, draw the camera stopped icon. Render it to the pixmap if
+	 * the size has changed.
+	 */
+	constexpr int margin = 20;
+
+	if (vfSize_ != size() || pixmap_.isNull()) {
+		QSize vfSize = size() - QSize{ 2 * margin, 2 * margin };
+		QSize pixmapSize{ 1, 1 };
+		pixmapSize.scale(vfSize, Qt::KeepAspectRatio);
+		pixmap_ = icon_.pixmap(pixmapSize);
+
+		vfSize_ = size();
+	}
+
+	QPoint point{ margin, margin };
+	if (pixmap_.width() < width() - 2 * margin)
+		point.setX((width() - pixmap_.width()) / 2);
+	else
+		point.setY((height() - pixmap_.height()) / 2);
+
+	painter.setBackgroundMode(Qt::OpaqueMode);
+	painter.drawPixmap(point, pixmap_);
+}
+
+QSize ViewFinderQt::sizeHint() const
+{
+	return size_.isValid() ? size_ : QSize(640, 480);
+}
diff --git a/src/apps/qcam/viewfinder_qt.h b/src/apps/qcam/viewfinder_qt.h
new file mode 100644
index 00000000..eb3a9988
--- /dev/null
+++ b/src/apps/qcam/viewfinder_qt.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * viewfinder_qt.h - qcam - QPainter-based ViewFinder
+ */
+
+#pragma once
+
+#include <QIcon>
+#include <QImage>
+#include <QList>
+#include <QMutex>
+#include <QSize>
+#include <QWidget>
+
+#include <libcamera/formats.h>
+#include <libcamera/framebuffer.h>
+#include <libcamera/pixel_format.h>
+
+#include "format_converter.h"
+#include "viewfinder.h"
+
+class ViewFinderQt : public QWidget, public ViewFinder
+{
+	Q_OBJECT
+
+public:
+	ViewFinderQt(QWidget *parent);
+	~ViewFinderQt();
+
+	const QList<libcamera::PixelFormat> &nativeFormats() const override;
+
+	int setFormat(const libcamera::PixelFormat &format, const QSize &size,
+		      const libcamera::ColorSpace &colorSpace,
+		      unsigned int stride) override;
+	void render(libcamera::FrameBuffer *buffer, Image *image) override;
+	void stop() override;
+
+	QImage getCurrentImage() override;
+
+Q_SIGNALS:
+	void renderComplete(libcamera::FrameBuffer *buffer);
+
+protected:
+	void paintEvent(QPaintEvent *) override;
+	QSize sizeHint() const override;
+
+private:
+	FormatConverter converter_;
+
+	libcamera::PixelFormat format_;
+	QSize size_;
+
+	/* Camera stopped icon */
+	QSize vfSize_;
+	QIcon icon_;
+	QPixmap pixmap_;
+
+	/* Buffer and render image */
+	libcamera::FrameBuffer *buffer_;
+	QImage image_;
+	QMutex mutex_; /* Prevent concurrent access to image_ */
+};
-- 
cgit v1.2.1