Przeglądaj źródła

UI: Add source icons

Clayton Groeneveld 6 lat temu
rodzic
commit
82ffcdc827
66 zmienionych plików z 551 dodań i 9 usunięć
  1. 1 0
      UI/CMakeLists.txt
  2. 21 5
      UI/data/themes/Acri.qss
  3. 25 0
      UI/data/themes/Dark.qss
  4. 4 0
      UI/data/themes/Dark/sources/brush.svg
  5. 1 0
      UI/data/themes/Dark/sources/camera.svg
  6. 1 0
      UI/data/themes/Dark/sources/default.svg
  7. 1 0
      UI/data/themes/Dark/sources/gamepad.svg
  8. 1 0
      UI/data/themes/Dark/sources/globe.svg
  9. 3 0
      UI/data/themes/Dark/sources/group.svg
  10. 3 0
      UI/data/themes/Dark/sources/image.svg
  11. 3 0
      UI/data/themes/Dark/sources/media.svg
  12. 4 0
      UI/data/themes/Dark/sources/microphone.svg
  13. 4 0
      UI/data/themes/Dark/sources/scene.svg
  14. 5 0
      UI/data/themes/Dark/sources/slideshow.svg
  15. 3 0
      UI/data/themes/Dark/sources/text.svg
  16. 1 0
      UI/data/themes/Dark/sources/window.svg
  17. 25 0
      UI/data/themes/Rachni.qss
  18. 25 0
      UI/data/themes/System.qss
  19. 4 0
      UI/forms/images/sources/brush.svg
  20. 1 0
      UI/forms/images/sources/camera.svg
  21. 1 0
      UI/forms/images/sources/default.svg
  22. 1 0
      UI/forms/images/sources/gamepad.svg
  23. 1 0
      UI/forms/images/sources/globe.svg
  24. 3 0
      UI/forms/images/sources/group.svg
  25. 3 0
      UI/forms/images/sources/image.svg
  26. 3 0
      UI/forms/images/sources/media.svg
  27. 4 0
      UI/forms/images/sources/microphone.svg
  28. 4 0
      UI/forms/images/sources/scene.svg
  29. 5 0
      UI/forms/images/sources/slideshow.svg
  30. 3 0
      UI/forms/images/sources/text.svg
  31. 1 0
      UI/forms/images/sources/window.svg
  32. 13 0
      UI/forms/obs.qrc
  33. 30 3
      UI/source-tree.cpp
  34. 2 0
      UI/source-tree.hpp
  35. 188 0
      UI/window-basic-main-icons.cpp
  36. 12 0
      UI/window-basic-main.cpp
  37. 80 0
      UI/window-basic-main.hpp
  38. 6 0
      libobs/obs-source.c
  39. 20 0
      libobs/obs-source.h
  40. 4 0
      libobs/obs.h
  41. 1 0
      plugins/decklink/decklink-source.cpp
  42. 1 0
      plugins/image-source/color-source.c
  43. 3 1
      plugins/image-source/image-source.c
  44. 1 0
      plugins/image-source/obs-slideshow.c
  45. 1 0
      plugins/linux-alsa/alsa-input.c
  46. 1 0
      plugins/linux-capture/xcomposite-main.cpp
  47. 1 0
      plugins/linux-capture/xshm-input.c
  48. 1 0
      plugins/linux-jack/jack-input.c
  49. 2 0
      plugins/linux-pulseaudio/pulse-input.c
  50. 1 0
      plugins/linux-v4l2/v4l2-input.c
  51. 1 0
      plugins/mac-avcapture/av-capture.mm
  52. 2 0
      plugins/mac-capture/mac-audio.c
  53. 1 0
      plugins/mac-capture/mac-display-capture.m
  54. 1 0
      plugins/mac-capture/mac-window-capture.m
  55. 1 0
      plugins/mac-syphon/syphon.m
  56. 1 0
      plugins/obs-ffmpeg/obs-ffmpeg-source.c
  57. 1 0
      plugins/obs-text/gdiplus/obs-text.cpp
  58. 1 0
      plugins/text-freetype2/text-freetype2.c
  59. 1 0
      plugins/vlc-video/vlc-video-source.c
  60. 1 0
      plugins/win-capture/duplicator-monitor-capture.c
  61. 1 0
      plugins/win-capture/game-capture.c
  62. 1 0
      plugins/win-capture/monitor-capture.c
  63. 1 0
      plugins/win-capture/window-capture.c
  64. 1 0
      plugins/win-dshow/win-dshow.cpp
  65. 1 0
      plugins/win-ivcam/realsense.cpp
  66. 2 0
      plugins/win-wasapi/win-wasapi.cpp

+ 1 - 0
UI/CMakeLists.txt

