歡迎您光臨本站 註冊首頁

如何將 Firefox 熱鍵從 Flash 播放器釋放出來

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  
Firefox 內的 Flash 播放器和其他嵌入式應用程序要求使用自己的掛鉤(hook)獲得鍵盤和滑鼠輸入。多年來,Flash 一直佔用 Firefox 的按鍵事件,使人們無法使用鍵盤進行導航、創建新標籤甚至退出 Flash 焦點。通過本文了解如何創建一個可以與 Firefox 擴展和 cnee 交互的 Perl 程序,幫助您取回鍵盤功能。

Firefox 內的 Flash 播放器和其他嵌入式應用程序要求使用自己的掛鉤(hook)獲得鍵盤和滑鼠輸入。自 2001 年 5 月以來,如果 Flash 佔用 Firefox 的按鍵,那麼您就無法使用鍵盤進行導航、創建標籤甚至退出 Flash 焦點(參見 參考資料 獲得 Mozilla bug No. 78414)。

本文提供的工具和代碼使 Linux® 上運行的 Firefox 能夠在嵌入式 Flash 播放器持有焦點的情況下響應 Ctrl+t(打開新標籤)等熱鍵。使用本文的代碼幫助 Firefox 應用程序重新控制鍵盤。本文並沒有修復底層問題,但是為 Linux 用戶提供了一種針對 Mozilla bug No. 78414 的解決方案。

通過使用 cnee 監視系統的鍵盤事件,並使用 Perl 跟蹤 Firefox 應用程序的狀態,即使在 Flash 播放器持有的焦點的情況下,也可以恢復 Firefox 熱鍵功能。

硬體和軟體需求

Linux 是必需的,還會用到 Firefox V2 或更高版本。需要使用 libXnee 和 cnee 組件監視系統內外的鍵盤事件,並使用 Perl 處理演算法。需要用到某些 Perl 模塊處理 cnee 輸出併發送 X Window System 事件:threads、Thread::Queue、X11:GUITest 和 Time::HiRes。有關這些模塊和 libXnee 軟體包的更多信息,請參閱 參考資料 小節。

儘管本文在 Linux 之上提供實現,但是這裡介紹的一般概念適用於多個操作系統,比如 Microsoft® Windows®。只需要使用其他軟體包替代 cnee 來可靠地輸出系統的鍵盤事件,因為 Firefox 擴展和 Perl 代碼是跨平台的。(如果您是一名聰明的 Windows 應用程序開發人員並且開發出類似本文提供內容的開源補丁,請將您的代碼通過電子郵件發送給我,我們將根據您的代碼改進這篇文章)。

熟悉如何編寫 Firefox 擴展以及安裝 Extension Developer 的 Extension 將很有用處(參見 參考資料)。





創建一個 Firefox 鍵盤報告擴展

要判斷 Flash 播放器何時佔用了 Firefox 熱鍵(比如 Ctrl+t),可通過兩個步驟實現。首先記錄 Firefox 中的地址欄文本何時發生改變。地址欄文本在每打開一個標籤、訪問一個新頁面或顯示不同的標籤時發生變化。其次在整個系統內監視鍵盤事件。如果 cnee 識別出一個鍵盤組合鍵(比如 Ctrl+t),但是最後一次地址欄文本變化發生在 X 秒之前,那麼 Flash 播放器已獲得鍵盤焦點。

Mozilla Developer 的 Center Progress Listeners 頁面給出一個簡單的方法,用於確定地址欄的變化。要實現類似的代碼,從 Google Calendar Encryption 文章下載預構建擴展(參見 參考資料)。提取擴展目標並修改 install.rdf 文件的內容,如下所示。


清單 1. install.rdf
				  <?xml version="1.0"?>  <RDF:RDF xmlns:em="http://www.mozilla.org/2004/em-rdf#"           xmlns:NC="http://home.netscape.com/NC-rdf#"           xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">    <RDF:Description RDF:about="urn:mozilla:install-manifest"                     em:id="[email protected]_IBM.com"                     em:name="flashUngrabber"                     em:version="1.0.0"                     em:creator="Nathan Harrington"                     em:description="flashUngrabber">      <em:targetApplication RDF:resource="rdf:#$9ttCz1"/>    </RDF:Description>    <RDF:Description RDF:about="rdf:#$9ttCz1"                     em:id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"                     em:minVersion="1.5"                     em:maxVersion="3.0.5" />  </RDF:RDF>  

