home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1995 August / NEBULA.mdf / SourceCode / Puppeteer1.1 / Source / Puppeteer.m < prev    next >
Encoding:
Text File  |  1994-03-23  |  9.2 KB  |  463 lines

  1. /*
  2.  * Puppeteer 1.1
  3.  *
  4.  * Copyright (c) 1994 Primitive Software Ltd.  All rights reserved.
  5.  *
  6.  * Author: Dave Griffiths <dave@prim.demon.co.uk>
  7.  */
  8.  
  9. #import "Puppeteer.h"
  10. #import "WindowInfo.h"
  11. #import "CyberPost.h"
  12. #import <assert.h>
  13.  
  14. @implementation Puppeteer
  15.  
  16. + connectToApp:(const char *)theName launch:(BOOL)launch
  17. /*
  18.  * Return an instance of puppeteer connected to the given app, or nil on failure.
  19.  */
  20. {
  21.     id puppet = [[self alloc] init];
  22.     
  23.     if ([puppet connect:theName launch:launch])
  24.         return puppet;
  25.     else {
  26.         [puppet free];
  27.         return nil;
  28.     }
  29. }
  30.  
  31. - (BOOL)connect:(const char *)theName launch:(BOOL)launch
  32. /*
  33.  * Connect to the specified app. Returns YES on success.
  34.  */
  35. {
  36.     /*
  37.      * Because the WindowInfo object sends postscript commands, we may need
  38.      * a postscript context for it to run in.
  39.      */
  40.     if (!DPSGetCurrentContext()) {
  41.         ctxt = DPSCreateContext(0, 0, NULL, NULL);
  42.         DPSSetContext(ctxt);
  43.     }
  44.  
  45.     appName = NXCopyStringBuffer(theName);
  46.     appSpeaker = [[Speaker alloc] init];
  47.     journalSpeaker = [[Speaker alloc] init];
  48.     if (launch) {
  49.         if ((appPort = NXPortFromName(appName, NULL)) == PORT_NULL)
  50.             return NO;
  51.     } else {
  52.         if ((appPort = NXPortNameLookup(appName, NULL)) == PORT_NULL)
  53.             return NO;
  54.     }
  55.     [appSpeaker setSendPort:appPort];
  56.  
  57.     return YES;
  58. }
  59.  
  60. - free
  61. {
  62.     if (appName)
  63.         free(appName);
  64.     if (appSpeaker)
  65.         [appSpeaker free];
  66.     if (journalSpeaker)
  67.         [journalSpeaker free];
  68.     if (ctxt)
  69.         DPSDestroyContext(ctxt);
  70.     
  71.     return [super free];
  72. }
  73.  
  74. - postEvent:(NXEvent *)event
  75. /*
  76.  * This is the method which actually posts the events to the puppet.
  77.  */
  78. {
  79.     assert(enabled);
  80.     
  81.     [journalSpeaker selectorRPC:
  82.         "_playJournalEventType:x:y:time:flags:window:subtype:miscL0:miscL1:ctxt:"         
  83.         paramTypes:"iddiiiiiii", 
  84.         event->type, 
  85.         event->location.x, 
  86.         event->location.y, 
  87.         event->time, 
  88.         event->flags | 0x80000000, 
  89.         event->window, 
  90.         event->data.compound.subtype, 
  91.         event->data.compound.misc.L[0], 
  92.         event->data.compound.misc.L[1], 
  93.         event->ctxt];
  94.     
  95.     return self;
  96. }
  97.  
  98. - postKeyboardString:(const char *)keyString flags:(int)flags
  99. {
  100.     int i, length = strlen(keyString);
  101.  
  102.     for (i=0; i<length; i++) {
  103.         [self postKeyboardEvent:NX_KEYDOWN window:NX_KEYWINDOW flags:flags 
  104.             charCode:keyString[i]];
  105.         [self postKeyboardEvent:NX_KEYUP window:NX_KEYWINDOW flags:flags 
  106.             charCode:keyString[i]];
  107.     }
  108.     
  109.     return self;
  110. }
  111.  
  112. - postKeyboardEvent:(int)eventType window:(int)window flags:(int)flags 
  113.     charCode:(char)charCode
  114. {
  115.     NXEvent event;
  116.     
  117.     bzero(&event, sizeof(event));
  118.     
  119.     event.type = eventType;
  120.     event.flags = flags;
  121.     event.window = window;
  122.     event.data.key.charCode = charCode;
  123.  
  124.     [self postEvent:&event];
  125.     
  126.     return self;
  127. }
  128.  
  129. - postKeyCode:(char)charCode window:(int)window flags:(int)flags
  130. {
  131.     [self postKeyboardEvent:NX_KEYDOWN window:window flags:flags charCode:charCode];
  132.     [self postKeyboardEvent:NX_KEYUP window:window flags:flags charCode:charCode];
  133.     
  134.     return self;
  135. }
  136.  
  137. - postMouseEvent:(int)eventType window:(int)window flags:(int)flags 
  138.     x:(double)x y:(double)y click:(int)click
  139. {
  140.     NXEvent event;
  141.     
  142.     bzero(&event, sizeof(event));
  143.     
  144.     event.type = eventType;
  145.     event.flags = flags;
  146.     event.window = window;
  147.     event.location.x = x;
  148.     event.location.y = y;
  149.     event.data.mouse.click = click;
  150.     
  151.     [self postEvent:&event];
  152.     
  153.     return self;
  154. }
  155.  
  156. - postSingleClick:(int)window flags:(int)flags x:(double)x y:(double)y
  157. {
  158.     [self postMouseEvent:NX_MOUSEDOWN window:window flags:flags x:x y:y click:1];
  159.     [self postMouseEvent:NX_MOUSEUP window:window flags:flags x:x y:y click:1];
  160.     
  161.     return self;
  162. }
  163.  
  164. - postDoubleClick:(int)window flags:(int)flags x:(double)x y:(double)y
  165. {
  166.     [self postSingleClick:window flags:flags x:x y:y];
  167.     [self postMouseEvent:NX_MOUSEDOWN window:window flags:flags x:x y:y click:2];
  168.     [self postMouseEvent:NX_MOUSEUP window:window flags:flags x:x y:y click:2];
  169.     
  170.     return self;
  171. }
  172.  
  173. - postTripleClick:(int)window flags:(int)flags x:(double)x y:(double)y
  174. {
  175.     [self postDoubleClick:window flags:flags x:x y:y];
  176.     [self postMouseEvent:NX_MOUSEDOWN window:window flags:flags x:x y:y click:3];
  177.     [self postMouseEvent:NX_MOUSEUP window:window flags:flags x:x y:y click:3];
  178.     
  179.     return self;
  180. }
  181.  
  182. - postActivate:(BOOL)activate
  183. {
  184.     NXEvent event;
  185.     
  186.     bzero(&event, sizeof(event));
  187.     
  188.     event.type = NX_KITDEFINED;
  189.     event.data.compound.subtype = activate ? NX_APPACT : NX_APPDEACT;
  190.     
  191.     [self postEvent:&event];
  192.     
  193.     return self;
  194. }
  195.  
  196. - dragWindow:(int)winNumber deltaX:(double)x deltaY:(double)y
  197. {
  198.     NXEvent event;
  199.     
  200.     bzero(&event, sizeof(event));
  201.     
  202.     event.type = NX_JOURNALEVENT;
  203.     event.data.compound.subtype = NX_WINDRAGGED;
  204.     event.window = winNumber;
  205.     event.location.x = x;
  206.     event.location.y = y;
  207.     event.data.mouse.click = 1;
  208.     
  209.     [self postEvent:&event];
  210.  
  211.     return self;
  212. }
  213.  
  214. - attachStrings
  215. {
  216.     port_t retPort;
  217.     
  218.     if (enabled)
  219.         return self;
  220.         
  221.     [appSpeaker selectorRPC:"_initJournaling:::" paramTypes:"isS", 0, 0, &retPort];
  222.     [journalSpeaker setSendPort:retPort];
  223.     [journalSpeaker selectorRPC:"_setStatus:" paramTypes:"i", 1];
  224.     
  225.     enabled = YES;
  226.         
  227.     return self;
  228. }
  229.  
  230. - releaseStrings
  231. {
  232.     if (!enabled)
  233.         return self;
  234.         
  235.     [journalSpeaker selectorRPC:"_setStatus:" paramTypes:"i", 0];
  236.     
  237.     enabled = NO;
  238.         
  239.     return self;
  240. }
  241.  
  242. - (int)getPid
  243. /*
  244.  * Returns the pid of the application, or -1 if it can't be found.
  245.  */
  246. {
  247.     char psLine[500];
  248.     FILE *psPipe;
  249.     int length = strlen(appName);
  250.     
  251.     if (pid)
  252.         return pid;
  253.         
  254.     if ((psPipe = popen("ps -axc", "r")) == NULL) {
  255.         NXRunAlertPanel(NULL, "Could not open pipe to \"ps\"", 
  256.             NULL, NULL, NULL);
  257.         return -1;
  258.     }
  259.     fgets(psLine, 500, psPipe);    // Skip first line
  260.     while (fgets(psLine, 500, psPipe) != NULL) {
  261.         if (!strncmp(&psLine[20], appName, length)) {
  262.             sscanf(psLine, "%d", &pid);
  263.             break;
  264.         }
  265.     }
  266.     pclose(psPipe);
  267.     
  268.     if (pid)
  269.         return pid;
  270.     else
  271.         return -1;
  272. }
  273.  
  274. - (int)getContext
  275. /*
  276.  * Returns the application's postscript context.
  277.  */
  278. {
  279.     int numWins, *winList, i, windowPid;
  280.     
  281.     if (context)
  282.         return context;
  283.         
  284.     [self getPid];
  285.     if (pid < 0)
  286.         return 0;
  287.     PScountwindowlist(0, &numWins);
  288.     winList = (int *)calloc(sizeof(int), numWins);
  289.     PSwindowlist(0, numWins, winList);
  290.     
  291.     for (i=0; i<numWins; i++) {
  292.         myCurrentOwner(winList[i], &context);
  293.         myPid(context, &windowPid);
  294.         if (windowPid == pid)
  295.             break;
  296.     }
  297.     if (i == numWins) {
  298.         fprintf(stderr, "Puppeteer: failed to find application's context\n");
  299.         context = 0;
  300.     }
  301.     
  302.     free(winList);
  303.         
  304.     return context;
  305. }
  306.  
  307. - windowList
  308. /*
  309.  * Returns a list of this application's windows. A new list is created each time this
  310.  * method is called, and it is the caller's responsibility to free it and it's contents.
  311.  */
  312. {
  313.     int numWins, *winList, i;
  314.     unsigned int winNumber;
  315.     id window, windowList;
  316.     
  317.     [self getContext];
  318.     if (!context)
  319.         return NULL;
  320.     PScountwindowlist(context, &numWins);
  321.     winList = (int *)calloc(sizeof(int), numWins);
  322.     PSwindowlist(context, numWins, winList);
  323.     
  324.     windowList = [[List alloc] init];
  325.     for (i=0; i<numWins; i++) {
  326.         NX_DURING
  327.         NXConvertGlobalToWinNum(winList[i], &winNumber);
  328.         NX_HANDLER
  329.             switch (NXLocalHandler.code) {
  330.             case dps_err_ps:
  331.             winNumber = 0;
  332.                     break;
  333.                default:
  334.                     NX_RERAISE();
  335.             }
  336.         NX_ENDHANDLER
  337.  
  338.         window = [[WindowInfo alloc] initLocalNumber:winNumber
  339.             globalNumber:winList[i]];
  340.         [windowList addObject:window];
  341.     }
  342.     free(winList);
  343.         
  344.     return windowList;
  345. }
  346.  
  347. - (int)windowCount
  348. /*
  349.  * Return the number of windows belonging to puppet.
  350.  */
  351. {
  352.     int windowCount;
  353.     
  354.     [self getContext];
  355.     if (!context)
  356.         return 0;
  357.         
  358.     PScountwindowlist(context, &windowCount);
  359.     
  360.     return windowCount;
  361. }
  362.  
  363. #define DELTA_X 1
  364.  
  365. - windowForPseudoNumber:(int)pseudoNumber
  366. /*
  367.  * Returns a WindowInfo object for the given pseudo window number (eg NX_KEYWINDOW),
  368.  * or nil if it can't be determined.
  369.  */
  370. {
  371.     id iList, fList, iWin, fWin;
  372.     int i, f, iCount, fCount, retry, iNum, fNum;
  373.     NXRect *iFrame, fFrame;
  374.  
  375.     assert(pseudoNumber < 0);
  376.  
  377.     /*
  378.      * First save the initial positions of all the windows.
  379.      */
  380.     iList = [self windowList];
  381.     iCount = [iList count];
  382.     iFrame = (NXRect *)malloc(iCount*sizeof(NXRect));
  383.     for (i=0; i<iCount; i++) {
  384.         iWin = [iList objectAt:i];
  385.         [iWin getFrame:&iFrame[i]];
  386.     }
  387.     
  388.     /*
  389.      * Now move the window by one pixel.
  390.      */
  391.     [self dragWindow:pseudoNumber deltaX:DELTA_X deltaY:0];
  392.     
  393.     for (retry=0; retry<10; retry++) {
  394.         fList = [self windowList];
  395.         for (i=0; i<iCount; i++) {
  396.             iWin = [iList objectAt:i];
  397.             iNum = [iWin localWindowNumber];
  398.             fCount = [fList count];
  399.             for (f=0; f<fCount; f++) {
  400.                 fWin = [fList objectAt:f];
  401.                 fNum = [fWin localWindowNumber];
  402.                 if (fNum == iNum)
  403.                     break;
  404.             }
  405.             if (f<fCount) {
  406.                 [fWin getFrame:&fFrame];
  407.                 if (iFrame[i].origin.x == (fFrame.origin.x - DELTA_X)) {
  408.                     [[iList freeObjects] free];
  409.                     //[[fList freeObjects] free];
  410.                     free(iFrame);
  411.                     return fWin;
  412.                 }
  413.             }
  414.         }
  415.         [[fList freeObjects] free];
  416.         sleep(1);
  417.         printf("Failed...\n");
  418.     }
  419.     
  420.     [[iList freeObjects] free];
  421.     free(iFrame);
  422.     printf("Failed utterly!\n");
  423.     
  424.     return NULL;
  425. }
  426.  
  427. - keyWindow
  428. {
  429.     return [self windowForPseudoNumber:NX_KEYWINDOW];
  430. }
  431.  
  432. - mainWindow
  433. {
  434.     return [self windowForPseudoNumber:NX_MAINWINDOW];
  435. }
  436.  
  437. - mainMenu
  438. {
  439.     return [self windowForPseudoNumber:NX_MAINMENU];
  440. }
  441.  
  442. - ping
  443. /*
  444.  * This method simply sends a message to the puppet application and waits for it to
  445.  * return. This ensures that the application has processed all outstanding events.
  446.  * We use the standard msgVersion:ok: method and ignore the return value.
  447.  */
  448. {
  449.     int ret, ok = 0;
  450.     char *const *version = 0;
  451.     
  452.     ret = [journalSpeaker msgVersion:version ok:&ok];
  453.     
  454.     return self;
  455. }
  456.  
  457. - appSpeaker
  458. {
  459.     return appSpeaker;
  460. }
  461.  
  462. @end
  463.