@@ -224,6 +224,7 @@ set(obs_SOURCES
 	window-basic-main-dropfiles.cpp
 	window-basic-main-profiles.cpp
 	window-basic-main-browser.cpp
+	window-basic-main-icons.cpp
 	window-basic-status-bar.cpp
 	window-basic-adv-audio.cpp
 	window-basic-transform.cpp

+ 21 - 5
UI/data/themes/Acri.qss

@@ -142,6 +142,7 @@ SourceTree::item {
 	margin-bottom: 2px;
 	margin-top: 0px;
 	border: 1px solid transparent;
+	height: 24px;
 }
 
 QListWidget QLineEdit {
@@ -200,11 +201,6 @@ SourceTree {
 	border-bottom: 1px solid #2f2f2f;
 }
 
-SourceTree QLabel {
-	padding: 2px 0px;
-	margin: -2px 4px -2px;
-}
-
 SourceTree QLineEdit {
 	background-color: #0c101e;
 	padding: 2px;
@@ -1006,3 +1002,23 @@ QPushButton#extraPanelDelete:hover {
 QPushButton#extraPanelDelete:pressed {
     background-color: #161f41;
 }
+
+/* Source Icons */
+
+OBSBasic {
+    qproperty-imageIcon: url(./Dark/sources/image.svg);
+    qproperty-colorIcon: url(./Dark/sources/brush.svg);
+    qproperty-slideshowIcon: url(./Dark/sources/slideshow.svg);
+    qproperty-audioInputIcon: url(./Dark/sources/microphone.svg);
+    qproperty-audioOutputIcon: url(./Dark/settings/audio.svg);
+    qproperty-desktopCapIcon: url(./Dark/settings/video.svg);
+    qproperty-windowCapIcon: url(./Dark/sources/window.svg);
+    qproperty-gameCapIcon: url(./Dark/sources/gamepad.svg);
+    qproperty-cameraIcon: url(./Dark/sources/camera.svg);
+    qproperty-textIcon: url(./Dark/sources/text.svg);
+    qproperty-mediaIcon: url(./Dark/sources/media.svg);
+    qproperty-browserIcon: url(./Dark/sources/globe.svg);
+    qproperty-groupIcon: url(./Dark/sources/group.svg);
+    qproperty-sceneIcon: url(./Dark/sources/scene.svg);
+    qproperty-defaultIcon: url(./Dark/sources/default.svg);
+}

+ 25 - 0
UI/data/themes/Dark.qss

@@ -700,6 +700,11 @@ OBSBasicSettings QListWidget::item {
     padding-bottom: 5px;
 }
 
+QListWidget::item,
+SourceTree::item {
+	height: 24px;
+}
+
 /* Locked CheckBox */
 
 LockedCheckBox {
@@ -749,3 +754,23 @@ QPushButton#extraPanelDelete:hover {
 QPushButton#extraPanelDelete:pressed {
     background-color: rgb(31,30,31);
 }
+
+/* Source Icons */
+
+OBSBasic {
+    qproperty-imageIcon: url(./Dark/sources/image.svg);
+    qproperty-colorIcon: url(./Dark/sources/brush.svg);
+    qproperty-slideshowIcon: url(./Dark/sources/slideshow.svg);
+    qproperty-audioInputIcon: url(./Dark/sources/microphone.svg);
+    qproperty-audioOutputIcon: url(./Dark/settings/audio.svg);
+    qproperty-desktopCapIcon: url(./Dark/settings/video.svg);
+    qproperty-windowCapIcon: url(./Dark/sources/window.svg);
+    qproperty-gameCapIcon: url(./Dark/sources/gamepad.svg);
+    qproperty-cameraIcon: url(./Dark/sources/camera.svg);
+    qproperty-textIcon: url(./Dark/sources/text.svg);
+    qproperty-mediaIcon: url(./Dark/sources/media.svg);
+    qproperty-browserIcon: url(./Dark/sources/globe.svg);
+    qproperty-groupIcon: url(./Dark/sources/group.svg);
+    qproperty-sceneIcon: url(./Dark/sources/scene.svg);
+    qproperty-defaultIcon: url(./Dark/sources/default.svg);
+}

+ 4 - 0
UI/data/themes/Dark/sources/brush.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="#d2d2d2">
+  <path d="M7.44.03c-.03 0-.04.02-.06.03l-3.75 2.66c-.04.03-.1.11-.13.16l-.13.25c.72.23 1.27.78 1.5 1.5l.25-.13c.05-.03.12-.08.16-.13l2.66-3.75c.03-.05.04-.09 0-.13l-.44-.44c-.02-.02-.04-.03-.06-.03zm-4.78 3.97c-.74 0-1.31.61-1.31 1.34 0 .99-.55 1.85-1.34 2.31.39.22.86.34 1.34.34 1.47 0 2.66-1.18 2.66-2.66 0-.74-.61-1.34-1.34-1.34z"
+  />
+</svg>

+ 1 - 0
UI/data/themes/Dark/sources/camera.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="#d2d2d2"><path fill-rule="evenodd" d="M15 3H7c0-.55-.45-1-1-1H2c-.55 0-1 .45-1 1-.55 0-1 .45-1 1v9c0 .55.45 1 1 1h14c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1zM6 5H2V4h4v1zm4.5 7C8.56 12 7 10.44 7 8.5S8.56 5 10.5 5 14 6.56 14 8.5 12.44 12 10.5 12zM13 8.5c0 1.38-1.13 2.5-2.5 2.5S8 9.87 8 8.5 9.13 6 10.5 6 13 7.13 13 8.5z"/></svg>

+ 1 - 0
UI/data/themes/Dark/sources/default.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#d2d2d2" 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>

+ 1 - 0
UI/data/themes/Dark/sources/gamepad.svg

@@ -0,0 +1 @@
+<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="gamepad" class="svg-inline--fa fa-gamepad fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" fill="#d2d2d2"><path d="M480 96H160C71.6 96 0 167.6 0 256s71.6 160 160 160c44.8 0 85.2-18.4 114.2-48h91.5c29 29.6 69.5 48 114.2 48 88.4 0 160-71.6 160-160S568.4 96 480 96zM256 276c0 6.6-5.4 12-12 12h-52v52c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12v-52H76c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h52v-52c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h52c6.6 0 12 5.4 12 12v40zm184 68c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48zm80-80c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48z"></path></svg>

+ 1 - 0
UI/data/themes/Dark/sources/globe.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="16" viewBox="0 0 14 16" fill="#d2d2d2"><path fill-rule="evenodd" d="M7 1C3.14 1 0 4.14 0 8s3.14 7 7 7c.48 0 .94-.05 1.38-.14-.17-.08-.2-.73-.02-1.09.19-.41.81-1.45.2-1.8-.61-.35-.44-.5-.81-.91-.37-.41-.22-.47-.25-.58-.08-.34.36-.89.39-.94.02-.06.02-.27 0-.33 0-.08-.27-.22-.34-.23-.06 0-.11.11-.2.13-.09.02-.5-.25-.59-.33-.09-.08-.14-.23-.27-.34-.13-.13-.14-.03-.33-.11s-.8-.31-1.28-.48c-.48-.19-.52-.47-.52-.66-.02-.2-.3-.47-.42-.67-.14-.2-.16-.47-.2-.41-.04.06.25.78.2.81-.05.02-.16-.2-.3-.38-.14-.19.14-.09-.3-.95s.14-1.3.17-1.75c.03-.45.38.17.19-.13-.19-.3 0-.89-.14-1.11-.13-.22-.88.25-.88.25.02-.22.69-.58 1.16-.92.47-.34.78-.06 1.16.05.39.13.41.09.28-.05-.13-.13.06-.17.36-.13.28.05.38.41.83.36.47-.03.05.09.11.22s-.06.11-.38.3c-.3.2.02.22.55.61s.38-.25.31-.55c-.07-.3.39-.06.39-.06.33.22.27.02.5.08.23.06.91.64.91.64-.83.44-.31.48-.17.59.14.11-.28.3-.28.3-.17-.17-.19.02-.3.08-.11.06-.02.22-.02.22-.56.09-.44.69-.42.83 0 .14-.38.36-.47.58-.09.2.25.64.06.66-.19.03-.34-.66-1.31-.41-.3.08-.94.41-.59 1.08.36.69.92-.19 1.11-.09.19.1-.06.53-.02.55.04.02.53.02.56.61.03.59.77.53.92.55.17 0 .7-.44.77-.45.06-.03.38-.28 1.03.09.66.36.98.31 1.2.47.22.16.08.47.28.58.2.11 1.06-.03 1.28.31.22.34-.88 2.09-1.22 2.28-.34.19-.48.64-.84.92s-.81.64-1.27.91c-.41.23-.47.66-.66.8 3.14-.7 5.48-3.5 5.48-6.84 0-3.86-3.14-7-7-7L7 1zm1.64 6.56c-.09.03-.28.22-.78-.08-.48-.3-.81-.23-.86-.28 0 0-.05-.11.17-.14.44-.05.98.41 1.11.41.13 0 .19-.13.41-.05.22.08.05.13-.05.14zM6.34 1.7c-.05-.03.03-.08.09-.14.03-.03.02-.11.05-.14.11-.11.61-.25.52.03-.11.27-.58.3-.66.25zm1.23.89c-.19-.02-.58-.05-.52-.14.3-.28-.09-.38-.34-.38-.25-.02-.34-.16-.22-.19.12-.03.61.02.7.08.08.06.52.25.55.38.02.13 0 .25-.17.25zm1.47-.05c-.14.09-.83-.41-.95-.52-.56-.48-.89-.31-1-.41-.11-.1-.08-.19.11-.34.19-.15.69.06 1 .09.3.03.66.27.66.55.02.25.33.5.19.63h-.01z"/></svg>

+ 3 - 0
UI/data/themes/Dark/sources/group.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="#d2d2d2">
+  <path d="M0 0v2h8v-1h-5v-1h-3zm0 3v4.5c0 .28.22.5.5.5h7c.28 0 .5-.22.5-.5v-4.5h-8z" />
+</svg>

+ 3 - 0
UI/data/themes/Dark/sources/image.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="#d2d2d2">
+  <path d="M0 0v8h8v-8h-8zm1 1h6v3l-1-1-1 1 2 2v1h-1l-4-4-1 1v-3z" />
+</svg>

+ 3 - 0
UI/data/themes/Dark/sources/media.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="#d2d2d2">
+  <path d="M0 0v6l6-3-6-3z" transform="translate(1 1)" />
+</svg>

+ 4 - 0
UI/data/themes/Dark/sources/microphone.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="#d2d2d2">
+  <path d="M2.91-.03a1 1 0 0 0-.13.03 1 1 0 0 0-.78 1v2a1 1 0 1 0 2 0v-2a1 1 0 0 0-1.09-1.03zm-2.56 2.03a.5.5 0 0 0-.34.5v.5c0 1.48 1.09 2.69 2.5 2.94v1.06h-.5c-.55 0-1 .45-1 1h4.01c0-.55-.45-1-1-1h-.5v-1.06c1.41-.24 2.5-1.46 2.5-2.94v-.5a.5.5 0 1 0-1 0v.5c0 1.11-.89 2-2 2-1.11 0-2-.89-2-2v-.5a.5.5 0 0 0-.59-.5.5.5 0 0 0-.06 0z"
+  transform="translate(1)" />
+</svg>

+ 4 - 0
UI/data/themes/Dark/sources/scene.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="#d2d2d2">
+  <path d="M.5 0c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm1.5 0v1h6v-1h-6zm-1.5 2c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm1.5 0v1h6v-1h-6zm-1.5 2c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm1.5 0v1h6v-1h-6zm-1.5 2c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm1.5 0v1h6v-1h-6z"
+  />
+</svg>

+ 5 - 0
UI/data/themes/Dark/sources/slideshow.svg

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg version="1.1" viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg">
+<path d="m7.3514 0.64865h0.64865v4.5946h-0.64865zm-4.7568-0.64865h5.4054v0.64865h-5.4054zm3.8919 1.5135h0.64865v4.5946h-0.64865zm-4.7568-0.64865h5.4054v0.64865h-5.4054zm3.8919 1.5135h0.64865v4.5946h-0.64865zm-4.7568-0.64865h5.4054v0.64865h-5.4054zm-0.86486 0.86486v5.4054h5.4054v-5.4054zm0.67568 0.67568h4.0541v2.027l-0.67568-0.67568-0.67568 0.67568 1.3514 1.3514v0.67568h-0.67568l-2.7027-2.7027-0.67568 0.67568z" fill="#d2d2d2" stroke-width=".72385"/>
+</svg>
+

+ 3 - 0
UI/data/themes/Dark/sources/text.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="#d2d2d2">
+  <path d="M0 0v2h.5c0-.55.45-1 1-1h1.5v5.5c0 .28-.22.5-.5.5h-.5v1h4v-1h-.5c-.28 0-.5-.22-.5-.5v-5.5h1.5c.55 0 1 .45 1 1h.5v-2h-8z" />
+</svg>

+ 1 - 0
UI/data/themes/Dark/sources/window.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="16" viewBox="0 0 14 16" fill="#d2d2d2"><path fill-rule="evenodd" d="M5 3h1v1H5V3zM3 3h1v1H3V3zM1 3h1v1H1V3zm12 10H1V5h12v8zm0-9H7V3h6v1zm1-1c0-.55-.45-1-1-1H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V3z"/></svg>

+ 25 - 0
UI/data/themes/Rachni.qss

@@ -184,6 +184,11 @@ QListWidget::item:hover:!active {
 	border: none;
 }
 
+QListWidget::item,
+SourceTree::item {
+	height: 24px;
+}
+
 /***********************/
 /* --- Dock widget --- */
 /***********************/
@@ -1340,3 +1345,23 @@ QPushButton#extraPanelDelete:hover {
 QPushButton#extraPanelDelete:pressed {
      background-color: rgb(240, 98, 146);
 }
+
+/* Source Icons */
+
+OBSBasic {
+    qproperty-imageIcon: url(./Dark/sources/image.svg);
+    qproperty-colorIcon: url(./Dark/sources/brush.svg);
+    qproperty-slideshowIcon: url(./Dark/sources/slideshow.svg);
+    qproperty-audioInputIcon: url(./Dark/sources/microphone.svg);
+    qproperty-audioOutputIcon: url(./Dark/settings/audio.svg);
+    qproperty-desktopCapIcon: url(./Dark/settings/video.svg);
+    qproperty-windowCapIcon: url(./Dark/sources/window.svg);
+    qproperty-gameCapIcon: url(./Dark/sources/gamepad.svg);
+    qproperty-cameraIcon: url(./Dark/sources/camera.svg);
+    qproperty-textIcon: url(./Dark/sources/text.svg);
+    qproperty-mediaIcon: url(./Dark/sources/media.svg);
+    qproperty-browserIcon: url(./Dark/sources/globe.svg);
+    qproperty-groupIcon: url(./Dark/sources/group.svg);
+    qproperty-sceneIcon: url(./Dark/sources/scene.svg);
+    qproperty-defaultIcon: url(./Dark/sources/default.svg);
+}

+ 25 - 0
UI/data/themes/System.qss

@@ -208,3 +208,28 @@ VisibilityCheckBox::indicator:unchecked {
 * [themeID="revertIcon"] {
     qproperty-icon: url(:res/images/revert.svg);
 }
+
+/* Source Icons */
+
+OBSBasic {
+    qproperty-imageIcon: url(:res/images/sources/image.svg);
+    qproperty-colorIcon: url(:res/images/sources/brush.svg);
+    qproperty-slideshowIcon: url(:res/images/sources/slideshow.svg);
+    qproperty-audioInputIcon: url(:res/images/sources/microphone.svg);
+    qproperty-audioOutputIcon: url(:settings/images/settings/audio.svg);
+    qproperty-desktopCapIcon: url(:settings/images/settings/video.svg);
+    qproperty-windowCapIcon: url(:res/images/sources/window.svg);
+    qproperty-gameCapIcon: url(:res/images/sources/gamepad.svg);
+    qproperty-cameraIcon: url(:res/images/sources/camera.svg);
+    qproperty-textIcon: url(:res/images/sources/text.svg);
+    qproperty-mediaIcon: url(:res/images/sources/media.svg);
+    qproperty-browserIcon: url(:res/images/sources/globe.svg);
+    qproperty-groupIcon: url(:res/images/sources/group.svg);
+    qproperty-sceneIcon: url(:res/images/sources/scene.svg);
+    qproperty-defaultIcon: url(:res/images/sources/default.svg);
+}
+
+QListWidget::item,
+SourceTree::item {
+	height: 24px;
+}

+ 4 - 0
UI/forms/images/sources/brush.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M7.44.03c-.03 0-.04.02-.06.03l-3.75 2.66c-.04.03-.1.11-.13.16l-.13.25c.72.23 1.27.78 1.5 1.5l.25-.13c.05-.03.12-.08.16-.13l2.66-3.75c.03-.05.04-.09 0-.13l-.44-.44c-.02-.02-.04-.03-.06-.03zm-4.78 3.97c-.74 0-1.31.61-1.31 1.34 0 .99-.55 1.85-1.34 2.31.39.22.86.34 1.34.34 1.47 0 2.66-1.18 2.66-2.66 0-.74-.61-1.34-1.34-1.34z"
+  />
+</svg>

+ 1 - 0
UI/forms/images/sources/camera.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M15 3H7c0-.55-.45-1-1-1H2c-.55 0-1 .45-1 1-.55 0-1 .45-1 1v9c0 .55.45 1 1 1h14c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1zM6 5H2V4h4v1zm4.5 7C8.56 12 7 10.44 7 8.5S8.56 5 10.5 5 14 6.56 14 8.5 12.44 12 10.5 12zM13 8.5c0 1.38-1.13 2.5-2.5 2.5S8 9.87 8 8.5 9.13 6 10.5 6 13 7.13 13 8.5z"/></svg>

+ 1 - 0
UI/forms/images/sources/default.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="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>

+ 1 - 0
UI/forms/images/sources/gamepad.svg

@@ -0,0 +1 @@
+<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="gamepad" class="svg-inline--fa fa-gamepad fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M480 96H160C71.6 96 0 167.6 0 256s71.6 160 160 160c44.8 0 85.2-18.4 114.2-48h91.5c29 29.6 69.5 48 114.2 48 88.4 0 160-71.6 160-160S568.4 96 480 96zM256 276c0 6.6-5.4 12-12 12h-52v52c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12v-52H76c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h52v-52c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h52c6.6 0 12 5.4 12 12v40zm184 68c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48zm80-80c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48z"></path></svg>

+ 1 - 0
UI/forms/images/sources/globe.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="16" viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 1C3.14 1 0 4.14 0 8s3.14 7 7 7c.48 0 .94-.05 1.38-.14-.17-.08-.2-.73-.02-1.09.19-.41.81-1.45.2-1.8-.61-.35-.44-.5-.81-.91-.37-.41-.22-.47-.25-.58-.08-.34.36-.89.39-.94.02-.06.02-.27 0-.33 0-.08-.27-.22-.34-.23-.06 0-.11.11-.2.13-.09.02-.5-.25-.59-.33-.09-.08-.14-.23-.27-.34-.13-.13-.14-.03-.33-.11s-.8-.31-1.28-.48c-.48-.19-.52-.47-.52-.66-.02-.2-.3-.47-.42-.67-.14-.2-.16-.47-.2-.41-.04.06.25.78.2.81-.05.02-.16-.2-.3-.38-.14-.19.14-.09-.3-.95s.14-1.3.17-1.75c.03-.45.38.17.19-.13-.19-.3 0-.89-.14-1.11-.13-.22-.88.25-.88.25.02-.22.69-.58 1.16-.92.47-.34.78-.06 1.16.05.39.13.41.09.28-.05-.13-.13.06-.17.36-.13.28.05.38.41.83.36.47-.03.05.09.11.22s-.06.11-.38.3c-.3.2.02.22.55.61s.38-.25.31-.55c-.07-.3.39-.06.39-.06.33.22.27.02.5.08.23.06.91.64.91.64-.83.44-.31.48-.17.59.14.11-.28.3-.28.3-.17-.17-.19.02-.3.08-.11.06-.02.22-.02.22-.56.09-.44.69-.42.83 0 .14-.38.36-.47.58-.09.2.25.64.06.66-.19.03-.34-.66-1.31-.41-.3.08-.94.41-.59 1.08.36.69.92-.19 1.11-.09.19.1-.06.53-.02.55.04.02.53.02.56.61.03.59.77.53.92.55.17 0 .7-.44.77-.45.06-.03.38-.28 1.03.09.66.36.98.31 1.2.47.22.16.08.47.28.58.2.11 1.06-.03 1.28.31.22.34-.88 2.09-1.22 2.28-.34.19-.48.64-.84.92s-.81.64-1.27.91c-.41.23-.47.66-.66.8 3.14-.7 5.48-3.5 5.48-6.84 0-3.86-3.14-7-7-7L7 1zm1.64 6.56c-.09.03-.28.22-.78-.08-.48-.3-.81-.23-.86-.28 0 0-.05-.11.17-.14.44-.05.98.41 1.11.41.13 0 .19-.13.41-.05.22.08.05.13-.05.14zM6.34 1.7c-.05-.03.03-.08.09-.14.03-.03.02-.11.05-.14.11-.11.61-.25.52.03-.11.27-.58.3-.66.25zm1.23.89c-.19-.02-.58-.05-.52-.14.3-.28-.09-.38-.34-.38-.25-.02-.34-.16-.22-.19.12-.03.61.02.7.08.08.06.52.25.55.38.02.13 0 .25-.17.25zm1.47-.05c-.14.09-.83-.41-.95-.52-.56-.48-.89-.31-1-.41-.11-.1-.08-.19.11-.34.19-.15.69.06 1 .09.3.03.66.27.66.55.02.25.33.5.19.63h-.01z"/></svg>

+ 3 - 0
UI/forms/images/sources/group.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M0 0v2h8v-1h-5v-1h-3zm0 3v4.5c0 .28.22.5.5.5h7c.28 0 .5-.22.5-.5v-4.5h-8z" />
+</svg>

+ 3 - 0
UI/forms/images/sources/image.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M0 0v8h8v-8h-8zm1 1h6v3l-1-1-1 1 2 2v1h-1l-4-4-1 1v-3z" />
+</svg>

+ 3 - 0
UI/forms/images/sources/media.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M0 0v6l6-3-6-3z" transform="translate(1 1)" />
+</svg>

+ 4 - 0
UI/forms/images/sources/microphone.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M2.91-.03a1 1 0 0 0-.13.03 1 1 0 0 0-.78 1v2a1 1 0 1 0 2 0v-2a1 1 0 0 0-1.09-1.03zm-2.56 2.03a.5.5 0 0 0-.34.5v.5c0 1.48 1.09 2.69 2.5 2.94v1.06h-.5c-.55 0-1 .45-1 1h4.01c0-.55-.45-1-1-1h-.5v-1.06c1.41-.24 2.5-1.46 2.5-2.94v-.5a.5.5 0 1 0-1 0v.5c0 1.11-.89 2-2 2-1.11 0-2-.89-2-2v-.5a.5.5 0 0 0-.59-.5.5.5 0 0 0-.06 0z"
+  transform="translate(1)" />
+</svg>

+ 4 - 0
UI/forms/images/sources/scene.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M.5 0c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm1.5 0v1h6v-1h-6zm-1.5 2c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm1.5 0v1h6v-1h-6zm-1.5 2c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm1.5 0v1h6v-1h-6zm-1.5 2c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm1.5 0v1h6v-1h-6z"
+  />
+</svg>

+ 5 - 0
UI/forms/images/sources/slideshow.svg

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg version="1.1" viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg">
+<path d="m7.3514 0.64865h0.64865v4.5946h-0.64865zm-4.7568-0.64865h5.4054v0.64865h-5.4054zm3.8919 1.5135h0.64865v4.5946h-0.64865zm-4.7568-0.64865h5.4054v0.64865h-5.4054zm3.8919 1.5135h0.64865v4.5946h-0.64865zm-4.7568-0.64865h5.4054v0.64865h-5.4054zm-0.86486 0.86486v5.4054h5.4054v-5.4054zm0.67568 0.67568h4.0541v2.027l-0.67568-0.67568-0.67568 0.67568 1.3514 1.3514v0.67568h-0.67568l-2.7027-2.7027-0.67568 0.67568z" stroke-width=".72385"/>
+</svg>
+

+ 3 - 0
UI/forms/images/sources/text.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
+  <path d="M0 0v2h.5c0-.55.45-1 1-1h1.5v5.5c0 .28-.22.5-.5.5h-.5v1h4v-1h-.5c-.28 0-.5-.22-.5-.5v-5.5h1.5c.55 0 1 .45 1 1h.5v-2h-8z" />
+</svg>

+ 1 - 0
UI/forms/images/sources/window.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="16" viewBox="0 0 14 16"><path fill-rule="evenodd" d="M5 3h1v1H5V3zM3 3h1v1H3V3zM1 3h1v1H1V3zm12 10H1V5h12v8zm0-9H7V3h6v1zm1-1c0-.55-.45-1-1-1H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V3z"/></svg>

+ 13 - 0
UI/forms/obs.qrc

@@ -18,6 +18,19 @@
     <file>images/visible.svg</file>
     <file>images/trash.svg</file>
     <file>images/revert.svg</file>
+    <file>images/sources/brush.svg</file>
+    <file>images/sources/camera.svg</file>
+    <file>images/sources/gamepad.svg</file>
+    <file>images/sources/globe.svg</file>
+    <file>images/sources/group.svg</file>
+    <file>images/sources/image.svg</file>
+    <file>images/sources/media.svg</file>
+    <file>images/sources/microphone.svg</file>
+    <file>images/sources/scene.svg</file>
+    <file>images/sources/slideshow.svg</file>
+    <file>images/sources/text.svg</file>
+    <file>images/sources/window.svg</file>
+    <file>images/sources/default.svg</file>
   </qresource>
   <qresource prefix="/settings">
     <file>images/settings/output.svg</file>

+ 30 - 3
UI/source-tree.cpp

@@ -57,6 +57,24 @@ SourceTreeItem::SourceTreeItem(SourceTree *tree_, OBSSceneItem sceneitem_)
 
 	obs_data_release(privData);
 
+	OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
+	const char *id = obs_source_get_id(source);
+	QIcon icon;
+
+	if (strcmp(id, "scene") == 0)
+		icon = main->GetSceneIcon();
+	else if (strcmp(id, "group") == 0)
+		icon = main->GetGroupIcon();
+	else
+		icon = main->GetSourceIcon(id);
+
+	QPixmap pixmap = icon.pixmap(QSize(16, 16));
+
+	QLabel *iconLabel = new QLabel();
+	iconLabel->setPixmap(pixmap);
+	iconLabel->setFixedSize(16, 16);
+	iconLabel->setStyleSheet("background: none");
+
 	vis = new VisibilityCheckBox();
 	vis->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
 	vis->setFixedSize(16, 16);
@@ -80,11 +98,13 @@ SourceTreeItem::SourceTreeItem(SourceTree *tree_, OBSSceneItem sceneitem_)
 #endif
 
 	boxLayout = new QHBoxLayout();
-	boxLayout->setContentsMargins(1, 1, 1, 1);
-	boxLayout->setSpacing(1);
+
+	boxLayout->setContentsMargins(0, 0, 0, 0);
+	boxLayout->addWidget(iconLabel);
+	boxLayout->addSpacing(2);
 	boxLayout->addWidget(label);
 	boxLayout->addWidget(vis);
-	boxLayout->setSpacing(2);
+	boxLayout->addSpacing(1);
 	boxLayout->addWidget(lock);
 #ifdef __APPLE__
 	/* Hack: Fixes a bug where scrollbars would be above the lock icon */
@@ -943,6 +963,13 @@ SourceTree::SourceTree(QWidget *parent_) : QListView(parent_)
 	UpdateNoSourcesMessage();
 	connect(App(), &OBSApp::StyleChanged, this,
 		&SourceTree::UpdateNoSourcesMessage);
+	connect(App(), &OBSApp::StyleChanged, this, &SourceTree::UpdateIcons);
+}
+
+void SourceTree::UpdateIcons()
+{
+	SourceTreeModel *stm = GetStm();
+	stm->SceneChanged();
 }
 
 void SourceTree::ResetWidgets()

+ 2 - 0
UI/source-tree.hpp

@@ -177,6 +177,8 @@ public:
 	bool GroupsSelected() const;
 	bool GroupedItemsSelected() const;
 
+	void UpdateIcons();
+
 public slots:
 	inline void ReorderItems() { GetStm()->ReorderItems(); }
 	void Remove(OBSSceneItem item);

+ 188 - 0
UI/window-basic-main-icons.cpp

@@ -0,0 +1,188 @@
+#include <window-basic-main.hpp>
+
+QIcon OBSBasic::GetSourceIcon(const char *id) const
+{
+	obs_icon_type type = obs_source_get_icon_type(id);
+
+	switch (type) {
+	case OBS_ICON_TYPE_IMAGE:
+		return GetImageIcon();
+	case OBS_ICON_TYPE_COLOR:
+		return GetColorIcon();
+	case OBS_ICON_TYPE_SLIDESHOW:
+		return GetSlideshowIcon();
+	case OBS_ICON_TYPE_AUDIO_INPUT:
+		return GetAudioInputIcon();
+	case OBS_ICON_TYPE_AUDIO_OUTPUT:
+		return GetAudioOutputIcon();
+	case OBS_ICON_TYPE_DESKTOP_CAPTURE:
+		return GetDesktopCapIcon();
+	case OBS_ICON_TYPE_WINDOW_CAPTURE:
+		return GetWindowCapIcon();
+	case OBS_ICON_TYPE_GAME_CAPTURE:
+		return GetGameCapIcon();
+	case OBS_ICON_TYPE_CAMERA:
+		return GetCameraIcon();
+	case OBS_ICON_TYPE_TEXT:
+		return GetTextIcon();
+	case OBS_ICON_TYPE_MEDIA:
+		return GetMediaIcon();
+	case OBS_ICON_TYPE_BROWSER:
+		return GetBrowserIcon();
+	case OBS_ICON_TYPE_CUSTOM:
+		//TODO: Add ability for sources to define custom icons
+		return GetDefaultIcon();
+	default:
+		return GetDefaultIcon();
+	}
+}
+
+void OBSBasic::SetImageIcon(const QIcon &icon)
+{
+	imageIcon = icon;
+}
+
+void OBSBasic::SetColorIcon(const QIcon &icon)
+{
+	colorIcon = icon;
+}
+
+void OBSBasic::SetSlideshowIcon(const QIcon &icon)
+{
+	slideshowIcon = icon;
+}
+
+void OBSBasic::SetAudioInputIcon(const QIcon &icon)
+{
+	audioInputIcon = icon;
+}
+
+void OBSBasic::SetAudioOutputIcon(const QIcon &icon)
+{
+	audioOutputIcon = icon;
+}
+
+void OBSBasic::SetDesktopCapIcon(const QIcon &icon)
+{
+	desktopCapIcon = icon;
+}
+
+void OBSBasic::SetWindowCapIcon(const QIcon &icon)
+{
+	windowCapIcon = icon;
+}
+
+void OBSBasic::SetGameCapIcon(const QIcon &icon)
+{
+	gameCapIcon = icon;
+}
+
+void OBSBasic::SetCameraIcon(const QIcon &icon)
+{
+	cameraIcon = icon;
+}
+
+void OBSBasic::SetTextIcon(const QIcon &icon)
+{
+	textIcon = icon;
+}
+
+void OBSBasic::SetMediaIcon(const QIcon &icon)
+{
+	mediaIcon = icon;
+}
+
+void OBSBasic::SetBrowserIcon(const QIcon &icon)
+{
+	browserIcon = icon;
+}
+
+void OBSBasic::SetGroupIcon(const QIcon &icon)
+{
+	groupIcon = icon;
+}
+
+void OBSBasic::SetSceneIcon(const QIcon &icon)
+{
+	sceneIcon = icon;
+}
+
+void OBSBasic::SetDefaultIcon(const QIcon &icon)
+{
+	defaultIcon = icon;
+}
+
+QIcon OBSBasic::GetImageIcon() const
+{
+	return imageIcon;
+}
+
+QIcon OBSBasic::GetColorIcon() const
+{
+	return colorIcon;
+}
+
+QIcon OBSBasic::GetSlideshowIcon() const
+{
+	return slideshowIcon;
+}
+
+QIcon OBSBasic::GetAudioInputIcon() const
+{
+	return audioInputIcon;
+}
+
+QIcon OBSBasic::GetAudioOutputIcon() const
+{
+	return audioOutputIcon;
+}
+
+QIcon OBSBasic::GetDesktopCapIcon() const
+{
+	return desktopCapIcon;
+}
+
+QIcon OBSBasic::GetWindowCapIcon() const
+{
+	return windowCapIcon;
+}
+
+QIcon OBSBasic::GetGameCapIcon() const
+{
+	return gameCapIcon;
+}
+
+QIcon OBSBasic::GetCameraIcon() const
+{
+	return cameraIcon;
+}
+
+QIcon OBSBasic::GetTextIcon() const
+{
+	return textIcon;
+}
+
+QIcon OBSBasic::GetMediaIcon() const
+{
+	return mediaIcon;
+}
+
+QIcon OBSBasic::GetBrowserIcon() const
+{
+	return browserIcon;
+}
+
+QIcon OBSBasic::GetGroupIcon() const
+{
+	return groupIcon;
+}
+
+QIcon OBSBasic::GetSceneIcon() const
+{
+	return sceneIcon;
+}
+
+QIcon OBSBasic::GetDefaultIcon() const
+{
+	return defaultIcon;
+}

+ 12 - 0
UI/window-basic-main.cpp

@@ -1816,6 +1816,8 @@ void OBSBasic::OBSInit()
 	ui->viewMenu->addAction(QTStr("MultiviewWindowed"), this,
 				SLOT(OpenMultiviewWindow()));
 
+	ui->sources->UpdateIcons();
+
 #if !defined(_WIN32) && !defined(__APPLE__)
 	delete ui->actionShowCrashLogs;
 	delete ui->actionUploadLastCrashLog;
@@ -4612,6 +4614,15 @@ QMenu *OBSBasic::CreateAddSourcePopupMenu()
 		connect(popupItem, SIGNAL(triggered(bool)), this,
 			SLOT(AddSourceFromAction()));
 
+		QIcon icon;
+
+		if (strcmp(type, "scene") == 0)
+			icon = GetSceneIcon();
+		else
+			icon = GetSourceIcon(type);
+
+		popupItem->setIcon(icon);
+
 		QAction *after = getActionAfter(popup, qname);
 		popup->insertAction(after, popupItem);
 	};