使用清單 2 的內容替換 chrome.manifest 文件。


清單 2. chrome.manifest
				  content flashUngrabber  chrome/content/    overlay chrome://browser/content/browser.xul  \    chrome://flashUngrabber/content/overlay.xul    locale  flashUngrabber  en-US   chrome/locale/en-US/    skin    flashUngrabber  classic/1.0     chrome/skin/  style   chrome://global/content/customizeToolbar.xul  \    chrome://flashUngrabber/skin/overlay.css  

注意,反斜杠(\)只是用來續行,因此不應放到文件中。如上所述替換了擴展元數據后,刪除 chrome/content/overlay.js 文件,插入下面所示的內容。


清單 3. overlay.js myExt_urlBarListener 函數
				  //overlay.js for flash "ungrabber" borrows heavily from  //https://developer.mozilla.org/en/Code_snippets/Progress_Listeners    var myExt_urlBarListener = {    QueryInterface: function(aIID)    {     if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||         aIID.equals(Components.interfaces.nsISupportsWeakReference) ||         aIID.equals(Components.interfaces.nsISupports))       return this;     throw Components.results.NS_NOINTERFACE;    },      // switching through tabs changes the location bar    onLocationChange: function(aProgress, aRequest, aURI)    {      myExtension.updateFile();    },    };  

上面的內容基本上是直接從 Progress Listeners 示例複製過來的,myExt_urlBarListener 函數查詢可用的介面以確保 onLocationChange 功能是可用的。每當地址欄發生變化時,myExtension.updateFile() 函數將得到調用。清單 4 展示了 updateFile 和 init/unint 函數,這兩個函數被添加到 overlay.js 文件的底部。


