unit shlIPC;

interface

uses

  m_api, Windows;

const

  REPLY_FAIL = $88888888;
  REPLY_OK = $00000000;

  REQUEST_ICONS = 1;
  REQUEST_GROUPS = (REQUEST_ICONS) shl 1;
  REQUEST_CONTACTS = (REQUEST_GROUPS) shl 1;
  REQUEST_XFRFILES = (REQUEST_CONTACTS) shl 1;
  REQUEST_NEWICONS = (REQUEST_XFRFILES) shl 1;
  REQUEST_CLEARMRU = (REQUEST_NEWICONS) shl 1;

  ICONS_NOTIMPL = $00000008;
  GROUPS_NOTIMPL = $00000080;
  CONTACTS_NOTIMPL = $00000800;

  STATUS_PROFILENAME = 2;


  // there maybe more than one reason why any request type wasn't returned

type

  { this can be a group entry, if it is, hContact = <index>
    the string contains the full group path }

  PSlotIPC = ^TSlotIPC;

  TSlotIPC = packed record
    cbSize: Byte;
    fType: int; // a REQUEST_* type
    Next: PSlotIPC;
    hContact: THandle;
    hProto: Cardinal; // hash of the protocol the user is on
    hGroup: Cardinal; // hash of the entire path (not defined for REQUEST_GROUPS slots)
    Status: Word;
    // only used for contacts -- can be STATUS_PROFILENAME -- but that is because returning the profile name is optional
    MRU: Byte; // if set, contact has been recently used
    cbStrSection: int;
  end;

  // if the slot contains a nickname, after the NULL, there is another NULL or a group path string

  PSlotProtoIcons = ^TSlotProtoIcons;

  TSlotProtoIcons = packed record
    pid: Cardinal; // pid of Miranda this protocol was on
    hProto: Cardinal; // hash of the protocol
    hIcons: array [0 .. 9] of HICON; // each status in order of ID_STATUS_*
    hBitmaps: array [0 .. 9] of HBITMAP; // each status "icon" as a bitmap
  end;

  TSlotProtoIconsArray = array [0 .. 0] of TSlotProtoIcons;
  // the process space the thread is running in WILL use a different mapping
  // address than the client's process space, addresses need to be adjusted
  // to the client's process space.. this is done by the following means :

  //
  // new_addr := (old_address - serverbase) + client base
  //
  // this isn't the best of solutions, the link list should be a variant array
  // without random access, which would mean each element's different
  // size would need to be computed each time it is accessed or read past

  PHeaderIPC = ^THeaderIPC;

  THeaderIPC = record
    cbSize: Cardinal;
    dwVersion: Cardinal;
    pServerBaseAddress: Pointer;
    pClientBaseAddress: Pointer;
    fRequests: Cardinal;
    dwFlags: Cardinal;
    Slots: Cardinal;
    Param: Cardinal;
    SignalEventName: array [0 .. 63] of Char;
    // Translate() won't work via Explorer
    MirandaName: array [0 .. 63] of Char;
    MRUMenuName: array [0 .. 63] of Char; // for the MRU menu itself
    ClearEntries: array [0 .. 63] of Char; // for the "clear entries"
    IconsBegin: PSlotIPC;
    ContactsBegin: PSlotIPC;
    GroupsBegin: PSlotIPC;
    NewIconsBegin: PSlotIPC;
    // start of an flat memory stack, which is referenced as a linked list
    DataSize: int;
    DataPtr: PSlotIPC;
    DataPtrEnd: PSlotIPC;
    DataFramePtr: Pointer;
  end;

const
  HIPC_NOICONS = 1;

procedure ipcPrepareRequests(ipcPacketSize: int; pipch: PHeaderIPC; fRequests: Cardinal);
function ipcSendRequest(hSignal, hWaitFor: THandle; pipch: PHeaderIPC; dwTimeoutMsecs: DWORD): Cardinal;
function ipcAlloc(pipch: PHeaderIPC; nSize: Integer): PSlotIPC;
procedure ipcFixupAddresses(FromServer: LongBool; pipch: PHeaderIPC);

type

  TStrTokRec = record
    szStr: PChar;
    szSet: set of Char;
    // need a delimiter after the token too?, e.g. FOO^BAR^ if FOO^BAR
    // is the string then only FOO^ is returned, could cause infinite loops
    // if the condition isn't accounted for thou.
    bSetTerminator: Boolean;
  end;

function StrTok(var strr: TStrTokRec): PChar;

type

  PGroupNode = ^TGroupNode;

  TGroupNode = record
    Left, Right, _prev, _next: PGroupNode;
    Depth: Cardinal;
    Hash: Cardinal; // hash of the group name alone
    szGroup: PChar;
    cchGroup: Integer;
    hMenu: THandle;
    hMenuGroupID: Integer;
    dwItems: Cardinal;
  end;

  PGroupNodeList = ^TGroupNodeList;

  TGroupNodeList = record
    First, Last: PGroupNode;
  end;