@@ -4637,6 +4648,7 @@ QMenu *OBSBasic::CreateAddSourcePopupMenu()
 	popup->addSeparator();
 	QAction *addGroup = new QAction(QTStr("Group"), this);
 	addGroup->setData(QT_UTF8("group"));
+	addGroup->setIcon(GetGroupIcon());
 	connect(addGroup, SIGNAL(triggered(bool)), this,
 		SLOT(AddSourceFromAction()));
 	popup->addAction(addGroup);

+ 80 - 0
UI/window-basic-main.hpp

@@ -117,6 +117,36 @@ private:
 
 class OBSBasic : public OBSMainWindow {
 	Q_OBJECT
+	Q_PROPERTY(QIcon imageIcon READ GetImageIcon WRITE SetImageIcon
+			   DESIGNABLE true)
+	Q_PROPERTY(QIcon colorIcon READ GetColorIcon WRITE SetColorIcon
+			   DESIGNABLE true)
+	Q_PROPERTY(QIcon slideshowIcon READ GetSlideshowIcon WRITE
+			   SetSlideshowIcon DESIGNABLE true)
+	Q_PROPERTY(QIcon audioInputIcon READ GetAudioInputIcon WRITE
+			   SetAudioInputIcon DESIGNABLE true)
+	Q_PROPERTY(QIcon audioOutputIcon READ GetAudioOutputIcon WRITE
+			   SetAudioOutputIcon DESIGNABLE true)
+	Q_PROPERTY(QIcon desktopCapIcon READ GetDesktopCapIcon WRITE
+			   SetDesktopCapIcon DESIGNABLE true)
+	Q_PROPERTY(QIcon windowCapIcon READ GetWindowCapIcon WRITE
+			   SetWindowCapIcon DESIGNABLE true)
+	Q_PROPERTY(QIcon gameCapIcon READ GetGameCapIcon WRITE SetGameCapIcon
+			   DESIGNABLE true)
+	Q_PROPERTY(QIcon cameraIcon READ GetCameraIcon WRITE SetCameraIcon
+			   DESIGNABLE true)
+	Q_PROPERTY(QIcon textIcon READ GetTextIcon WRITE SetTextIcon
+			   DESIGNABLE true)
+	Q_PROPERTY(QIcon mediaIcon READ GetMediaIcon WRITE SetMediaIcon
+			   DESIGNABLE true)
+	Q_PROPERTY(QIcon browserIcon READ GetBrowserIcon WRITE SetBrowserIcon
+			   DESIGNABLE true)
+	Q_PROPERTY(QIcon groupIcon READ GetGroupIcon WRITE SetGroupIcon
+			   DESIGNABLE true)
+	Q_PROPERTY(QIcon sceneIcon READ GetSceneIcon WRITE SetSceneIcon
+			   DESIGNABLE true)
+	Q_PROPERTY(QIcon defaultIcon READ GetDefaultIcon WRITE SetDefaultIcon
+			   DESIGNABLE true)
 
 	friend class OBSAbout;
 	friend class OBSBasicPreview;
@@ -441,6 +471,36 @@ private:
 				 bool firstCreate);
 #endif
 