清單 4. overlay.js myExtension 函數
				  var myExtension = {      init: function() {      // add the listener on web page loaded      gBrowser.addProgressListener(myExt_urlBarListener,          Components.interfaces.nsIWebProgress.NOTIFY_STATE_DOCUMENT);    },      uninit: function() {      // remove the listener when page is unloaded      gBrowser.removeProgressListener(myExt_urlBarListener);    },      updateFile: function() {        // write the epoch seconds + precision when the location bar was changed      locTime = new Date().getTime();        var fileOut = Components.classes["@mozilla.org/file/local;1"]                          .createInstance(Components.interfaces.nsILocalFile);      fileOut.initWithPath("/tmp/locationBarChange");      var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]                              .createInstance(Components.interfaces.nsIFileOutputStream);      foStream.init(fileOut, 0x02 | 0x08 | 0x20, 0666, 0);      foStream.write(locTime.toString(), locTime.toString().length);      foStream.close();    }    };  

通過向 /tmp/locationBarChange 文件寫入最後更新時間(UNIX® 之後的幾秒內),可以實現簡單的進程間通信。添加清單 5 中的代碼行確保擴展被正確載入和卸載。


清單 5. overlay.js addEventListeners
				  window.addEventListener("load", function() {myExtension.init()}, false);  window.addEventListener("unload", function() {myExtension.uninit()}, false);  

為了完成擴展更新,需要用以下的內容替換 chrome/content/overlay.xul 文件中的內容。


清單 6. overlay.xul
				  <?xml version="1.0"?>  <?xml-stylesheet href="chrome://quickgooglecal/skin/overlay.css" type="text/css"?>  <!DOCTYPE overlay SYSTEM "chrome://quickgooglecal/locale/overlay.dtd">  <overlay id="helloworld-overlay"           xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">    <script src="overlay.js"/>  </overlay>  

擴展現在已經準備好載入到 Firefox 了。一個簡單方法是切換到擴展的根目錄併發出命令 zip -r flashUngrabber.xpi *,以創建一個 xpi。在 Firefox 載入 xpi(也可從 下載 小節獲得)並重新啟動瀏覽器。

重新載入完成後,發出以下命令 perl -e 'while(1){print `cat /tmp/locationBarChange` . "\n";sleep(1)}'。將 Firefox 窗口變為可查看,並在不同的標籤中創建和載入頁面。您將發現如果修改標籤、載入不同的頁面或修改地址欄中文本,每隔一秒就會輸出不斷增加的數字。





flashUngrabber.pl 程序

現在已經確定了 Firefox 何時會響應一個熱鍵按下動作,現在我們可以編寫一個程序,監視它應該響應而實際上沒有響應的情況。清單 7 所示的 flashUngrabber.pl 程序可以實現這個過程。


清單 7. flashUngrabber.pl 頭部
				  #!/usr/bin/perl -w  # flashUngrabber.pl - monitor keyboard events, send firefox key combos  use strict;    use X11::GUITest qw( :ALL );                # make sure firefox app has focus  use Time::HiRes  qw( gettimeofday usleep ); # sub second timings  use threads;                                # for asynchronous pipe reads  use Thread::Queue;                          # for asynchronous pipe reads      my $padLen = 16;       # epoch significant digits  my $foundControl = 0;  # loop control variable  my $setLocalTime = 0;  # last recorded synthetic event   my %keys = ();         # key codes and times      my ($currWind) = FindWindowLike( 'Mozilla Firefox' );  die "can't find Mozilla Firefox window\n" if ( !$currWind );  

上面的代碼包含了必要的模塊並定義了變數。在 Firefox 應用程序名稱中顯示的某些 “特殊” 字元,比如 “—”(印刷中的 “m-dash”),可能會引起 FindWindowLike 函數失敗。如果 flashUngrabber.pl 無法找到您的 Firefox 應用程序 ID 的話,嘗試載入不同的頁面或切換到不同的標籤。清單 8 繼續展示了程序的設置。


清單 8. 繼續展示 flashUngrabber.pl
				  my $cneeCmd  = qq{ cnee --record --keyboard | };  my $pipeCnee = createPipe( $cneeCmd ) or die "cannot create cnee pipe\n";    $keys{ "ctrl-t" }{ cneeCode } = '0,2,0,0,0,28';  $keys{ "ctrl-t" }{ sendKeys } = '^(t)';  $keys{ "ctrl-t" }{ event } = 0;    $keys{ "ctrl-w" }{ cneeCode } = '0,2,0,0,0,25';  $keys{ "ctrl-w" }{ sendKeys } = '^(w)';  $keys{ "ctrl-w" }{ event } = 0;  

在鍵盤監視模式下創建了到 cnee 程序的連接后,將在 %keys 散列中定義特殊的鍵碼。稍後將在程序中搜索這些鍵,並且它們的最後記錄時間將作為 “event” 散列元素的值存儲。清單 9 展示了主程序循環的開始部分。


清單 9. flashUngrabber.pl 主程序循環的開始部分
				  while( 1 )  {    # read all data off the cnee output queue, process each line.  cnee data     # needs to be control first, then the very next line be the key like: ctlr-t    my $cneeData =  "";    while( $pipeCnee->pending ){ $cneeData .= $pipeCnee->dequeue or next }      for my $line ( split "\n", $cneeData )    {      if( $foundControl == 1 )      {         $foundControl = 0;        for my $name( keys %keys )        {           next unless ( $line =~ /$keys{$name}{"cneeCode"}/ );          $keys{$name}{"event"} = getTimeStr();          }#for each key        }#if control pressed        if( ($line =~ /0,2,0,0,0,37/) || ($line =~ /0,2,0,0,0,109/) )      {        # control pressed        $foundControl = 1;        }elsif( ($line =~ /0,3,0,0,0,37/) || ($line =~ /0,3,0,0,0,109/) )      {        #control released        $foundControl = 0;        }#if control pressed      }#for each line  

考慮到系統載入和各種其他因素,鍵盤事件在到達 cnee 程序之前可以由 Firefox 處理。與此相反,cnee 可能會在 Firefox 有機會處理擊鍵事件之前輸出鍵盤事件。這種特殊的無限循環和微休眠(micro-sleep)方法目的是允許 cnee 和 firefox 有機會處理事件,同時保持足夠的 UI 性能。

在每次執行主循環時,cnee 輸出(如果有的話)將進行處理以查找控制鍵。如果控制鍵被找到,並且在 %keys 散列中指定了下一個按下的鍵,那麼該鍵的事件時間將被記錄下來。清單 10 展示了讀取 cnee 事件後主處理循環的後面部分。


清單 10. 繼續 flashUngrabber.pl 主程序循環
				    my $curTime = getTimeStr();      for my $name ( keys %keys )    {       # require the event to have .5 second to bubble up to cnee      next unless ( ($curTime - $keys{$name}{"event"} ) > 500000 &&                    $keys{$name}{"event"} != 0 );        # reset the event time      $keys{$name}{"event"} = 0;        next unless ( $currWind == GetInputFocus() ); # skip if firefox not focused        next unless( -e "/tmp/locationBarChange" );   # skip if no address bar data        open( FFOUT, "/tmp/locationBarChange" ) or die "no location bar file";        my $ffTime = <FFOUT>;      close(FFOUT);        # determine if firefox has written a location bar change recently       $ffTime = $ffTime . "0" x ( $padLen - length($ffTime) );      if( $ffTime > $setLocalTime ){ $setLocalTime = $ffTime }        # if it's been more than two seconds since last event      next unless( ($curTime - $setLocalTime)  > 2000000 );  

將處理每一個鍵碼,以確定檢測到事件之後是否至少經過了半秒時間。如果 Firefox 當前持有焦點,/tmp/locationBarChange 文件將退出,並且自最後一個合成事件發出后至少過了 2 秒,下面繼續顯示處理。


清單 11. flashUngrabber.pl 主程序循環的結束部分
				      # record original mouse position      my($origX,$origY) = GetMousePos();      my( $x, $y, $width, $height ) = GetWindowPos( $currWind );        # highly subjective, clicks in google search box on default firefox       # installation.  Sleeps are ugly, but help ensure inputs trigger       # correctly on a heavily loaded machine      ClickWindow( $currWind, $width-150, $height-($height-40) );      usleep(200000);      SendKeys( $keys{$name}{"sendKeys"} );      usleep(200000);      MoveMouseAbs( $origX, $origY );      usleep(200000);      $setLocalTime = $curTime;      }#for each key combo to look for      usleep(100000); # wait a tenth of a second    }#while main loop  

此時,需要發送一個合成事件,因此將記錄當前的滑鼠位置和窗口位置。此時僅僅發送 Ctrl+t 並不能獲得想要的行為,因為 Flash 播放器將捕獲按鍵事件。最可靠的方法是移動滑鼠,單擊窗口(例如,在 Google Search 框內)併發送 Ctrl+t,確保擊鍵事件由 Firefox 處理。在按鍵前將滑鼠移回初始位置可以確保將滑鼠放回您離開它的位置。

超負荷的系統載入和各種其他因素會影響 Firefox 接收滑鼠移動和鍵盤事件。減少或去除 usleep 函數調用可以提高發送按鍵事件的速度,但是如果系統響應變慢的話會引起其他問題。

如果有一個非標準的 Firefox 工具欄設置或希望確保合成單擊事件被發送到瀏覽器中的不同位置,那麼可能需要修改 ClickWindow 坐標。清單 12 展示了 createPipe 和 getTimeStr 支持子常式。


清單 12. flashUngrabber.pl 子常式
				  sub createPipe  {     my $cmd = shift;    my $queue = new Thread::Queue;    async{        my $pid = open my $pipe, $cmd or die $!;        $queue->enqueue( 
Firefox 內的 Flash 播放器和其他嵌入式應用程序要求使用自己的掛鉤(hook)獲得鍵盤和滑鼠輸入。多年來,Flash 一直佔用 Firefox 的按鍵事件,使人們無法使用鍵盤進行導航、創建新標籤甚至退出 Flash 焦點。通過本文了解如何創建一個可以與 Firefox 擴展和 cnee 交互的 Perl 程序,幫助您取回鍵盤功能。

Firefox 內的 Flash 播放器和其他嵌入式應用程序要求使用自己的掛鉤(hook)獲得鍵盤和滑鼠輸入。自 2001 年 5 月以來,如果 Flash 佔用 Firefox 的按鍵,那麼您就無法使用鍵盤進行導航、創建標籤甚至退出 Flash 焦點(參見 參考資料 獲得 Mozilla bug No. 78414)。

本文提供的工具和代碼使 Linux® 上運行的 Firefox 能夠在嵌入式 Flash 播放器持有焦點的情況下響應 Ctrl+t(打開新標籤)等熱鍵。使用本文的代碼幫助 Firefox 應用程序重新控制鍵盤。本文並沒有修復底層問題,但是為 Linux 用戶提供了一種針對 Mozilla bug No. 78414 的解決方案。

通過使用 cnee 監視系統的鍵盤事件,並使用 Perl 跟蹤 Firefox 應用程序的狀態,即使在 Flash 播放器持有的焦點的情況下,也可以恢復 Firefox 熱鍵功能。

硬體和軟體需求

Linux 是必需的,還會用到 Firefox V2 或更高版本。需要使用 libXnee 和 cnee 組件監視系統內外的鍵盤事件,並使用 Perl 處理演算法。需要用到某些 Perl 模塊處理 cnee 輸出併發送 X Window System 事件:threads、Thread::Queue、X11:GUITest 和 Time::HiRes。有關這些模塊和 libXnee 軟體包的更多信息,請參閱 參考資料 小節。

儘管本文在 Linux 之上提供實現,但是這裡介紹的一般概念適用於多個操作系統,比如 Microsoft® Windows®。只需要使用其他軟體包替代 cnee 來可靠地輸出系統的鍵盤事件,因為 Firefox 擴展和 Perl 代碼是跨平台的。(如果您是一名聰明的 Windows 應用程序開發人員並且開發出類似本文提供內容的開源補丁,請將您的代碼通過電子郵件發送給我,我們將根據您的代碼改進這篇文章)。

熟悉如何編寫 Firefox 擴展以及安裝 Extension Developer 的 Extension 將很有用處(參見 參考資料)。





創建一個 Firefox 鍵盤報告擴展

要判斷 Flash 播放器何時佔用了 Firefox 熱鍵(比如 Ctrl+t),可通過兩個步驟實現。首先記錄 Firefox 中的地址欄文本何時發生改變。地址欄文本在每打開一個標籤、訪問一個新頁面或顯示不同的標籤時發生變化。其次在整個系統內監視鍵盤事件。如果 cnee 識別出一個鍵盤組合鍵(比如 Ctrl+t),但是最後一次地址欄文本變化發生在 X 秒之前,那麼 Flash 播放器已獲得鍵盤焦點。

Mozilla Developer 的 Center Progress Listeners 頁面給出一個簡單的方法,用於確定地址欄的變化。要實現類似的代碼,從 Google Calendar Encryption 文章下載預構建擴展(參見 參考資料)。提取擴展目標並修改 install.rdf 文件的內容,如下所示。


清單 1. install.rdf
				  <?xml version="1.0"?>  <RDF:RDF xmlns:em="http://www.mozilla.org/2004/em-rdf#"           xmlns:NC="http://home.netscape.com/NC-rdf#"           xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">    <RDF:Description RDF:about="urn:mozilla:install-manifest"                     em:id="[email protected]_IBM.com"                     em:name="flashUngrabber"                     em:version="1.0.0"                     em:creator="Nathan Harrington"                     em:description="flashUngrabber">      <em:targetApplication RDF:resource="rdf:#$9ttCz1"/>    </RDF:Description>    <RDF:Description RDF:about="rdf:#$9ttCz1"                     em:id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"                     em:minVersion="1.5"                     em:maxVersion="3.0.5" />  </RDF:RDF>  

使用清單 2 的內容替換 chrome.manifest 文件。


清單 2. chrome.manifest
				  content flashUngrabber  chrome/content/    overlay chrome://browser/content/browser.xul  \    chrome://flashUngrabber/content/overlay.xul    locale  flashUngrabber  en-US   chrome/locale/en-US/    skin    flashUngrabber  classic/1.0     chrome/skin/  style   chrome://global/content/customizeToolbar.xul  \    chrome://flashUngrabber/skin/overlay.css  

注意,反斜杠(\)只是用來續行,因此不應放到文件中。如上所述替換了擴展元數據后,刪除 chrome/content/overlay.js 文件,插入下面所示的內容。


清單 3. overlay.js myExt_urlBarListener 函數
				  //overlay.js for flash "ungrabber" borrows heavily from  //https://developer.mozilla.org/en/Code_snippets/Progress_Listeners    var myExt_urlBarListener = {    QueryInterface: function(aIID)    {     if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||         aIID.equals(Components.interfaces.nsISupportsWeakReference) ||         aIID.equals(Components.interfaces.nsISupports))       return this;     throw Components.results.NS_NOINTERFACE;    },      // switching through tabs changes the location bar    onLocationChange: function(aProgress, aRequest, aURI)    {      myExtension.updateFile();    },    };  

上面的內容基本上是直接從 Progress Listeners 示例複製過來的,myExt_urlBarListener 函數查詢可用的介面以確保 onLocationChange 功能是可用的。每當地址欄發生變化時,myExtension.updateFile() 函數將得到調用。清單 4 展示了 updateFile 和 init/unint 函數,這兩個函數被添加到 overlay.js 文件的底部。


清單 4. overlay.js myExtension 函數
				  var myExtension = {      init: function() {      // add the listener on web page loaded      gBrowser.addProgressListener(myExt_urlBarListener,          Components.interfaces.nsIWebProgress.NOTIFY_STATE_DOCUMENT);    },      uninit: function() {      // remove the listener when page is unloaded      gBrowser.removeProgressListener(myExt_urlBarListener);    },      updateFile: function() {        // write the epoch seconds + precision when the location bar was changed      locTime = new Date().getTime();        var fileOut = Components.classes["@mozilla.org/file/local;1"]                          .createInstance(Components.interfaces.nsILocalFile);      fileOut.initWithPath("/tmp/locationBarChange");      var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]                              .createInstance(Components.interfaces.nsIFileOutputStream);      foStream.init(fileOut, 0x02 | 0x08 | 0x20, 0666, 0);      foStream.write(locTime.toString(), locTime.toString().length);      foStream.close();    }    };  

通過向 /tmp/locationBarChange 文件寫入最後更新時間(UNIX® 之後的幾秒內),可以實現簡單的進程間通信。添加清單 5 中的代碼行確保擴展被正確載入和卸載。


清單 5. overlay.js addEventListeners
				  window.addEventListener("load", function() {myExtension.init()}, false);  window.addEventListener("unload", function() {myExtension.uninit()}, false);  

為了完成擴展更新,需要用以下的內容替換 chrome/content/overlay.xul 文件中的內容。


清單 6. overlay.xul
				  <?xml version="1.0"?>  <?xml-stylesheet href="chrome://quickgooglecal/skin/overlay.css" type="text/css"?>  <!DOCTYPE overlay SYSTEM "chrome://quickgooglecal/locale/overlay.dtd">  <overlay id="helloworld-overlay"           xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">    <script src="overlay.js"/>  </overlay>  

擴展現在已經準備好載入到 Firefox 了。一個簡單方法是切換到擴展的根目錄併發出命令 zip -r flashUngrabber.xpi *,以創建一個 xpi。在 Firefox 載入 xpi(也可從 下載 小節獲得)並重新啟動瀏覽器。

重新載入完成後,發出以下命令 perl -e 'while(1){print `cat /tmp/locationBarChange` . "\n";sleep(1)}'。將 Firefox 窗口變為可查看,並在不同的標籤中創建和載入頁面。您將發現如果修改標籤、載入不同的頁面或修改地址欄中文本,每隔一秒就會輸出不斷增加的數字。





flashUngrabber.pl 程序

現在已經確定了 Firefox 何時會響應一個熱鍵按下動作,現在我們可以編寫一個程序,監視它應該響應而實際上沒有響應的情況。清單 7 所示的 flashUngrabber.pl 程序可以實現這個過程。


清單 7. flashUngrabber.pl 頭部
				  #!/usr/bin/perl -w  # flashUngrabber.pl - monitor keyboard events, send firefox key combos  use strict;    use X11::GUITest qw( :ALL );                # make sure firefox app has focus  use Time::HiRes  qw( gettimeofday usleep ); # sub second timings  use threads;                                # for asynchronous pipe reads  use Thread::Queue;                          # for asynchronous pipe reads      my $padLen = 16;       # epoch significant digits  my $foundControl = 0;  # loop control variable  my $setLocalTime = 0;  # last recorded synthetic event   my %keys = ();         # key codes and times      my ($currWind) = FindWindowLike( 'Mozilla Firefox' );  die "can't find Mozilla Firefox window\n" if ( !$currWind );  

上面的代碼包含了必要的模塊並定義了變數。在 Firefox 應用程序名稱中顯示的某些 “特殊” 字元,比如 “—”(印刷中的 “m-dash”),可能會引起 FindWindowLike 函數失敗。如果 flashUngrabber.pl 無法找到您的 Firefox 應用程序 ID 的話,嘗試載入不同的頁面或切換到不同的標籤。清單 8 繼續展示了程序的設置。


清單 8. 繼續展示 flashUngrabber.pl
				  my $cneeCmd  = qq{ cnee --record --keyboard | };  my $pipeCnee = createPipe( $cneeCmd ) or die "cannot create cnee pipe\n";    $keys{ "ctrl-t" }{ cneeCode } = '0,2,0,0,0,28';  $keys{ "ctrl-t" }{ sendKeys } = '^(t)';  $keys{ "ctrl-t" }{ event } = 0;    $keys{ "ctrl-w" }{ cneeCode } = '0,2,0,0,0,25';  $keys{ "ctrl-w" }{ sendKeys } = '^(w)';  $keys{ "ctrl-w" }{ event } = 0;  

在鍵盤監視模式下創建了到 cnee 程序的連接后,將在 %keys 散列中定義特殊的鍵碼。稍後將在程序中搜索這些鍵,並且它們的最後記錄時間將作為 “event” 散列元素的值存儲。清單 9 展示了主程序循環的開始部分。


清單 9. flashUngrabber.pl 主程序循環的開始部分
				  while( 1 )  {    # read all data off the cnee output queue, process each line.  cnee data     # needs to be control first, then the very next line be the key like: ctlr-t    my $cneeData =  "";    while( $pipeCnee->pending ){ $cneeData .= $pipeCnee->dequeue or next }      for my $line ( split "\n", $cneeData )    {      if( $foundControl == 1 )      {         $foundControl = 0;        for my $name( keys %keys )        {           next unless ( $line =~ /$keys{$name}{"cneeCode"}/ );          $keys{$name}{"event"} = getTimeStr();          }#for each key        }#if control pressed        if( ($line =~ /0,2,0,0,0,37/) || ($line =~ /0,2,0,0,0,109/) )      {        # control pressed        $foundControl = 1;        }elsif( ($line =~ /0,3,0,0,0,37/) || ($line =~ /0,3,0,0,0,109/) )      {        #control released        $foundControl = 0;        }#if control pressed      }#for each line  

考慮到系統載入和各種其他因素,鍵盤事件在到達 cnee 程序之前可以由 Firefox 處理。與此相反,cnee 可能會在 Firefox 有機會處理擊鍵事件之前輸出鍵盤事件。這種特殊的無限循環和微休眠(micro-sleep)方法目的是允許 cnee 和 firefox 有機會處理事件,同時保持足夠的 UI 性能。

在每次執行主循環時,cnee 輸出(如果有的話)將進行處理以查找控制鍵。如果控制鍵被找到,並且在 %keys 散列中指定了下一個按下的鍵,那麼該鍵的事件時間將被記錄下來。清單 10 展示了讀取 cnee 事件後主處理循環的後面部分。


清單 10. 繼續 flashUngrabber.pl 主程序循環
				    my $curTime = getTimeStr();      for my $name ( keys %keys )    {       # require the event to have .5 second to bubble up to cnee      next unless ( ($curTime - $keys{$name}{"event"} ) > 500000 &&                    $keys{$name}{"event"} != 0 );        # reset the event time      $keys{$name}{"event"} = 0;        next unless ( $currWind == GetInputFocus() ); # skip if firefox not focused        next unless( -e "/tmp/locationBarChange" );   # skip if no address bar data        open( FFOUT, "/tmp/locationBarChange" ) or die "no location bar file";        my $ffTime = <FFOUT>;      close(FFOUT);        # determine if firefox has written a location bar change recently       $ffTime = $ffTime . "0" x ( $padLen - length($ffTime) );      if( $ffTime > $setLocalTime ){ $setLocalTime = $ffTime }        # if it's been more than two seconds since last event      next unless( ($curTime - $setLocalTime)  > 2000000 );  

將處理每一個鍵碼,以確定檢測到事件之後是否至少經過了半秒時間。如果 Firefox 當前持有焦點,/tmp/locationBarChange 文件將退出,並且自最後一個合成事件發出后至少過了 2 秒,下面繼續顯示處理。


清單 11. flashUngrabber.pl 主程序循環的結束部分
				      # record original mouse position      my($origX,$origY) = GetMousePos();      my( $x, $y, $width, $height ) = GetWindowPos( $currWind );        # highly subjective, clicks in google search box on default firefox       # installation.  Sleeps are ugly, but help ensure inputs trigger       # correctly on a heavily loaded machine      ClickWindow( $currWind, $width-150, $height-($height-40) );      usleep(200000);      SendKeys( $keys{$name}{"sendKeys"} );      usleep(200000);      MoveMouseAbs( $origX, $origY );      usleep(200000);      $setLocalTime = $curTime;      }#for each key combo to look for      usleep(100000); # wait a tenth of a second    }#while main loop  

此時,需要發送一個合成事件,因此將記錄當前的滑鼠位置和窗口位置。此時僅僅發送 Ctrl+t 並不能獲得想要的行為,因為 Flash 播放器將捕獲按鍵事件。最可靠的方法是移動滑鼠,單擊窗口(例如,在 Google Search 框內)併發送 Ctrl+t,確保擊鍵事件由 Firefox 處理。在按鍵前將滑鼠移回初始位置可以確保將滑鼠放回您離開它的位置。

超負荷的系統載入和各種其他因素會影響 Firefox 接收滑鼠移動和鍵盤事件。減少或去除 usleep 函數調用可以提高發送按鍵事件的速度,但是如果系統響應變慢的話會引起其他問題。

如果有一個非標準的 Firefox 工具欄設置或希望確保合成單擊事件被發送到瀏覽器中的不同位置,那麼可能需要修改 ClickWindow 坐標。清單 12 展示了 createPipe 和 getTimeStr 支持子常式。


清單 12. flashUngrabber.pl 子常式
___FCKpd___11

createPipe 子常式實現了從 cnee 程序的非阻塞管道讀取,而 getTimeStr 提供了一個長度一致的高精度時間串。將以上清單保存為 flashUngrabber.pl 程序並使用以下命令運行程序:perl flashUngrabber.pl。





使用

通過載入 Flash 內容播放器測試您的配置,比如 YouTube 視頻。如果單擊 Flash 播放器 — 比如音量控制 — 並按下 Ctrl+t,您將看到滑鼠將移動到 Google 搜索框,創建了一個新標籤,然後滑鼠返回到其原來的位置。





結束語

通過使用本文提供的代碼和工具,您可以從 Flash 手中重新奪回最喜愛的 Firefox 熱鍵功能。考慮向 flashUngrabber.pl 程序添加 cnee 鍵碼,實現更進一步的鍵盤導航,比如按下 Ctrl+tab 將移動到下一個標籤,或按下 Ctrl+l 將訪問地址欄。從 Flash 播放器取回 PgUp 和 PgDn 鍵來滾動整個頁面,或添加 cnee --record --mouse 選項重新支持滾輪。(責任編輯:A6)

) while <$pipe>; $queue->enqueue( undef ); }->detach; #detach causes the threads to be silently terminated on exit (sometimes) return $queue; }#createPipe sub getTimeStr { # i suppose the idea of not providing standard length time strings makes # sense... somewhere, this is not one of those times my ($seconds, $microseconds) = gettimeofday; my $text = "$seconds$microseconds"; return( $text . "0" x ($padLen - length($text)) ); }#getTimeStr

createPipe 子常式實現了從 cnee 程序的非阻塞管道讀取,而 getTimeStr 提供了一個長度一致的高精度時間串。將以上清單保存為 flashUngrabber.pl 程序並使用以下命令運行程序:perl flashUngrabber.pl。





使用

通過載入 Flash 內容播放器測試您的配置,比如 YouTube 視頻。如果單擊 Flash 播放器 — 比如音量控制 — 並按下 Ctrl+t,您將看到滑鼠將移動到 Google 搜索框,創建了一個新標籤,然後滑鼠返回到其原來的位置。





結束語

通過使用本文提供的代碼和工具,您可以從 Flash 手中重新奪回最喜愛的 Firefox 熱鍵功能。考慮向 flashUngrabber.pl 程序添加 cnee 鍵碼,實現更進一步的鍵盤導航,比如按下 Ctrl+tab 將移動到下一個標籤,或按下 Ctrl+l 將訪問地址欄。從 Flash 播放器取回 PgUp 和 PgDn 鍵來滾動整個頁面,或添加 cnee --record --mouse 選項重新支持滾輪。(責任編輯:A6)



[火星人 ] 如何將 Firefox 熱鍵從 Flash 播放器釋放出來已經有599次圍觀

http://coctec.com/docs/linux/show-post-68981.html