function AllocGroupNode(list: PGroupNodeList; Root: PGroupNode; Depth: Integer): PGroupNode;
function FindGroupNode(P: PGroupNode; const Hash, Depth: dword): PGroupNode;

type

  // a contact can never be a submenu too.
  TSlotDrawType = (dtEntry, dtGroup, dtContact, dtCommand);
  TSlotDrawTypes = set of TSlotDrawType;

  PMenuDrawInfo = ^TMenuDrawInfo;

  TMenuCommandCallback = function(pipch: PHeaderIPC; // IPC header info, already mapped
    hWorkThreadEvent: THandle; // event object being waited on on miranda thread
    hAckEvent: THandle; // ack event object that has been created
    psd: PMenuDrawInfo // command/draw info
    ): Integer; stdcall;

  TMenuDrawInfo = record
    szText: PChar;
    szProfile: PChar;
    cch: Integer;
    wID: Integer; // should be the same as the menu item's ID
    fTypes: TSlotDrawTypes;
    hContact: THandle;
    hStatusIcon: THandle;
    // HICON from Self^.ProtoIcons[index].hIcons[status]; Do not DestroyIcon()
    hStatusBitmap: THandle; // HBITMAP, don't free.
    pid: Integer;
    MenuCommandCallback: TMenuCommandCallback; // dtCommand must be set also.
  end;

implementation

function FindGroupNode(P: PGroupNode; const Hash, Depth: dword): PGroupNode;
begin
  Result := P;
  while Result <> nil do
  begin
    if (Result^.Hash = Hash) and (Result^.Depth = Depth) then
      Exit;
    If Result^.Left <> nil then
    begin
      P := Result;
      Result := FindGroupNode(Result^.Left, Hash, Depth);
      If Result <> nil then
        Exit;
      Result := P;
    end;
    Result := Result^.Right;
  end; // while
end;

function AllocGroupNode(list: PGroupNodeList; Root: PGroupNode; Depth: Integer): PGroupNode;
begin
  New(Result);
  Result^.Left := nil;
  Result^.Right := nil;
  Result^.Depth := Depth;
  if Depth > 0 then
  begin
    if Root^.Left = nil then
      Root^.Left := Result
    else
    begin
      Root := Root^.Left;
      while Root^.Right <> nil do
        Root := Root^.Right;
      Root^.Right := Result;
    end;
  end
  else
  begin
    if list^.First = nil then
      list^.First := Result;
    if list^.Last <> nil then
      list^.Last^.Right := Result;
    list^.Last := Result;
  end; // if
end;

procedure ipcPrepareRequests(ipcPacketSize: int; pipch: PHeaderIPC; fRequests: Cardinal);
begin
  // some fields may already have values like the event object name to open
  pipch^.cbSize := sizeof(THeaderIPC);
  pipch^.dwVersion := PLUGIN_MAKE_VERSION(2, 0, 1, 2);
  pipch^.dwFlags := 0;
  pipch^.pServerBaseAddress := nil;
  pipch^.pClientBaseAddress := pipch;
  pipch^.fRequests := fRequests;
  pipch^.Slots := 0;
  pipch^.IconsBegin := nil;
  pipch^.ContactsBegin := nil;
  pipch^.GroupsBegin := nil;
  pipch^.NewIconsBegin := nil;
  pipch^.DataSize := ipcPacketSize - pipch^.cbSize;
  // the server side will adjust these pointers as soon as it opens
  // the mapped file to it's base address, these are set 'ere because ipcAlloc()
  // maybe used on the client side and are translated by the server side.
  // ipcAlloc() is used on the client side when transferring filenames
  // to the ST thread.
  uint_ptr(pipch^.DataPtr) := uint_ptr(pipch) + sizeof(THeaderIPC);
  uint_ptr(pipch^.DataPtrEnd) := uint_ptr(pipch^.DataPtr) + pipch^.DataSize;
  pipch^.DataFramePtr := pipch^.DataPtr;
  // fill the data area
  FillChar(pipch^.DataPtr^, pipch^.DataSize, 0);
end;

function ipcSendRequest(hSignal, hWaitFor: THandle; pipch: PHeaderIPC; dwTimeoutMsecs: DWORD): Cardinal;
begin
  { signal ST to work }
  SetEvent(hSignal);
  { wait for reply, it should open a handle to hWaitFor... }
  while True do
  begin
    Result := WaitForSingleObjectEx(hWaitFor, dwTimeoutMsecs, True);
    if Result = WAIT_OBJECT_0 then
    begin
      Result := pipch^.fRequests;
      break;
    end
    else if Result = WAIT_IO_COMPLETION then
    begin
      (* APC call... *)
    end
    else
    begin
      Result := REPLY_FAIL;
      break;
    end; // if
  end; // while