+	QIcon imageIcon;
+	QIcon colorIcon;
+	QIcon slideshowIcon;
+	QIcon audioInputIcon;
+	QIcon audioOutputIcon;
+	QIcon desktopCapIcon;
+	QIcon windowCapIcon;
+	QIcon gameCapIcon;
+	QIcon cameraIcon;
+	QIcon textIcon;
+	QIcon mediaIcon;
+	QIcon browserIcon;
+	QIcon groupIcon;
+	QIcon sceneIcon;
+	QIcon defaultIcon;
+
+	QIcon GetImageIcon() const;
+	QIcon GetColorIcon() const;
+	QIcon GetSlideshowIcon() const;
+	QIcon GetAudioInputIcon() const;
+	QIcon GetAudioOutputIcon() const;
+	QIcon GetDesktopCapIcon() const;
+	QIcon GetWindowCapIcon() const;
+	QIcon GetGameCapIcon() const;
+	QIcon GetCameraIcon() const;
+	QIcon GetTextIcon() const;
+	QIcon GetMediaIcon() const;
+	QIcon GetBrowserIcon() const;
+	QIcon GetDefaultIcon() const;
+
 public slots:
 	void DeferSaveBegin();
 	void DeferSaveEnd();
@@ -567,6 +627,22 @@ private slots:
 
 	void ResetStatsHotkey();
 
