如何将 Firefox 热键从 Flash 播放器释放出来

来源:developerWorks 中国 作者:Nathan Harrington
  
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="flashUngrabber_cnnew1@devWorks_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="flashUngrabber_cnnew1@devWorks_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)


时间:2009-02-04 11:13 来源:developerWorks 中国 作者:Nathan Harrington 原文链接

好文,顶一下
(1)
100%
文章真差,踩一下
(0)
0%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量