end;

function ipcAlloc(pipch: PHeaderIPC; nSize: Integer): PSlotIPC;
var
  PSP: uint_ptr;
begin
  Result := nil;
  { nSize maybe zero, in that case there is no string section --- }
  PSP := uint_ptr(pipch^.DataFramePtr) + sizeof(TSlotIPC) + nSize;
  { is it past the end? }
  If PSP >= uint_ptr(pipch^.DataPtrEnd) then
    Exit;
  { return the pointer }
  Result := pipch^.DataFramePtr;
  { set up the item }
  Result^.cbSize := sizeof(TSlotIPC);
  Result^.cbStrSection := nSize;
  { update the frame ptr }
  pipch^.DataFramePtr := Pointer(PSP);
  { let this item jump to the next yet-to-be-allocated-item which should be null anyway }
  Result^.Next := Pointer(PSP);
end;

procedure ipcFixupAddresses(FromServer: LongBool; pipch: PHeaderIPC);
var
  pct: PSlotIPC;
  q: ^PSlotIPC;
  iServerBase: int_ptr;
  iClientBase: int_ptr;
begin
  if pipch^.pServerBaseAddress = pipch^.pClientBaseAddress then
    Exit;
  iServerBase := int_ptr(pipch^.pServerBaseAddress);
  iClientBase := int_ptr(pipch^.pClientBaseAddress);
  // fix up all the pointers in the header
  if pipch^.IconsBegin <> nil then
  begin
    uint_ptr(pipch^.IconsBegin) := (uint_ptr(pipch^.IconsBegin) - iServerBase) + iClientBase;
  end; // if

  if pipch^.ContactsBegin <> nil then
  begin
    uint_ptr(pipch^.ContactsBegin) := (uint_ptr(pipch^.ContactsBegin) - iServerBase) + iClientBase;
  end; // if

  if pipch^.GroupsBegin <> nil then
  begin
    uint_ptr(pipch^.GroupsBegin) := (uint_ptr(pipch^.GroupsBegin) - iServerBase) + iClientBase;
  end; // if

  if pipch^.NewIconsBegin <> nil then
  begin
    uint_ptr(pipch^.NewIconsBegin) := (uint_ptr(pipch^.NewIconsBegin) - iServerBase) +
      iClientBase;
  end;
  uint_ptr(pipch^.DataPtr) := (uint_ptr(pipch^.DataPtr) - iServerBase) + iClientBase;
  uint_ptr(pipch^.DataPtrEnd) := (uint_ptr(pipch^.DataPtrEnd) - iServerBase) + iClientBase;
  uint_ptr(pipch^.DataFramePtr) := (uint_ptr(pipch^.DataFramePtr) - iServerBase) + iClientBase;
  // and the link list
  pct := pipch^.DataPtr;
  while (pct <> nil) do
  begin
    // the first pointer is already fixed up, have to get a pointer
    // to the next pointer and modify where it jumps to
    q := @pct^.Next;
    if q^ <> nil then
    begin
      uint_ptr(q^) := (uint_ptr(q^) - iServerBase) + iClientBase;
    end; // if
    pct := q^;
  end; // while
end;

function StrTok(var strr: TStrTokRec): PChar;
begin
  Result := nil;
  { don't allow #0's in sets or null strings }
  If (strr.szStr = nil) or (#0 in strr.szSet) then
    Exit;
  { strip any leading delimiters }
  while strr.szStr^ in strr.szSet do
    Inc(strr.szStr);
  { end on null? full of delimiters }
  If strr.szStr^ = #0 then
  begin
    // wipe out the pointer
    strr.szStr := nil;
    Exit;
  end;
  { store the start of the token }
  Result := strr.szStr;
  { process til start of another delim }
  while not(strr.szStr^ in strr.szSet) do
  begin
    { don't process past the real null, is a delimter required to cap the token? }
    If strr.szStr^ = #0 then
      break;
    Inc(strr.szStr);
  end;
  { if we end on a null stop reprocessin' }
  If strr.szStr^ = #0 then
  begin
    // no more tokens can be read
    strr.szStr := nil;
    // is a ending delimiter required?
    If strr.bSetTerminator then
    begin
      // rollback
      strr.szStr := Result;
      Result := nil;
    end;
    //
  end
  else
  begin
    { mark the end of the token, may AV if a constant pchar is passed }
    strr.szStr^ := #0;
    { skip past this fake null for next time }
    Inc(strr.szStr);
  end;
end;

end.