+	void SetImageIcon(const QIcon &icon);
+	void SetColorIcon(const QIcon &icon);
+	void SetSlideshowIcon(const QIcon &icon);
+	void SetAudioInputIcon(const QIcon &icon);
+	void SetAudioOutputIcon(const QIcon &icon);
+	void SetDesktopCapIcon(const QIcon &icon);
+	void SetWindowCapIcon(const QIcon &icon);
+	void SetGameCapIcon(const QIcon &icon);
+	void SetCameraIcon(const QIcon &icon);
+	void SetTextIcon(const QIcon &icon);
+	void SetMediaIcon(const QIcon &icon);
+	void SetBrowserIcon(const QIcon &icon);
+	void SetGroupIcon(const QIcon &icon);
+	void SetSceneIcon(const QIcon &icon);
+	void SetDefaultIcon(const QIcon &icon);
+
 private:
 	/* OBS Callbacks */
 	static void SceneReordered(void *data, calldata_t *params);
@@ -696,6 +772,10 @@ public:
 	void AddProjectorMenuMonitors(QMenu *parent, QObject *target,
 				      const char *slot);
 
+	QIcon GetSourceIcon(const char *id) const;
+	QIcon GetGroupIcon() const;
+	QIcon GetSceneIcon() const;
+
 protected:
 	virtual void closeEvent(QCloseEvent *event) override;
 	virtual void changeEvent(QEvent *event) override;

