obsproject / obs-browser

CEF-based OBS Studio browser plugin
GNU General Public License v2.0
771 stars 218 forks source link

Copy, Paste & Select All Hotkeys not working in Browser Source Interact & Docks (macOS) #365

Open dustindmiller opened 2 years ago

dustindmiller commented 2 years ago

Operating System Info

macOS 11

Other OS

No response

OBS Studio Version

27.2.4

OBS Studio Version (Other)

No response

OBS Studio Log URL

All

OBS Studio Crash Log URL

No response

Expected Behavior

Using CMD+C, CMD+V & CMD+A should copy, paste and select all respectively.

Current Behavior

Using mouse and contextual menu is the only way to currently do this.

Steps to Reproduce

  1. Place cursor in browser source interact or dock.
  2. Try hotkey (example CMD+A)
  3. Does not function (does not select all)
  4. Same for all other aforementioned commands.

Anything else we should know?

No response

WizardCM commented 2 years ago

Note: this was split from #238.

Honestly, this is pretty much expected behaviour. The way hotkeys work on macOS as an operating system is that whatever is in the menu bar takes priority.

To properly fix this, we'd have to ensure OBS knows what context the user is in, and modify the Edit menu to replace the actions of copy/paste/select all with their CEF counterparts.

Unfortunately, I don't know how viable/possible this is. I know CEF in general is very bad at notifying the parent application (in this case OBS) whether it's focused.

The good(?) news is the API to perform the actions themselves does exist as part of CefFrame(), including

jhennig3 commented 1 year ago
  1. Set up OBS Studio and OBS Browser Source on macOS:

  2. Modify the OBS Browser Source plugin:

    • Navigate to the obs-browser plugin directory.
    • Open the obs-browser-source.cpp file in a text editor or IDE.
  3. Define the custom Edit menu:

    • Inside the obs-browser-source.cpp file, locate the browser_source_create function.
    • In this function, create a new instance of CefMenuModel to represent the custom Edit menu:
      CefRefPtr<CefMenuModel> edit_menu = CefMenuModel::CreateMenuModel();
    • Add custom items to the menu using the AddItem method. For example:
      edit_menu->AddItem(1, "Copy");
      edit_menu->AddItem(2, "Paste");
      edit_menu->AddItem(3, "Select All");
    • Set the new edit_menu as a submenu for the browser source's context menu:
      browser_source->context_menu()->AddSubMenu(4, "Edit", edit_menu);
  4. Handle user input for the new menu item:

    • Create a new class that inherits from CefMenuModelDelegate and override the ExecuteCommand method to handle the custom menu commands:

      class EditMenuDelegate : public CefMenuModelDelegate {
      public:
      virtual void ExecuteCommand(CefRefPtr<CefMenuModel> menu_model,
                                 int command_id,
                                 CefEventHandle event) OVERRIDE {
       // Handle the custom menu commands here.
      }
      
      IMPLEMENT_REFCOUNTING(EditMenuDelegate);
      };
    • In the browser_source_create function, create a new instance of the EditMenuDelegate class and set it as the delegate for the edit_menu:
      CefRefPtr<EditMenuDelegate> edit_menu_delegate = new EditMenuDelegate();
      edit_menu->SetDelegate(edit_menu_delegate.get());
  5. Trigger the corresponding CEF actions:

    • In the ExecuteCommand method of the EditMenuDelegate class, use the command_id parameter to determine which action to perform. For example:

      void ExecuteCommand(CefRefPtr<CefMenuModel> menu_model,
                       int command_id,
                       CefEventHandle event) OVERRIDE {
      CefRefPtr<CefBrowser> browser = ...; // Get the CEF browser instance from the OBS browser source.
      
      switch (command_id) {
       case 1: // Copy
         browser->GetFocusedFrame()->Copy();
         break;
       case 2: // Paste
         browser->GetFocusedFrame()->Paste();
         break;
       case 3: // Select All
         browser->GetFocusedFrame()->SelectAll();
         break;
       default:
         break;
      }
      }
  6. Compile and test:

    • Rebuild the OBS Browser Source plugin with your changes.
    • Test the new custom Edit menu in OBS Studio, ensuring that the custom copy, paste, and select all commands work as expected.
jhennig3 commented 1 year ago

The OBS Browser plugin uses CEF internally, but it doesn't expose the source code for the CEF part in a .cpp file. Instead, you'll have to modify the JavaScript code that communicates with the plugin to create a custom context menu with the desired functionality.

To create a custom Edit menu for the OBS Browser Source, follow these steps:

Set up a new local HTML file that will be used as the Browser Source:

Create a new HTML file with the following basic structure: php Copy code <!DOCTYPE html>

Custom Edit Menu

Save the file locally, e.g., as custom_edit_menu.html. Add an event listener for the context menu to the JavaScript code in the HTML file:

javascript Copy code window.addEventListener('contextmenu', (event) => { event.preventDefault(); // Code to create and display the custom context menu will go here. }); Create the custom Edit menu using JavaScript:

Inside the event listener, create an HTML element for the custom context menu: javascript Copy code const contextMenu = document.createElement('div'); contextMenu.style.position = 'fixed'; contextMenu.style.left = ${event.clientX}px; contextMenu.style.top = ${event.clientY}px; contextMenu.style.backgroundColor = '#f8f8f8'; contextMenu.style.border = '1px solid #ccc'; contextMenu.style.padding = '8px'; contextMenu.style.zIndex = 9999; Create menu items for copy, paste, and select all, and add them to the context menu: javascript Copy code const copyItem = document.createElement('button'); copyItem.textContent = 'Copy'; copyItem.onclick = () => { document.execCommand('copy'); document.body.removeChild(contextMenu); }; contextMenu.appendChild(copyItem);

const pasteItem = document.createElement('button'); pasteItem.textContent = 'Paste'; pasteItem.onclick = () => { document.execCommand('paste'); document.body.removeChild(contextMenu); }; contextMenu.appendChild(pasteItem);

const selectAllItem = document.createElement('button'); selectAllItem.textContent = 'Select All'; selectAllItem.onclick = () => { document.execCommand('selectAll'); document.body.removeChild(contextMenu); }; contextMenu.appendChild(selectAllItem); Display the custom context menu and handle hiding it:

Add the context menu to the document body: javascript Copy code document.body.appendChild(contextMenu); Add an event listener to hide the context menu when clicking outside of it: javascript Copy code window.addEventListener('click', () => { if (document.body.contains(contextMenu)) { document.body.removeChild(contextMenu); } }); Set up OBS Studio and the Browser Source:

Open OBS Studio. Create a new Browser Source. In the Browser Source properties, set the local file as the source, e.g., custom_edit_menu.html. With these modifications, you should have a custom context menu with copy, paste, and select all commands in the OBS Browser Source. Note that this approach uses JavaScript's execCommand function, which is now deprecated. You may need to use the Clipboard API.

jhennig3 commented 1 year ago
  1. Set up a new local HTML file that will be used as the Browser Source:

    • Create a new HTML file with the following basic structure:
      <!DOCTYPE html>
      <html>
      <head>
      <title>Custom Edit Menu</title>
      <style>
       /* Add any required CSS here */
      </style>
      </head>
      <body>
      <!-- Add your page content here -->
      <script>
       // Add JavaScript code here
      </script>
      </body>
      </html>
    • Save the file locally, e.g., as custom_edit_menu.html.
  2. Add an event listener for the context menu to the JavaScript code in the HTML file: window.addEventListener('contextmenu', (event) => { event.preventDefault(); // Code to create and display the custom context menu will go here. });

  3. Create the custom Edit menu using JavaScript:

    • Inside the event listener, create an HTML element for the custom context menu:
      const contextMenu = document.createElement('div');
      contextMenu.style.position = 'fixed';
      contextMenu.style.left = `${event.clientX}px`;
      contextMenu.style.top = `${event.clientY}px`;
      contextMenu.style.backgroundColor = '#f8f8f8';
      contextMenu.style.border = '1px solid #ccc';
      contextMenu.style.padding = '8px';
      contextMenu.style.zIndex = 9999;
    • Create menu items for copy, paste, and select all, and add them to the context menu using the Clipboard API:
      
      const copyItem = document.createElement('button');
      copyItem.textContent = 'Copy';
      copyItem.onclick = async () => {
      try {
      const selectedText = window.getSelection().toString();
      await navigator.clipboard.writeText(selectedText);
      } catch (err) {
      console.error('Failed to copy text:', err);
      }
      document.body.removeChild(contextMenu);
      };
      contextMenu.appendChild(copyItem);

    const pasteItem = document.createElement('button'); pasteItem.textContent = 'Paste'; pasteItem.onclick = async () => { try { const clipboardText = await navigator.clipboard.readText(); // Handle pasting the text as needed. } catch (err) { console.error('Failed to paste text:', err); } document.body.removeChild(contextMenu); }; contextMenu.appendChild(pasteItem);

    const selectAllItem = document.createElement('button'); selectAllItem.textContent = 'Select All'; selectAllItem.onclick = () => { document.execCommand('selectAll'); document.body.removeChild(contextMenu); }; contextMenu.appendChild(selectAllItem);

  4. Display the custom context menu and handle hiding it:

    • Add the context menu to the document body:
      document.body.appendChild(contextMenu);
    • Add an event listener to hide the context menu when clicking outside of it:
      window.addEventListener('click', () => {
      if (document.body.contains(contextMenu)) {
      document.body.removeChild(contextMenu);
      }
      });
  5. Set up OBS Studio and the Browser Source:

    • Open OBS Studio.
    • Create a new Browser Source.
    • In the Browser Source properties, set the local file as the source, e.g., custom_edit_menu.html.
Blackn0va commented 1 year ago

is there a possibility that the copy paste is also installed with the hotkey in a next update? The function has otherwise no use for me.

hyperiris commented 6 months ago

This is a CEF related bug.

please try https://glitch.com/edit/#!/async-clipboard-text in web browser and cef demo such as cefclient, you can find that.