+ 6 - 0
libobs/obs-source.c

@@ -4734,3 +4734,9 @@ uint32_t obs_source_get_last_obs_version(const obs_source_t *source)
 		       ? source->last_obs_ver
 		       : 0;
 }
+
+enum obs_icon_type obs_source_get_icon_type(const char *id)
+{
+	const struct obs_source_info *info = get_source_info(id);
+	return (info) ? info->icon_type : OBS_ICON_TYPE_UNKNOWN;
+}

+ 20 - 0
libobs/obs-source.h

@@ -43,6 +43,23 @@ enum obs_balance_type {
 	OBS_BALANCE_TYPE_LINEAR,
 };
 
+enum obs_icon_type {
+	OBS_ICON_TYPE_UNKNOWN,
+	OBS_ICON_TYPE_IMAGE,
+	OBS_ICON_TYPE_COLOR,
+	OBS_ICON_TYPE_SLIDESHOW,
+	OBS_ICON_TYPE_AUDIO_INPUT,
+	OBS_ICON_TYPE_AUDIO_OUTPUT,
+	OBS_ICON_TYPE_DESKTOP_CAPTURE,
+	OBS_ICON_TYPE_WINDOW_CAPTURE,
+	OBS_ICON_TYPE_GAME_CAPTURE,
+	OBS_ICON_TYPE_CAMERA,
+	OBS_ICON_TYPE_TEXT,
+	OBS_ICON_TYPE_MEDIA,
+	OBS_ICON_TYPE_BROWSER,
+	OBS_ICON_TYPE_CUSTOM,
+};
+
 /**
  * @name Source output flags
  *
@@ -471,6 +488,9 @@ struct obs_source_info {
 	bool (*audio_mix)(void *data, uint64_t *ts_out,
 			  struct audio_output_data *audio_output,
 			  size_t channels, size_t sample_rate);
+
+	/** Icon type for the source */
+	enum obs_icon_type icon_type;
 };
 
 EXPORT void obs_register_source_s(const struct obs_source_info *info,

+ 4 - 0
libobs/obs.h

@@ -2170,6 +2170,10 @@ static inline void obs_source_frame_destroy(struct obs_source_frame *frame)
 EXPORT void obs_source_frame_copy(struct obs_source_frame *dst,
 				  const struct obs_source_frame *src);
 
+/* ------------------------------------------------------------------------- */
+/* Get source icon type */
+EXPORT enum obs_icon_type obs_source_get_icon_type(const char *id);
+
 #ifdef __cplusplus
 }
 #endif

+ 1 - 0
plugins/decklink/decklink-source.cpp

@@ -342,6 +342,7 @@ struct obs_source_info create_decklink_source_info()
 	decklink_source_info.update = decklink_update;
 	decklink_source_info.show = decklink_show;
 	decklink_source_info.hide = decklink_hide;
+	decklink_source_info.icon_type = OBS_ICON_TYPE_CAMERA;
 
 	return decklink_source_info;
 }

+ 1 - 0
plugins/image-source/color-source.c

@@ -119,4 +119,5 @@ struct obs_source_info color_source_info = {
 	.get_height = color_source_getheight,
 	.video_render = color_source_render,
 	.get_properties = color_source_properties,
+	.icon_type = OBS_ICON_TYPE_COLOR,
 };

+ 3 - 1
plugins/image-source/image-source.c

@@ -265,7 +265,9 @@ static struct obs_source_info image_source_info = {
 	.get_height = image_source_getheight,
 	.video_render = image_source_render,
 	.video_tick = image_source_tick,
-	.get_properties = image_source_properties};
+	.get_properties = image_source_properties,
+	.icon_type = OBS_ICON_TYPE_IMAGE,
+};
 
 OBS_DECLARE_MODULE()
 OBS_MODULE_USE_DEFAULT_LOCALE("image-source", "en-US")

+ 1 - 0
plugins/image-source/obs-slideshow.c

@@ -940,4 +940,5 @@ struct obs_source_info slideshow_info = {
 	.get_height = ss_height,
 	.get_defaults = ss_defaults,
 	.get_properties = ss_properties,
+	.icon_type = OBS_ICON_TYPE_SLIDESHOW,
 };

+ 1 - 0
plugins/linux-alsa/alsa-input.c

@@ -88,6 +88,7 @@ struct obs_source_info alsa_input_capture = {
 	.get_defaults = alsa_get_defaults,
 	.get_name = alsa_get_name,
 	.get_properties = alsa_get_properties,
+	.icon_type = OBS_ICON_TYPE_AUDIO_INPUT,
 };
 
 static bool _alsa_try_open(struct alsa_data *);

+ 1 - 0
plugins/linux-capture/xcomposite-main.cpp

@@ -82,6 +82,7 @@ extern "C" void xcomposite_load(void)
 	sinfo.video_render = xcompcap_video_render;
 	sinfo.get_width = xcompcap_getwidth;
 	sinfo.get_height = xcompcap_getheight;
+	sinfo.icon_type = OBS_ICON_TYPE_WINDOW_CAPTURE,
 
 	obs_register_source(&sinfo);
 }

+ 1 - 0
plugins/linux-capture/xshm-input.c

@@ -508,4 +508,5 @@ struct obs_source_info xshm_input = {
 	.video_render = xshm_video_render,
 	.get_width = xshm_getwidth,
 	.get_height = xshm_getheight,
+	.icon_type = OBS_ICON_TYPE_DESKTOP_CAPTURE,
 };

+ 1 - 0
plugins/linux-jack/jack-input.c

@@ -147,4 +147,5 @@ struct obs_source_info jack_output_capture = {
 	.update = jack_update,
 	.get_defaults = jack_input_defaults,
 	.get_properties = jack_input_properties,
+	.icon_type = OBS_ICON_TYPE_AUDIO_OUTPUT,
 };

+ 2 - 0
plugins/linux-pulseaudio/pulse-input.c

@@ -594,6 +594,7 @@ struct obs_source_info pulse_input_capture = {
 	.update = pulse_update,
 	.get_defaults = pulse_defaults,
 	.get_properties = pulse_input_properties,
+	.icon_type = OBS_ICON_TYPE_AUDIO_INPUT,
 };
 
 struct obs_source_info pulse_output_capture = {
@@ -607,4 +608,5 @@ struct obs_source_info pulse_output_capture = {
 	.update = pulse_update,
 	.get_defaults = pulse_defaults,
 	.get_properties = pulse_output_properties,
+	.icon_type = OBS_ICON_TYPE_AUDIO_OUTPUT,
 };

+ 1 - 0
plugins/linux-v4l2/v4l2-input.c

@@ -1005,4 +1005,5 @@ struct obs_source_info v4l2_input = {
 	.update = v4l2_update,
 	.get_defaults = v4l2_defaults,
 	.get_properties = v4l2_properties,
+	.icon_type = OBS_ICON_TYPE_CAMERA,
 };

+ 1 - 0
plugins/mac-avcapture/av-capture.mm

@@ -2194,6 +2194,7 @@ bool obs_module_load(void)
 		.get_defaults = av_capture_defaults,
 		.get_properties = av_capture_properties,
 		.update = av_capture_update,
+		.icon_type = OBS_ICON_TYPE_CAMERA,
 	};
 
 	obs_register_source(&av_capture_info);

+ 2 - 0
plugins/mac-capture/mac-audio.c

@@ -817,6 +817,7 @@ struct obs_source_info coreaudio_input_capture_info = {
 	.update = coreaudio_update,
 	.get_defaults = coreaudio_defaults,
 	.get_properties = coreaudio_input_properties,
+	.icon_type = OBS_ICON_TYPE_AUDIO_INPUT,
 };
 
 struct obs_source_info coreaudio_output_capture_info = {
@@ -830,4 +831,5 @@ struct obs_source_info coreaudio_output_capture_info = {
 	.update = coreaudio_update,
 	.get_defaults = coreaudio_defaults,
 	.get_properties = coreaudio_output_properties,
+	.icon_type = OBS_ICON_TYPE_AUDIO_OUTPUT,
 };

+ 1 - 0
plugins/mac-capture/mac-display-capture.m

@@ -658,4 +658,5 @@ struct obs_source_info display_capture_info = {
 	.get_defaults = display_capture_defaults,
 	.get_properties = display_capture_properties,
 	.update = display_capture_update,
+	.icon_type = OBS_ICON_TYPE_DESKTOP_CAPTURE,
 };

+ 1 - 0
plugins/mac-capture/mac-window-capture.m

@@ -235,4 +235,5 @@ struct obs_source_info window_capture_info = {
 	.get_defaults = window_capture_defaults,
 	.get_properties = window_capture_properties,
 	.update = window_capture_update,
+	.icon_type = OBS_ICON_TYPE_WINDOW_CAPTURE,
 };

+ 1 - 0
plugins/mac-syphon/syphon.m

@@ -1281,4 +1281,5 @@ struct obs_source_info syphon_info = {
 	.get_height = syphon_get_height,
 	.update = syphon_update,
 	.save = syphon_save,
+	.icon_type = OBS_ICON_TYPE_GAME_CAPTURE,
 };

+ 1 - 0
plugins/obs-ffmpeg/obs-ffmpeg-source.c

@@ -505,4 +505,5 @@ struct obs_source_info ffmpeg_source = {
 	.deactivate = ffmpeg_source_deactivate,
 	.video_tick = ffmpeg_source_tick,
 	.update = ffmpeg_source_update,
+	.icon_type = OBS_ICON_TYPE_MEDIA,
 };

+ 1 - 0
plugins/obs-text/gdiplus/obs-text.cpp

@@ -1042,6 +1042,7 @@ bool obs_module_load(void)
 	si.type = OBS_SOURCE_TYPE_INPUT;
 	si.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW;
 	si.get_properties = get_properties;
+	si.icon_type = OBS_ICON_TYPE_TEXT;
 
 	si.get_name = [](void *) { return obs_module_text("TextGDIPlus"); };
 	si.create = [](obs_data_t *settings, obs_source_t *source) {

+ 1 - 0
plugins/text-freetype2/text-freetype2.c

@@ -52,6 +52,7 @@ static struct obs_source_info freetype2_source_info = {
 	.video_render = ft2_source_render,
 	.video_tick = ft2_video_tick,
 	.get_properties = ft2_source_properties,
+	.icon_type = OBS_ICON_TYPE_TEXT,
 };
 
 static bool plugin_initialized = false;

+ 1 - 0
plugins/vlc-video/vlc-video-source.c

@@ -961,4 +961,5 @@ struct obs_source_info vlc_source_info = {
 	.get_properties = vlcs_properties,
 	.activate = vlcs_activate,
 	.deactivate = vlcs_deactivate,
+	.icon_type = OBS_ICON_TYPE_MEDIA,
 };

+ 1 - 0
plugins/win-capture/duplicator-monitor-capture.c

@@ -323,4 +323,5 @@ struct obs_source_info duplicator_capture_info = {
 	.get_height = duplicator_capture_height,
 	.get_defaults = duplicator_capture_defaults,
 	.get_properties = duplicator_capture_properties,
+	.icon_type = OBS_ICON_TYPE_DESKTOP_CAPTURE,
 };

+ 1 - 0
plugins/win-capture/game-capture.c

@@ -2103,4 +2103,5 @@ struct obs_source_info game_capture_info = {
 	.update = game_capture_update,
 	.video_tick = game_capture_tick,
 	.video_render = game_capture_render,
+	.icon_type = OBS_ICON_TYPE_GAME_CAPTURE,
 };

+ 1 - 0
plugins/win-capture/monitor-capture.c

@@ -243,4 +243,5 @@ struct obs_source_info monitor_capture_info = {
 	.get_height = monitor_capture_height,
 	.get_defaults = monitor_capture_defaults,
 	.get_properties = monitor_capture_properties,
+	.icon_type = OBS_ICON_TYPE_DESKTOP_CAPTURE,
 };

+ 1 - 0
plugins/win-capture/window-capture.c

@@ -259,4 +259,5 @@ struct obs_source_info window_capture_info = {
 	.get_height = wc_height,
 	.get_defaults = wc_defaults,
 	.get_properties = wc_properties,
+	.icon_type = OBS_ICON_TYPE_WINDOW_CAPTURE,
 };

+ 1 - 0
plugins/win-dshow/win-dshow.cpp

@@ -1999,5 +1999,6 @@ void RegisterDShowSource()
 	info.update = UpdateDShowInput;
 	info.get_defaults = GetDShowDefaults;
 	info.get_properties = GetDShowProperties;
+	info.icon_type = OBS_ICON_TYPE_CAMERA;
 	obs_register_source(&info);
 }

+ 1 - 0
plugins/win-ivcam/realsense.cpp

@@ -185,6 +185,7 @@ bool obs_module_load(void)
 	info.get_name = GetIVCamName;
 	info.create = IVCamSource::CreateIVCamSource;
 	info.destroy = DestroyIVCamSource;
+	info.icon_type = OBS_ICON_TYPE_CAMERA;
 	obs_register_source(&info);
 	return true;
 }

+ 2 - 0
plugins/win-wasapi/win-wasapi.cpp

@@ -618,6 +618,7 @@ void RegisterWASAPIInput()
 	info.update = UpdateWASAPISource;
 	info.get_defaults = GetWASAPIDefaultsInput;
 	info.get_properties = GetWASAPIPropertiesInput;
+	info.icon_type = OBS_ICON_TYPE_AUDIO_INPUT;
 	obs_register_source(&info);
 }
 
@@ -634,5 +635,6 @@ void RegisterWASAPIOutput()
 	info.update = UpdateWASAPISource;
 	info.get_defaults = GetWASAPIDefaultsOutput;
 	info.get_properties = GetWASAPIPropertiesOutput;
+	info.icon_type = OBS_ICON_TYPE_AUDIO_OUTPUT;
 	obs_register_source(&info);
 }