Build 0.20.10.0

This commit is contained in:
Markus Isberg
2022-12-05 19:48:59 +02:00
parent 31d2dc658e
commit 9d2f160314
55 changed files with 335 additions and 3511 deletions

View File

@@ -174,9 +174,9 @@ namespace Barotrauma
Character.DisableControls = true;
if (PlayerInput.KeyHit(Keys.Tab))
if (PlayerInput.KeyHit(Keys.Tab) && !textBox.IsIMEActive)
{
textBox.Text = AutoComplete(textBox.Text, increment: string.IsNullOrEmpty(currentAutoCompletedCommand) ? 0 : 1 );
textBox.Text = AutoComplete(textBox.Text, increment: string.IsNullOrEmpty(currentAutoCompletedCommand) ? 0 : 1 );
}
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))

View File

@@ -1,72 +1,18 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace EventInput
{
public class CharacterEventArgs : EventArgs
public readonly record struct CharacterEventArgs(char Character, long Param)
{
private readonly char character;
private readonly long lParam;
public CharacterEventArgs(char character, long lParam)
{
this.character = character;
this.lParam = lParam;
}
public char Character
{
get { return character; }
}
public long Param
{
get { return lParam; }
}
public long RepeatCount
{
get { return lParam & 0xffff; }
}
public bool ExtendedKey
{
get { return (lParam & (1 << 24)) > 0; }
}
public bool AltPressed
{
get { return (lParam & (1 << 29)) > 0; }
}
public bool PreviousState
{
get { return (lParam & (1 << 30)) > 0; }
}
public bool TransitionState
{
get { return (lParam & (1 << 31)) > 0; }
}
public long RepeatCount => Param & 0xffff;
public bool ExtendedKey => (Param & (1 << 24)) > 0;
public bool AltPressed => (Param & (1 << 29)) > 0;
public bool PreviousState => (Param & (1 << 30)) > 0;
public bool TransitionState => (Param & (1 << 31)) > 0;
}
public class KeyEventArgs : EventArgs
{
private Keys keyCode;
public KeyEventArgs(Keys keyCode)
{
this.keyCode = keyCode;
}
public Keys KeyCode
{
get { return keyCode; }
}
}
public readonly record struct KeyEventArgs(Keys KeyCode, char Character);
public delegate void CharEnteredHandler(object sender, CharacterEventArgs e);
public delegate void KeyEventHandler(object sender, KeyEventArgs e);
@@ -89,14 +35,11 @@ namespace EventInput
/// </summary>
public static event KeyEventHandler KeyUp;
#if !WINDOWS
/// <summary>
/// Raised when the user is editing text and IME is in progress.
/// Windows build uses ImeSharp instead because SDL2's IME implementation is broken on Windows (https://github.com/libsdl-org/SDL/issues/2243)
/// </summary>
public static event EditingTextHandler EditingText;
#endif
static bool initialized;
@@ -110,11 +53,10 @@ namespace EventInput
{
return;
}
window.TextInput += ReceiveInput;
#if !WINDOWS
window.KeyDown += ReceiveKeyDown;
window.TextEditing += ReceiveTextEditing;
#endif
initialized = true;
}
@@ -122,15 +64,17 @@ namespace EventInput
private static void ReceiveInput(object sender, TextInputEventArgs e)
{
OnCharEntered(e.Character);
KeyDown?.Invoke(sender, new KeyEventArgs(e.Key));
}
#if !WINDOWS
private static void ReceiveKeyDown(object sender, TextInputEventArgs e)
{
KeyDown?.Invoke(sender, new KeyEventArgs(e.Key, e.Character));
}
private static void ReceiveTextEditing(object sender, TextEditingEventArgs e)
{
EditingText?.Invoke(sender, e);
}
#endif
public static void OnCharEntered(char character)
{

View File

@@ -10,11 +10,7 @@ namespace EventInput
void ReceiveTextInput(string text);
void ReceiveCommandInput(char command);
void ReceiveSpecialInput(Keys key);
#if !WINDOWS
/// Windows build uses ImeSharp instead because SDL2's IME implementation is broken on Windows (https://github.com/libsdl-org/SDL/issues/2243)
void ReceiveEditingInput(string text, int start);
#endif
void ReceiveEditingInput(string text, int start, int length);
bool Selected { get; set; } //or Focused
}
@@ -26,59 +22,27 @@ namespace EventInput
EventInput.Initialize(window);
EventInput.CharEntered += EventInput_CharEntered;
EventInput.KeyDown += EventInput_KeyDown;
#if !WINDOWS
EventInput.EditingText += EventInput_TextEditing;
#endif
/*
* SDL by default starts in a state where it accepts IME inputs
* this is bad because this blocks keybinds since the IME thinks
* it's typing in a text box and not forwarding keybinds to the game.
*/
TextInput.StopTextInput();
GameMain.ResetIMEWorkaround();
}
#if !WINDOWS
public void EventInput_TextEditing(object sender, TextEditingEventArgs e)
{
_subscriber?.ReceiveEditingInput(e.Text, e.Start);
_subscriber?.ReceiveEditingInput(e.Text, e.Start, e.Length);
}
#endif
public void EventInput_KeyDown(object sender, KeyEventArgs e)
{
_subscriber?.ReceiveSpecialInput(e.KeyCode);
if (char.IsControl(e.Character))
{
_subscriber?.ReceiveCommandInput(e.Character);
}
}
void EventInput_CharEntered(object sender, CharacterEventArgs e)
{
if (_subscriber == null)
return;
if (char.IsControl(e.Character))
{
_subscriber.ReceiveCommandInput(e.Character);
// Doesn't work as expected. Not sure why this should be run in a separate thread.
//#if WINDOWS
// //ctrl-v
// if (e.Character == 0x16) // 22
// {
// //XNA runs in Multiple Thread Apartment state, which cannot recieve clipboard
// Thread thread = new Thread(PasteThread);
// thread.SetApartmentState(ApartmentState.STA);
// thread.Start();
// thread.Join();
// _subscriber.ReceiveTextInput(_pasteResult);
// }
// else
// {
// _subscriber.ReceiveCommandInput(e.Character);
// }
//#else
// _subscriber.ReceiveCommandInput(e.Character);
//#endif
}
else
{
_subscriber.ReceiveTextInput(e.Character);
}
_subscriber?.ReceiveTextInput(e.Character);
}
IKeyboardSubscriber _subscriber;
@@ -97,8 +61,9 @@ namespace EventInput
if (value is GUITextBox box)
{
TextInput.SetTextInputRect(box.Rect);
TextInput.SetTextInputRect(box.MouseRect);
TextInput.StartTextInput();
TextInput.SetTextInputRect(box.MouseRect);
}
_subscriber = value;

View File

@@ -1261,10 +1261,6 @@ namespace Barotrauma
UpdateMessages(deltaTime);
UpdateSavingIndicator(deltaTime);
}
#if WINDOWS
GUITextBox.UpdateIME();
#endif
}
public static void UpdateGUIMessageBoxesOnly(float deltaTime)

View File

@@ -111,10 +111,7 @@ namespace Barotrauma
}
public void ReceiveTextInput(string text) { }
public void ReceiveCommandInput(char command) { }
#if !WINDOWS
public void ReceiveEditingInput(string text, int start) { }
#endif
public void ReceiveEditingInput(string text, int start, int length) { }
public void ReceiveSpecialInput(Keys key)
{
@@ -125,9 +122,7 @@ namespace Barotrauma
listBox.ReceiveSpecialInput(key);
GUI.KeyboardDispatcher.Subscriber = this;
break;
case Keys.Enter:
case Keys.Space:
case Keys.Escape:
default:
GUI.KeyboardDispatcher.Subscriber = null;
break;
}

View File

@@ -1347,10 +1347,7 @@ namespace Barotrauma
}
public void ReceiveTextInput(string text) { }
public void ReceiveCommandInput(char command) { }
#if !WINDOWS
public void ReceiveEditingInput(string text, int start) { }
#endif
public void ReceiveEditingInput(string text, int start, int length) { }
public void ReceiveSpecialInput(Keys key)
{
@@ -1368,9 +1365,7 @@ namespace Barotrauma
case Keys.Right:
if (isHorizontal && AllowArrowKeyScroll) { SelectNext(playSelectSound: PlaySelectSound.Yes); }
break;
case Keys.Enter:
case Keys.Space:
case Keys.Escape:
default:
GUI.KeyboardDispatcher.Subscriber = null;
break;
}

View File

@@ -223,24 +223,24 @@ namespace Barotrauma
public ScalableFont GetFontForStr(string str) =>
Prefabs.ActivePrefab.GetFontForCategory(TextManager.GetSpeciallyHandledCategories(str));
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth)
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects spriteEffects, float layerDepth)
{
DrawString(sb, text.Value, position, color, rotation, origin, scale, se, layerDepth);
DrawString(sb, text.Value, position, color, rotation, origin, scale, spriteEffects, layerDepth);
}
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth)
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects spriteEffects, float layerDepth)
{
GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, se, layerDepth);
GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth);
}
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, Alignment alignment = Alignment.TopLeft)
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, Alignment alignment = Alignment.TopLeft)
{
DrawString(sb, text.Value, position, color, rotation, origin, scale, se, layerDepth, alignment);
DrawString(sb, text.Value, position, color, rotation, origin, scale, spriteEffects, layerDepth, alignment);
}
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
{
GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, se, layerDepth, alignment, forceUpperCase);
GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth, alignment, forceUpperCase);
}
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit, bool italics = false)
@@ -253,9 +253,9 @@ namespace Barotrauma
GetFontForStr(text).DrawString(sb, text, position, color, forceUpperCase, italics);
}
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, in ImmutableArray<RichTextData>? richTextData, int rtdOffset = 0, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, in ImmutableArray<RichTextData>? richTextData, int rtdOffset = 0, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
{
GetFontForStr(text).DrawStringWithColors(sb, text, position, color, rotation, origin, scale, se, layerDepth, richTextData, rtdOffset, alignment, forceUpperCase);
GetFontForStr(text).DrawStringWithColors(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth, richTextData, rtdOffset, alignment, forceUpperCase);
}
public Vector2 MeasureString(LocalizedString str, bool removeExtraSpacing = false)

View File

@@ -256,6 +256,8 @@ namespace Barotrauma
private readonly IMEPreviewTextHandler imePreviewTextHandler;
public bool IsIMEActive => imePreviewTextHandler is { HasText: true };
public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, GUIFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool createClearButton = false, bool createPenIcon = true)
: base(style, rectT)
@@ -574,6 +576,7 @@ namespace Barotrauma
{
CaretIndex = Math.Min(Text.Length, CaretIndex + input.Length);
OnTextChanged?.Invoke(this, Text);
imePreviewTextHandler?.Reset();
}
}
@@ -602,10 +605,12 @@ namespace Barotrauma
public void ReceiveCommandInput(char command)
{
if (Text == null) Text = "";
if (IsIMEActive) { return; }
if (Text == null) { Text = ""; }
// Prevent alt gr from triggering any of these as that combination is often needed for special characters
if (PlayerInput.IsAltDown()) return;
if (PlayerInput.IsAltDown()) { return; }
switch (command)
{
@@ -678,21 +683,22 @@ namespace Barotrauma
break;
}
}
#if !WINDOWS
public void ReceiveEditingInput(string text, int start)
public void ReceiveEditingInput(string text, int start, int length)
{
if (string.IsNullOrEmpty(text))
{
if (start is 0) { imePreviewTextHandler.Reset(); }
imePreviewTextHandler.Reset();
return;
}
imePreviewTextHandler.UpdateText(text, start);
imePreviewTextHandler.UpdateText(text, start, length);
}
#endif
public void ReceiveSpecialInput(Keys key)
{
if (IsIMEActive) { return; }
switch (key)
{
case Keys.Left:
@@ -883,20 +889,7 @@ namespace Barotrauma
public void DrawIMEPreview(SpriteBatch spriteBatch)
{
if (!imePreviewTextHandler.HasText) { return; }
Vector2 imePosition = CaretScreenPos;
int inflate = GUI.IntScale(3);
RectangleF rect = new RectangleF(imePosition, imePreviewTextHandler.TextSize);
rect.Inflate(inflate, inflate);
RectangleF borderRect = rect;
borderRect.Inflate(1, 1);
GUI.DrawFilledRectangle(spriteBatch, borderRect, Color.White, depth: 0.02f);
GUI.DrawFilledRectangle(spriteBatch, rect, Color.Black, depth: 0.01f);
Font.DrawString(spriteBatch, imePreviewTextHandler.PreviewText, imePosition, GUIStyle.Orange, 0.0f, Vector2.Zero, 1f, SpriteEffects.None, 0, alignment: textBlock.TextAlignment, forceUpperCase: textBlock.ForceUpperCase);
imePreviewTextHandler.DrawIMEPreview(spriteBatch, CaretScreenPos, textBlock);
}
private void CalculateSelection()

View File

@@ -1,86 +0,0 @@
using ImeSharp;
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma;
/// <summary>
/// A class for handling Input Method Editor (used for inputting e.g. Chinese and Japanese text)
/// </summary>
public partial class GUITextBox : GUIComponent
{
private static bool initialized;
public static GUIFrame IMEWindow { get; set; }
public static GUITextBlock IMETextBlock { get; set; }
public static void UpdateIME()
{
if (!initialized) { InitializeIME(); }
if (GUI.KeyboardDispatcher.Subscriber is GUITextBox { Selected: true })
{
IMEWindow?.AddToGUIUpdateList(order: 10);
}
}
private static void InitializeIME()
{
InputMethod.Initialize(GameMain.Instance.Window.Hwnd, false);
InputMethod.TextCompositionCallback = OnTextComposition;
InputMethod.CommitTextCompositionCallback = OnCommitTextComposition;
InputMethod.Enabled = true;
IMEWindow = new GUIFrame(new RectTransform(new Point(GUI.IntScale(300), GUI.IntScale(300)), GUI.Canvas), "InnerFrame") { CanBeFocused = false, Visible = false };
IMETextBlock = new GUITextBlock(new RectTransform(Vector2.One, IMEWindow.RectTransform), "") { CanBeFocused = false };
initialized = true;
}
private static void OnTextComposition(IMEString compositionText, int cursorPosition, IMEString[] candidateList, int candidatePageStart, int candidatePageSize, int candidateSelection)
{
if (GUI.KeyboardDispatcher.Subscriber is not GUITextBox { Selected: true } textBox) { return; }
IMEWindow.Visible = true;
string text = compositionText.ToString().Insert(cursorPosition, "|");
if (candidateList != null)
{
text += "\n";
for (int i = 0; i < candidatePageSize; i++)
{
string candidateStr = $"\t{candidatePageStart + i + 1} {candidateList[i]}";
if (candidateSelection == i)
{
candidateStr = $" ‖color:{XMLExtensions.ToStringHex(Color.White)}‖{candidateStr}‖end‖";
}
candidateStr += "\n";
text += candidateStr;
}
}
IMETextBlock.Text = RichString.Rich(text);
IMEWindow.RectTransform.NonScaledSize = new Point(
Math.Max(IMEWindow.Rect.Width, (int)IMETextBlock.TextSize.X + GUI.IntScale(32)),
(int)IMETextBlock.TextSize.Y);
Point windowPos = new Point(textBox.Rect.X, textBox.Rect.Bottom);
if (windowPos.Y + IMEWindow.Rect.Height > GameMain.GraphicsHeight)
{
windowPos.Y = textBox.Rect.Y - IMEWindow.Rect.Height;
}
IMEWindow.RectTransform.ScreenSpaceOffset = windowPos;
}
private static void OnCommitTextComposition(string text)
{
if (IMEWindow.Visible)
{
foreach (char c in text)
{
if (!char.IsControl(c))
{
GUI.KeyboardDispatcher.Subscriber?.ReceiveTextInput(c);
}
}
}
IMEWindow.Visible = false;
}
}

View File

@@ -1,18 +1,25 @@
#nullable enable
using System.Collections.Immutable;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
public sealed class IMEPreviewTextHandler
{
public string PreviewText { get; private set; } = string.Empty;
public Vector2 TextSize { get; private set; }
public bool HasText => !string.IsNullOrEmpty(PreviewText);
public bool HasText => !string.IsNullOrEmpty(previewText);
// This has to be settable because for some reason we update the font of GUITextBox in some places
public GUIFont Font { get; set; }
private string previewText = string.Empty;
private Vector2 textSize;
private bool isSectioned;
private ImmutableArray<RichTextData>? richTextData;
public IMEPreviewTextHandler(GUIFont font)
{
Font = font;
@@ -20,35 +27,64 @@ namespace Barotrauma
public void Reset()
{
TextSize = Vector2.Zero;
PreviewText = string.Empty;
textSize = Vector2.Zero;
previewText = string.Empty;
richTextData = null;
isSectioned = false;
}
public void UpdateText(string text, int start)
public void UpdateText(string text, int start, int length)
{
if (string.IsNullOrEmpty(text) && start is 0)
isSectioned = start >= 0 && length > 0;
richTextData = null;
if (string.IsNullOrEmpty(text))
{
Reset();
return;
}
int totalLength = start + text.Length;
string newText = PreviewText;
if (newText.Length > totalLength)
{
newText = newText[..totalLength];
}
previewText = text;
if (totalLength > newText.Length)
{
// this is required for some reason on Windows
// my guess is that the order which TextEditing events come thru is not guaranteed
newText = newText.PadRight(totalLength);
}
textSize = Font.MeasureString(text);
newText = newText.Remove(start, text.Length).Insert(start, text);
PreviewText = newText;
TextSize = Font.MeasureString(PreviewText);
if (!isSectioned) { return; }
string coloredText = ToolBox.ColorSectionOfString(text, start, length, GUIStyle.Orange);
RichString richString = RichString.Rich(coloredText);
previewText = richString.SanitizedValue;
richTextData = richString.RichTextData;
}
public void DrawIMEPreview(SpriteBatch spriteBatch, Vector2 position, GUITextBlock textBlock)
{
if (!HasText) { return; }
int inflate = GUI.IntScale(3);
RectangleF rect = new RectangleF(position, textSize);
rect.Inflate(inflate, inflate);
RectangleF borderRect = rect;
borderRect.Inflate(1, 1);
GUI.DrawFilledRectangle(spriteBatch, borderRect, Color.White, depth: 0.02f);
GUI.DrawFilledRectangle(spriteBatch, rect, Color.Black, depth: 0.01f);
Font.DrawStringWithColors(spriteBatch,
text: previewText,
position: position,
color: isSectioned ? GUIStyle.TextColorNormal : GUIStyle.Orange,
rotation: 0.0f,
origin: Vector2.Zero,
scale: 1f,
spriteEffects: SpriteEffects.None,
layerDepth: 0,
richTextData: richTextData,
alignment: textBlock.TextAlignment,
forceUpperCase: textBlock.ForceUpperCase);
}
}
}

View File

@@ -377,6 +377,7 @@ namespace Barotrauma
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Hull));
performanceCounterTimer = Stopwatch.StartNew();
ResetIMEWorkaround();
}
/// <summary>
@@ -1239,5 +1240,20 @@ namespace Barotrauma
};
msgBox.Buttons[1].OnClicked = msgBox.Close;
}
/*
* On some systems, IME input is enabled by default, and being able to set the game to a state
* where it doesn't accept IME input on game launch seems very inconsistent.
* This function quickly cycles through IME input states and is called from couple different places
* to ensure that IME input is disabled properly when it's not needed.
*/
public static void ResetIMEWorkaround()
{
Rectangle rect = new Rectangle(0, 0, GraphicsWidth, GraphicsHeight);
TextInput.SetTextInputRect(rect);
TextInput.StartTextInput();
TextInput.SetTextInputRect(rect);
TextInput.StopTextInput();
}
}
}

View File

@@ -999,6 +999,7 @@ namespace Barotrauma
foreach (MapEntityCategory category in Enum.GetValues(typeof(MapEntityCategory)))
{
if (category == MapEntityCategory.None) { continue; }
entityCategoryButtons.Add(new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), entityMenuTop.RectTransform, scaleBasis: ScaleBasis.BothHeight),
"", style: "CategoryButton." + category.ToString())
{
@@ -1085,6 +1086,7 @@ namespace Barotrauma
foreach (MapEntityCategory category in Enum.GetValues(typeof(MapEntityCategory)))
{
if (category == MapEntityCategory.None) { continue; }
LocalizedString categoryName = TextManager.Get("MapEntityCategory." + category);
maxTextWidth = (int)Math.Max(maxTextWidth, GUIStyle.SubHeadingFont.MeasureString(categoryName.Replace(" ", "\n")).X + GUI.IntScale(50));
foreach (MapEntityPrefab ep in MapEntityPrefab.List)

View File

@@ -863,22 +863,21 @@ namespace Barotrauma
PlayDamageSound(damageType, damage, bodyPosition, 800.0f);
}
private static readonly List<DamageSound> tempList = new List<DamageSound>();
public static void PlayDamageSound(string damageType, float damage, Vector2 position, float range = 2000.0f, IEnumerable<Identifier> tags = null)
{
damage = MathHelper.Clamp(damage + Rand.Range(-10.0f, 10.0f), 0.0f, 100.0f);
tempList.Clear();
foreach (var s in damageSounds)
{
if ((s.DamageRange == Vector2.Zero ||
(damage >= s.DamageRange.X && damage <= s.DamageRange.Y)) &&
s.DamageType == damageType &&
(s.RequiredTag.IsEmpty || (tags == null ? s.RequiredTag.IsEmpty : tags.Contains(s.RequiredTag))))
{
tempList.Add(s);
}
}
var damageSound = tempList.GetRandomUnsynced();
//if the damage is too low for any sound, don't play anything
if (damageSounds.All(d => damage < d.DamageRange.X)) { return; }
//allow the damage to differ by 10 from the configured damage range,
//so the same amount of damage doesn't always play the same sound
float randomizedDamage = MathHelper.Clamp(damage + Rand.Range(-10.0f, 10.0f), 0.0f, 100.0f);
var suitableSounds = damageSounds.Where(s =>
s.DamageType == damageType &&
(s.DamageRange == Vector2.Zero || (randomizedDamage >= s.DamageRange.X && randomizedDamage <= s.DamageRange.Y)) &&
(s.RequiredTag.IsEmpty || (tags == null ? s.RequiredTag.IsEmpty : tags.Contains(s.RequiredTag))));
var damageSound = suitableSounds.GetRandomUnsynced();
damageSound?.Sound?.Play(1.0f, range, position, muffle: !damageSound.IgnoreMuffling && ShouldMuffleSound(Character.Controlled, position, range, null));
}

View File

@@ -512,5 +512,34 @@ namespace Barotrauma
return new Vector2(paddingX, paddingY);
}
public static string ColorSectionOfString(string text, int start, int length, Color color)
{
int end = start + length;
if (start < 0 || length < 0 || end > text.Length)
{
throw new ArgumentOutOfRangeException($"Invalid start ({start}) or length ({length}) for text \"{text}\".");
}
string stichedString = string.Empty;
if (start > 0)
{
stichedString += text[..start];
}
// this is the highlighted part
stichedString += ColorString(text[start..end], color);
if (end < text.Length)
{
stichedString += text[end..];
}
return stichedString;
static string ColorString(string text, Color color) => $"‖color:{color.ToStringHex()}‖{text}‖end‖";
}
}
}

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.20.9.0</Version>
<Version>0.20.10.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.20.9.0</Version>
<Version>0.20.10.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.20.9.0</Version>
<Version>0.20.10.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>
@@ -117,7 +117,6 @@
<ProjectReference Include="..\..\Libraries\Lidgren.Network\Lidgren.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
<ProjectReference Include="..\..\Libraries\MonoGame.Framework\Src\MonoGame.Framework\MonoGame.Framework.Windows.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
<ProjectReference Include="..\..\Libraries\SharpFont\Source\SharpFont\SharpFont.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
<ProjectReference Include="..\..\Libraries\ImeSharp\ImeSharp.csproj" AdditionalProperties="Configuration=Release" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<ProjectReference Include="..\..\Libraries\Concentus\CSharp\Concentus\Concentus.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
@@ -127,7 +126,6 @@
<ProjectReference Include="..\..\Libraries\Lidgren.Network\Lidgren.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
<ProjectReference Include="..\..\Libraries\MonoGame.Framework\Src\MonoGame.Framework\MonoGame.Framework.Windows.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
<ProjectReference Include="..\..\Libraries\SharpFont\Source\SharpFont\SharpFont.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
<ProjectReference Include="..\..\Libraries\ImeSharp\ImeSharp.csproj" AdditionalProperties="Configuration=Debug" />
</ItemGroup>
<ItemGroup>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.20.9.0</Version>
<Version>0.20.10.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.20.9.0</Version>
<Version>0.20.10.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.20.9.0</Version>
<Version>0.20.10.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -30,8 +30,7 @@ namespace Barotrauma.Abilities
NotSelf = 3,
Alive = 4,
Monster = 5,
InFriendlySubmarine = 6,
Large = 7,
InFriendlySubmarine = 6
};
protected List<TargetType> ParseTargetTypes(string[] targetTypeStrings)
@@ -80,9 +79,6 @@ namespace Barotrauma.Abilities
return !targetCharacter.IsHuman;
case TargetType.InFriendlySubmarine:
return targetCharacter.Submarine != null && targetCharacter.Submarine.TeamID == character.TeamID;
case TargetType.Large:
// mass of mudraptor is ~48
return targetCharacter.AnimController is { Mass: > 50.0f };
default:
return true;
}

View File

@@ -1,21 +1,25 @@
using System;
using Barotrauma.Extensions;
using System;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionItem : AbilityConditionData
{
private readonly string[] identifiers;
private readonly string[] tags;
private readonly ImmutableArray<Identifier> identifiers;
private readonly ImmutableArray<Identifier> tags;
private readonly MapEntityCategory category = MapEntityCategory.None;
public AbilityConditionItem(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
{
identifiers = conditionElement.GetAttributeStringArray("identifiers", Array.Empty<string>(), convertToLowerInvariant: true);
tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
identifiers = conditionElement.GetAttributeIdentifierArray("identifiers", Array.Empty<Identifier>()).ToImmutableArray();
tags = conditionElement.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>()).ToImmutableArray();
category = conditionElement.GetAttributeEnum("category", MapEntityCategory.None);
if (!identifiers.Any() && !tags.Any())
if (identifiers.None() && tags.None() && category == MapEntityCategory.None)
{
DebugConsole.ThrowError($"Error in talent \"{characterTalent}\". No identifiers or tags defined.");
DebugConsole.ThrowError($"Error in talent \"{characterTalent}\". No identifiers, tags or category defined.");
}
}
@@ -33,6 +37,11 @@ namespace Barotrauma.Abilities
if (itemPrefab != null)
{
if (category != MapEntityCategory.None)
{
if (!itemPrefab.Category.HasFlag(category)) { return false; }
}
if (identifiers.Any())
{
if (!identifiers.Any(t => itemPrefab.Identifier == t))
@@ -40,7 +49,6 @@ namespace Barotrauma.Abilities
return false;
}
}
return !tags.Any() || tags.Any(t => itemPrefab.Tags.Any(p => t == p));
}
else

View File

@@ -10,7 +10,7 @@ namespace Barotrauma.Abilities
{
foreach (Character c in GameSession.GetSessionCrewCharacters(CharacterType.Both))
{
if (c.IsUnconscious)
if (!c.IsDead && c.IsUnconscious)
{
return true;
}

View File

@@ -34,7 +34,7 @@ namespace Barotrauma.Abilities
public bool IsViable()
{
if (!AllowClientSimulation && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
if (!AllowClientSimulation && GameMain.NetworkMember is { IsClient: true }) { return false; }
if (RequiresAlive && Character.IsDead) { return false; }
return true;
}

View File

@@ -8,6 +8,8 @@ namespace Barotrauma.Abilities
{
internal sealed class CharacterAbilityUnlockApprenticeshipTalentTree : CharacterAbility
{
public override bool AllowClientSimulation => false;
public CharacterAbilityUnlockApprenticeshipTalentTree(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement) { }
public override void InitializeAbility(bool addingFirstTime)

View File

@@ -417,14 +417,7 @@ namespace Barotrauma.Items.Components
item.body.FarseerBody.OnCollision += OnProjectileCollision;
item.body.FarseerBody.IsBullet = true;
item.body.CollisionCategories = Physics.CollisionProjectile;
item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking;
if (item.Prefab.DamagedByProjectiles && !IgnoreProjectilesWhileActive)
{
item.body.CollidesWith |= Physics.CollisionProjectile;
}
EnableProjectileCollisions();
IsActive = true;
if (stickJoint == null) { return; }
@@ -549,11 +542,10 @@ namespace Barotrauma.Items.Components
}
else if (fixture?.Body == null || fixture.IsSensor)
{
//ignore sensors and items
//ignore sensors
return true;
}
if (fixture.Body.UserData is VineTile) { return true; }
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return true; }
if (fixture.Body.UserData as string == "ruinroom" || fixture.Body.UserData is Hull || fixture.UserData is Hull) { return true; }
//if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub
@@ -563,13 +555,28 @@ namespace Barotrauma.Items.Components
if (fixture.Body.UserData is Entity entity && entity.Submarine != submarine) { return true; }
}
//ignore everything else than characters, sub walls and level walls
if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) &&
!fixture.CollisionCategories.HasFlag(Physics.CollisionWall) &&
!fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return true; }
if (fixture.Body.UserData is VoronoiCell && (this.item.Submarine != null || submarine != null)) { return true; }
if (fixture.Body.UserData is Item item)
{
if (item == Item) { return true; }
if (item.Condition <= 0) { return true; }
if (!item.Prefab.DamagedByProjectiles && item.GetComponent<Door>() == null) { return true; }
}
else if (fixture.Body.UserData is Holdable { CanPush: false })
{
// Ignore holdables that can't push -> shouldn't block
return true;
}
else
{
// TODO: This might make us ignore something we don't want to ignore?
// Not item -> ignore everything else than characters, sub walls and level walls
if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) &&
!fixture.CollisionCategories.HasFlag(Physics.CollisionWall) &&
!fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return true; }
}
fixture.Body.GetTransform(out FarseerPhysics.Common.Transform transform);
if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; }
@@ -586,20 +593,16 @@ namespace Barotrauma.Items.Components
}
else if (fixture?.Body == null || fixture.IsSensor)
{
//ignore sensors and items
//ignore sensors
return -1;
}
if (fixture.Body.UserData is VineTile) { return -1; }
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return -1; }
if (fixture.Body.UserData as string == "ruinroom" || fixture.Body?.UserData is Hull || fixture.UserData is Hull) { return -1; }
if (!(fixture.Body.UserData is Holdable holdable && holdable.CanPush))
if (fixture.Body.UserData is Item item)
{
//ignore everything else than characters, sub walls and level walls
if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) &&
!fixture.CollisionCategories.HasFlag(Physics.CollisionWall) &&
!fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return -1; }
if (item.Condition <= 0) { return -1; }
if (!item.Prefab.DamagedByProjectiles && item.GetComponent<Door>() == null) { return -1; }
}
if (fixture.Body.UserData as string == "ruinroom" || fixture.Body?.UserData is Hull || fixture.UserData is Hull) { return -1; }
//if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub
if (submarine != null)
@@ -609,6 +612,12 @@ namespace Barotrauma.Items.Components
if (fixture.Body.UserData is Limb limb && limb.character?.Submarine != submarine) { return -1; }
}
// Ignore holdables that can't push -> shouldn't block
if (fixture.Body.UserData is Holdable { CanPush: false })
{
return -1;
}
//ignore level cells if the item and the point of impact are inside a sub
if (fixture.Body.UserData is VoronoiCell)
{
@@ -639,7 +648,7 @@ namespace Barotrauma.Items.Components
hits.Add(new HitscanResult(fixture, point, normal, fraction));
return 1;
}, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking);
}, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking | Physics.CollisionProjectile);
return hits;
}
@@ -759,6 +768,12 @@ namespace Barotrauma.Items.Components
else if (target.Body.UserData is Item item)
{
if (item.Condition <= 0.0f) { return false; }
if (!item.Prefab.DamagedByProjectiles) { return false; }
}
else if (target.Body.UserData is Holdable { CanPush: false })
{
// Ignore holdables that can't push -> shouldn't block
return false;
}
//ignore character colliders (the projectile only hits limbs)
@@ -774,7 +789,7 @@ namespace Barotrauma.Items.Components
{
item.body.FarseerBody.ResetDynamics();
}
if (hits.Count() >= MaxTargetsToHit || target.Body.UserData is VoronoiCell)
if (hits.Count >= MaxTargetsToHit || target.Body.UserData is VoronoiCell)
{
DisableProjectileCollisions();
return true;
@@ -1047,6 +1062,16 @@ namespace Barotrauma.Items.Components
return true;
}
private void EnableProjectileCollisions()
{
item.body.CollisionCategories = Physics.CollisionProjectile;
item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking;
if (!IgnoreProjectilesWhileActive)
{
item.body.CollidesWith |= Physics.CollisionProjectile;
}
}
private void DisableProjectileCollisions()
{
item.body.FarseerBody.OnCollision -= OnProjectileCollision;

View File

@@ -10,6 +10,7 @@ namespace Barotrauma
[Flags]
enum MapEntityCategory
{
None = 0,
Structure = 1,
Decorative = 2,
Machine = 4,

View File

@@ -35,6 +35,7 @@
{
GUI.DisableSavingIndicatorDelayed();
}
GameMain.ResetIMEWorkaround();
#endif
}

View File

@@ -1,3 +1,28 @@
---------------------------------------------------------------------------------------------------------
v0.20.10.0
---------------------------------------------------------------------------------------------------------
Input Method Editor (Chinese/Japanese input) fixes:
- Fixed inputs sometimes not working. Happened because listboxes stole keyboard focus, for example when rearranging bot orders or interacting with a chatbox.
- Fixed moving the input position with arrow keys in the candidate box moving the caret in the textbox too.
- Fixed current composition text disappearing when you browse through the options with arrow keys.
- Fixed candidate box not being hidden when there's no input (= when you've just selected the textbox or committed the input).
Fixes/improvements to depth charges:
- Fixed projectile-on-projectile collisions not working properly.
- Reduced the health of the depth charges (100 -> 10) to make them easier to detonate with turrets.
- Made decoys self-destruct instead of just becoming disabled after 45 seconds, turning them into time bombs.
- Fixed depth charges disappearing with a delay (after the explosion particles had gone away, making it easy to see them vanish).
- Fixed regular depth charges being destructible before launching.
Misc fixes:
- Fixed "Graduation Ceremony" talent unlocking 2 talent trees instead of one.
- Fixed Scrap Cannon's firing sound being barely audibly.
- Fixed "Better Than New" allowing you to repair any device past the maximum condition, not just electrical devices.
- Adjustments to Harpoon Coil-rifle sounds.
- Fixed some sprite bleed issues in the new talent icons.
- Fixed Kastrull's reactor having 1000 condition instead of 100.
---------------------------------------------------------------------------------------------------------
v0.20.9.0
---------------------------------------------------------------------------------------------------------

View File

@@ -1,201 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace ImeSharp
{
public unsafe struct IMEString : IEnumerable<char>
{
internal const int IMECharBufferSize = 64;
public static readonly IMEString Empty = new IMEString((List<char>)null);
internal struct Enumerator : IEnumerator<char>
{
private IMEString _imeString;
private char _currentCharacter;
private int _currentIndex;
public Enumerator(IMEString imeString)
{
_imeString = imeString;
_currentCharacter = '\0';
_currentIndex = -1;
}
public bool MoveNext()
{
int size = _imeString.Count;
_currentIndex++;
if (_currentIndex == size)
return false;
fixed (char* ptr = _imeString.buffer)
{
_currentCharacter = *(ptr + _currentIndex);
}
return true;
}
public void Reset()
{
_currentIndex = -1;
}
public void Dispose()
{
}
public char Current { get { return _currentCharacter; } }
object IEnumerator.Current { get { return Current; } }
}
public int Count { get { return _size; } }
public char this[int index]
{
get
{
if (index >= Count || index < 0)
throw new ArgumentOutOfRangeException("index");
fixed (char* ptr = buffer)
{
return *(ptr + index);
}
}
}
private int _size;
fixed char buffer[IMECharBufferSize];
public IMEString(string characters)
{
if (string.IsNullOrEmpty(characters))
{
_size = 0;
return;
}
_size = characters.Length;
if (_size > IMECharBufferSize)
_size = IMECharBufferSize - 1;
fixed (char* _ptr = buffer)
{
char* ptr = _ptr;
for (var i = 0; i < _size; i++)
{
*ptr = characters[i];
ptr++;
}
}
}
public IMEString(List<char> characters)
{
if (characters == null || characters.Count == 0)
{
_size = 0;
return;
}
_size = characters.Count;
if (_size > IMECharBufferSize)
_size = IMECharBufferSize - 1;
fixed (char* _ptr = buffer)
{
char* ptr = _ptr;
for (var i = 0; i < _size; i++)
{
*ptr = characters[i];
ptr++;
}
}
}
public IMEString(char[] characters, int count)
{
if (characters == null || count <= 0)
{
_size = 0;
return;
}
_size = count;
if (_size > IMECharBufferSize)
_size = IMECharBufferSize - 1;
if (_size > characters.Length)
_size = characters.Length;
fixed (char* _ptr = buffer)
{
char* ptr = _ptr;
for (var i = 0; i < _size; i++)
{
*ptr = characters[i];
ptr++;
}
}
}
public IMEString(IntPtr bStrPtr)
{
if (bStrPtr == IntPtr.Zero)
{
_size = 0;
return;
}
var ptrSrc = (char*)bStrPtr;
int i = 0;
fixed (char* _ptr = buffer)
{
char* ptr = _ptr;
while (ptrSrc[i] != '\0')
{
*ptr = ptrSrc[i];
i++;
ptr++;
}
}
_size = i;
}
public override string ToString()
{
fixed (char* ptr = buffer)
{
return new string(ptr, 0, _size);
}
}
public IntPtr ToIntPtr()
{
fixed (char* ptr = buffer)
{
return (IntPtr)ptr;
}
}
public IEnumerator<char> GetEnumerator()
{
return new Enumerator(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -1,61 +0,0 @@
using System;
namespace ImeSharp
{
/// <summary>
/// Arguments for the <see cref="IImmService.TextComposition" /> event.
/// </summary>
public struct IMETextCompositionEventArgs
{
/// <summary>
// Construct a TextCompositionEventArgs with composition infos.
/// </summary>
public IMETextCompositionEventArgs(IMEString compositionText,
int cursorPosition,
IMEString[] candidateList = null,
int candidatePageStart = 0,
int candidatePageSize = 0,
int candidateSelection = 0)
{
CompositionText = compositionText;
CursorPosition = cursorPosition;
CandidateList = candidateList;
CandidatePageStart = candidatePageStart;
CandidatePageSize = candidatePageSize;
CandidateSelection = candidateSelection;
}
/// <summary>
/// The full string as it's composed by the IMM.
/// </summary>
public readonly IMEString CompositionText;
/// <summary>
/// The position of the cursor inside the composed string.
/// </summary>
public readonly int CursorPosition;
/// <summary>
/// The candidate text list for the composition.
/// This property is only supported on WindowsDX and WindowsUniversal.
/// If the composition string does not generate candidates this array is empty.
/// </summary>
public readonly IMEString[] CandidateList;
/// <summary>
/// First candidate index of current page.
/// </summary>
public readonly int CandidatePageStart;
/// <summary>
/// How many candidates should display per page.
/// </summary>
public readonly int CandidatePageSize;
/// <summary>
/// The selected candidate index.
/// </summary>
public readonly int CandidateSelection;
}
}

View File

@@ -1,12 +0,0 @@
namespace ImeSharp
{
public struct IMETextInputEventArgs
{
public IMETextInputEventArgs(char character)
{
Character = character;
}
public readonly char Character;
}
}

View File

@@ -1,30 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<Description>C# wrapper for Windows IME APIs. Its goal is to support both IMM32 and TSF.</Description>
<PackageTags>IME;netcoreapp3.1;net5.0;net6.0;winforms;windows;tsf;imm32</PackageTags>
<PackageId>ImeSharp</PackageId>
<PackageProjectUrl>https://github.com/ryancheung/ImeSharp</PackageProjectUrl>
<RepositoryUrl>https://github.com/ryancheung/ImeSharp</RepositoryUrl>
<Authors>ryancheung</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup>
<BaseOutputPath>..\Artifacts</BaseOutputPath>
<AssemblyName>ImeSharp</AssemblyName>
<RootNamespace>ImeSharp</RootNamespace>
<LangVersion>5</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TsfSharp" Version="1.0.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,349 +0,0 @@
using System;
using System.Buffers;
using System.Runtime.InteropServices;
using System.Globalization;
using System.Diagnostics;
using ImeSharp.Native;
namespace ImeSharp
{
internal class Imm32Manager
{
// If the system is IMM enabled, this is true.
private static bool _immEnabled = SafeSystemMetrics.IsImmEnabled;
public static bool ImmEnabled { get { return _immEnabled; } }
public const int LANG_CHINESE = 0x04;
public const int LANG_KOREAN = 0x12;
public const int LANG_JAPANESE = 0x11;
public static int PRIMARYLANGID(int lgid)
{
return ((ushort)(lgid) & 0x3ff);
}
static Imm32Manager()
{
SetCurrentCulture();
}
/// <summary>
/// return true if the current keyboard layout is a real IMM32-IME.
/// </summary>
public static bool IsImm32ImeCurrent()
{
if (!_immEnabled)
return false;
IntPtr hkl = NativeMethods.GetKeyboardLayout(0);
return IsImm32Ime(hkl);
}
/// <summary>
/// return true if the keyboard layout is a real IMM32-IME.
/// </summary>
public static bool IsImm32Ime(IntPtr hkl)
{
if (hkl == IntPtr.Zero)
return false;
return ((NativeMethods.IntPtrToInt32(hkl) & 0xf0000000) == 0xe0000000);
}
private static int _inputLanguageId;
internal static void SetCurrentCulture()
{
var hkl = NativeMethods.GetKeyboardLayout(0);
_inputLanguageId = NativeMethods.IntPtrToInt32(hkl) & 0xFFFF;
}
private IntPtr _windowHandle;
private IntPtr _defaultImc;
private IntPtr DefaultImc
{
get
{
if (_defaultImc == IntPtr.Zero)
{
IntPtr himc = NativeMethods.ImmCreateContext();
// Store the default imc to _defaultImc.
_defaultImc = himc;
}
return _defaultImc;
}
}
private static ImmCompositionStringHandler _compositionStringHandler;
private static ImmCompositionIntHandler _compositionCursorHandler;
private bool _lastImmOpenStatus;
public Imm32Manager(IntPtr windowHandle)
{
_windowHandle = windowHandle;
_compositionStringHandler = new ImmCompositionStringHandler(DefaultImc, NativeMethods.GCS_COMPSTR);
_compositionCursorHandler = new ImmCompositionIntHandler(DefaultImc, NativeMethods.GCS_CURSORPOS);
}
public static Imm32Manager Current
{
get
{
var defaultImm32Manager = InputMethod.DefaultImm32Manager;
if (defaultImm32Manager == null)
{
defaultImm32Manager = new Imm32Manager(InputMethod.WindowHandle);
InputMethod.DefaultImm32Manager = defaultImm32Manager;
}
return defaultImm32Manager;
}
}
public void Enable()
{
if (DefaultImc != IntPtr.Zero)
{
// Create a temporary system caret
NativeMethods.CreateCaret(_windowHandle, IntPtr.Zero, 2, 10);
NativeMethods.ImmAssociateContext(_windowHandle, _defaultImc);
}
}
public void Disable()
{
NativeMethods.ImmAssociateContext(_windowHandle, IntPtr.Zero);
NativeMethods.DestroyCaret();
}
const int kCaretMargin = 1;
// Set candidate window position.
// Borrowed from https://github.com/chromium/chromium/blob/master/ui/base/ime/win/imm32_manager.cc
public void SetCandidateWindow(TsfSharp.Rect caretRect)
{
int x = caretRect.Left;
int y = caretRect.Top;
if (PRIMARYLANGID(_inputLanguageId) == LANG_CHINESE)
{
// Chinese IMEs ignore function calls to ::ImmSetCandidateWindow()
// when a user disables TSF (Text Service Framework) and CUAS (Cicero
// Unaware Application Support).
// On the other hand, when a user enables TSF and CUAS, Chinese IMEs
// ignore the position of the current system caret and uses the
// parameters given to ::ImmSetCandidateWindow() with its 'dwStyle'
// parameter CFS_CANDIDATEPOS.
// Therefore, we do not only call ::ImmSetCandidateWindow() but also
// set the positions of the temporary system caret.
var candidateForm = new NativeMethods.CANDIDATEFORM();
candidateForm.dwStyle = NativeMethods.CFS_CANDIDATEPOS;
candidateForm.ptCurrentPos.X = x;
candidateForm.ptCurrentPos.Y = y;
NativeMethods.ImmSetCandidateWindow(_defaultImc, ref candidateForm);
}
if (PRIMARYLANGID(_inputLanguageId) == LANG_JAPANESE)
NativeMethods.SetCaretPos(x, caretRect.Bottom);
else
NativeMethods.SetCaretPos(x, y);
// Set composition window position also to ensure move the candidate window position.
var compositionForm = new NativeMethods.COMPOSITIONFORM();
compositionForm.dwStyle = NativeMethods.CFS_POINT;
compositionForm.ptCurrentPos.X = x;
compositionForm.ptCurrentPos.Y = y;
NativeMethods.ImmSetCompositionWindow(_defaultImc, ref compositionForm);
if (PRIMARYLANGID(_inputLanguageId) == LANG_KOREAN)
{
// Chinese IMEs and Japanese IMEs require the upper-left corner of
// the caret to move the position of their candidate windows.
// On the other hand, Korean IMEs require the lower-left corner of the
// caret to move their candidate windows.
y += kCaretMargin;
}
// Need to return here since some Chinese IMEs would stuck if set
// candidate window position with CFS_EXCLUDE style.
if (PRIMARYLANGID(_inputLanguageId) == LANG_CHINESE) return;
// Japanese IMEs and Korean IMEs also use the rectangle given to
// ::ImmSetCandidateWindow() with its 'dwStyle' parameter CFS_EXCLUDE
// to move their candidate windows when a user disables TSF and CUAS.
// Therefore, we also set this parameter here.
var excludeRectangle = new NativeMethods.CANDIDATEFORM();
compositionForm.dwStyle = NativeMethods.CFS_EXCLUDE;
compositionForm.ptCurrentPos.X = x;
compositionForm.ptCurrentPos.Y = y;
compositionForm.rcArea.Left = x;
compositionForm.rcArea.Top = y;
compositionForm.rcArea.Right = caretRect.Right;
compositionForm.rcArea.Bottom = caretRect.Bottom;
NativeMethods.ImmSetCandidateWindow(_defaultImc, ref excludeRectangle);
}
internal bool ProcessMessage(IntPtr hWnd, uint msg, ref IntPtr wParam, ref IntPtr lParam)
{
switch (msg)
{
case NativeMethods.WM_INPUTLANGCHANGE:
SetCurrentCulture();
break;
case NativeMethods.WM_IME_SETCONTEXT:
if (wParam.ToInt32() == 1 && InputMethod.Enabled)
{
// Must re-associate ime context or things won't work.
NativeMethods.ImmAssociateContext(_windowHandle, DefaultImc);
if (_lastImmOpenStatus)
NativeMethods.ImmSetOpenStatus(DefaultImc, true);
var lParam64 = lParam.ToInt64();
if (!InputMethod.ShowOSImeWindow)
lParam64 &= ~NativeMethods.ISC_SHOWUICANDIDATEWINDOW;
else
lParam64 &= ~NativeMethods.ISC_SHOWUICOMPOSITIONWINDOW;
lParam = (IntPtr)(int)lParam64;
}
break;
case NativeMethods.WM_KILLFOCUS:
_lastImmOpenStatus = NativeMethods.ImmGetOpenStatus(DefaultImc);
break;
case NativeMethods.WM_IME_NOTIFY:
IMENotify(wParam.ToInt32());
if (!InputMethod.ShowOSImeWindow)
return true;
break;
case NativeMethods.WM_IME_STARTCOMPOSITION:
//Debug.WriteLine("NativeMethods.WM_IME_STARTCOMPOSITION");
IMEStartComposion(lParam.ToInt32());
// Force to not show composition window, `lParam64 &= ~ISC_SHOWUICOMPOSITIONWINDOW` don't work sometime.
return true;
case NativeMethods.WM_IME_COMPOSITION:
//Debug.WriteLine("NativeMethods.WM_IME_COMPOSITION");
IMEComposition(lParam.ToInt32());
break;
case NativeMethods.WM_IME_ENDCOMPOSITION:
//Debug.WriteLine("NativeMethods.WM_IME_ENDCOMPOSITION");
IMEEndComposition(lParam.ToInt32());
if (!InputMethod.ShowOSImeWindow)
return true;
break;
}
return false;
}
private void IMENotify(int WParam)
{
switch (WParam)
{
case NativeMethods.IMN_OPENCANDIDATE:
case NativeMethods.IMN_CHANGECANDIDATE:
IMEChangeCandidate();
break;
case NativeMethods.IMN_CLOSECANDIDATE:
InputMethod.ClearCandidates();
break;
default:
break;
}
}
private void IMEChangeCandidate()
{
if (TextServicesLoader.ServicesInstalled) // TSF is enabled
{
if (!TextStore.Current.SupportUIElement) // But active IME not support UIElement
UpdateCandidates(); // We have to fetch candidate list here.
return;
}
// Normal candidate list fetch in IMM32
UpdateCandidates();
// Send event on candidate updates
InputMethod.OnTextComposition(this, new IMEString(_compositionStringHandler.Values, _compositionStringHandler.Count), _compositionCursorHandler.Value);
if (InputMethod.CandidateList != null)
ArrayPool<IMEString>.Shared.Return(InputMethod.CandidateList);
}
private unsafe void UpdateCandidates()
{
uint length = NativeMethods.ImmGetCandidateList(DefaultImc, 0, IntPtr.Zero, 0);
if (length > 0)
{
IntPtr pointer = Marshal.AllocHGlobal((int)length);
length = NativeMethods.ImmGetCandidateList(DefaultImc, 0, pointer, length);
NativeMethods.CANDIDATELIST* cList = (NativeMethods.CANDIDATELIST*)pointer;
var selection = (int)cList->dwSelection;
var pageStart = (int)cList->dwPageStart;
var pageSize = (int)cList->dwPageSize;
selection -= pageStart;
IMEString[] candidates = ArrayPool<IMEString>.Shared.Rent(pageSize);
int i, j;
for (i = pageStart, j = 0; i < cList->dwCount && j < pageSize; i++, j++)
{
int sOffset = Marshal.ReadInt32(pointer, 24 + 4 * i);
candidates[j] = new IMEString(pointer + sOffset);
}
//Debug.WriteLine("IMM========IMM");
//Debug.WriteLine("pageStart: {0}, pageSize: {1}, selection: {2}, candidates:", pageStart, pageSize, selection);
//for (int k = 0; k < candidates.Length; k++)
// Debug.WriteLine(" {2}{0}.{1}", k + 1, candidates[k], k == selection ? "*" : "");
//Debug.WriteLine("IMM++++++++IMM");
InputMethod.CandidatePageStart = pageStart;
InputMethod.CandidatePageSize = pageSize;
InputMethod.CandidateSelection = selection;
InputMethod.CandidateList = candidates;
Marshal.FreeHGlobal(pointer);
}
}
private void ClearComposition()
{
_compositionStringHandler.Clear();
}
private void IMEStartComposion(int lParam)
{
InputMethod.OnTextCompositionStarted(this);
ClearComposition();
}
private void IMEComposition(int lParam)
{
if (_compositionStringHandler.Update(lParam))
{
_compositionCursorHandler.Update();
InputMethod.OnTextComposition(this, new IMEString(_compositionStringHandler.Values, _compositionStringHandler.Count), _compositionCursorHandler.Value);
}
}
private void IMEEndComposition(int lParam)
{
InputMethod.ClearCandidates();
ClearComposition();
InputMethod.OnTextCompositionEnded(this);
}
}
}

View File

@@ -1,119 +0,0 @@
using System;
using System.Text;
using System.Runtime.InteropServices;
using ImeSharp.Native;
namespace ImeSharp
{
internal abstract class ImmCompositionResultHandler
{
protected IntPtr _imeContext;
public int Flag { get; private set; }
internal ImmCompositionResultHandler(IntPtr imeContext, int flag)
{
this.Flag = flag;
_imeContext = imeContext;
}
internal virtual void Update() { }
internal bool Update(int lParam)
{
if ((lParam & Flag) == Flag)
{
Update();
return true;
}
return false;
}
}
internal class ImmCompositionStringHandler : ImmCompositionResultHandler
{
internal const int BufferSize = 1024;
private byte[] _byteBuffer = new byte[BufferSize];
private int _byteCount;
private char[] _charBuffer = new char[BufferSize / 2];
private int _charCount;
public char[] Values { get { return _charBuffer; } }
public int Count { get { return _charCount; } }
public char this[int index]
{
get
{
if (index >= _charCount || index < 0)
throw new ArgumentOutOfRangeException("index");
return _charBuffer[index];
}
}
internal ImmCompositionStringHandler(IntPtr imeContext, int flag) : base(imeContext, flag)
{
}
public override string ToString()
{
if (_charCount <= 0)
return string.Empty;
return new string(_charBuffer, 0, _charCount);
}
internal void Clear()
{
Array.Clear(_byteBuffer, 0, _byteCount);
_byteCount = 0;
Array.Clear(_charBuffer, 0, _charCount);
_charCount = 0;
}
internal override void Update()
{
_byteCount = NativeMethods.ImmGetCompositionString(_imeContext, Flag, IntPtr.Zero, 0);
IntPtr pointer = Marshal.AllocHGlobal(_byteCount);
try
{
Array.Clear(_byteBuffer, 0, _byteCount);
if (_byteCount > 0)
{
NativeMethods.ImmGetCompositionString(_imeContext, Flag, pointer, _byteCount);
Marshal.Copy(pointer, _byteBuffer, 0, _byteCount);
Array.Clear(_charBuffer, 0, _charCount);
_charCount = Encoding.Unicode.GetChars(_byteBuffer, 0, _byteCount, _charBuffer, 0);
}
}
finally
{
Marshal.FreeHGlobal(pointer);
}
}
}
internal class ImmCompositionIntHandler : ImmCompositionResultHandler
{
public int Value { get; private set; }
internal ImmCompositionIntHandler(IntPtr imeContext, int flag) : base(imeContext, flag) { }
public override string ToString()
{
return Value.ToString();
}
internal override void Update()
{
Value = NativeMethods.ImmGetCompositionString(_imeContext, Flag, IntPtr.Zero, 0);
}
}
}

View File

@@ -1,246 +0,0 @@
using System;
using System.Globalization;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using ImeSharp.Native;
namespace ImeSharp
{
public static class InputMethod
{
private static IntPtr _windowHandle;
public static IntPtr WindowHandle { get { return _windowHandle; } }
private static IntPtr _prevWndProc;
private static NativeMethods.WndProcDelegate _wndProcDelegate;
private static TextServicesContext _textServicesContext;
internal static TextServicesContext TextServicesContext
{
get { return _textServicesContext; }
set { _textServicesContext = value; }
}
private static TextStore _defaultTextStore;
internal static TextStore DefaultTextStore
{
get { return _defaultTextStore; }
set { _defaultTextStore = value; }
}
private static Imm32Manager _defaultImm32Manager;
internal static Imm32Manager DefaultImm32Manager
{
get { return _defaultImm32Manager; }
set { _defaultImm32Manager = value; }
}
private static bool _enabled;
public static bool Enabled
{
get { return _enabled; }
set
{
if (_enabled == value) return;
_enabled = value;
EnableOrDisableInputMethod(_enabled);
}
}
internal static TsfSharp.Rect TextInputRect;
/// <summary>
/// Set the position of the candidate window rendered by the OS.
/// Let the OS render the candidate window by set param "showOSImeWindow" to <c>true</c> on <see cref="Initialize"/>.
/// </summary>
public static void SetTextInputRect(int x, int y, int width, int height)
{
if (!_showOSImeWindow) return;
TextInputRect.Left = x;
TextInputRect.Top = y;
TextInputRect.Right = x + width;
TextInputRect.Bottom = y + height;
if (Imm32Manager.ImmEnabled)
Imm32Manager.Current.SetCandidateWindow(TextInputRect);
}
private static bool _showOSImeWindow = false;
/// <summary>
/// Return if let OS render IME Candidate window or not.
/// </summary>
public static bool ShowOSImeWindow { get { return _showOSImeWindow; } }
internal static int CandidatePageStart;
internal static int CandidatePageSize;
internal static int CandidateSelection;
internal static IMEString[] CandidateList;
internal static void ClearCandidates()
{
CandidateList = null;
CandidatePageStart = 0;
CandidatePageSize = 0;
CandidateSelection = 0;
}
public static event EventHandler<IMETextCompositionEventArgs> TextComposition;
public static event EventHandler<IMETextInputEventArgs> TextInput;
public static event EventHandler<string> CommitTextComposition;
public static TextInputCallback TextInputCallback { get; set; }
public static TextCompositionCallback TextCompositionCallback { get; set; }
public static CommitTextCompositionCallback CommitTextCompositionCallback { get; set; }
/// <summary>
/// Initialize InputMethod with a Window Handle.
/// Let the OS render the candidate window by set <see paramref="showOSImeWindow"/> to <c>true</c>.
/// </summary>
public static void Initialize(IntPtr windowHandle, bool showOSImeWindow = true)
{
if (_windowHandle != IntPtr.Zero)
throw new InvalidOperationException("InputMethod can only be initialized once!");
_windowHandle = windowHandle;
_showOSImeWindow = showOSImeWindow;
_wndProcDelegate = new NativeMethods.WndProcDelegate(WndProc);
_prevWndProc = (IntPtr)NativeMethods.SetWindowLongPtr(_windowHandle, NativeMethods.GWL_WNDPROC,
Marshal.GetFunctionPointerForDelegate(_wndProcDelegate));
}
internal static void OnTextInput(object sender, char character)
{
if (TextInput != null)
TextInput.Invoke(sender, new IMETextInputEventArgs(character));
if (TextInputCallback != null)
TextInputCallback(character);
}
// Some Chinese IME only send composition start event but no composition update event.
// We need this to ensure candidate window position can be set in time.
internal static void OnTextCompositionStarted(object sender)
{
if (TextComposition != null)
TextComposition.Invoke(sender, new IMETextCompositionEventArgs(IMEString.Empty, 0));
if (TextCompositionCallback != null)
TextCompositionCallback(IMEString.Empty, 0, null, 0, 0, 0);
}
// On text composition update.
internal static void OnTextComposition(object sender, IMEString compositionText, int cursorPos)
{
if (compositionText.Count == 0) // Crash guard
cursorPos = 0;
if (cursorPos > compositionText.Count) // Another crash guard
cursorPos = compositionText.Count;
if (TextComposition != null)
{
TextComposition.Invoke(sender,
new IMETextCompositionEventArgs(compositionText, cursorPos, CandidateList, CandidatePageStart, CandidatePageSize, CandidateSelection));
}
if (TextCompositionCallback != null)
TextCompositionCallback(compositionText, cursorPos, CandidateList, CandidatePageStart, CandidatePageSize, CandidateSelection);
}
internal static void OnTextCompositionResult(object sender, string compositionResult)
{
if (CommitTextComposition != null)
CommitTextComposition.Invoke(sender, compositionResult);
if (CommitTextCompositionCallback != null)
CommitTextCompositionCallback(compositionResult);
}
internal static void OnTextCompositionEnded(object sender)
{
if (TextComposition != null)
TextComposition.Invoke(sender, new IMETextCompositionEventArgs(IMEString.Empty, 0));
if (TextCompositionCallback != null)
TextCompositionCallback(IMEString.Empty, 0, null, 0, 0, 0);
}
private static void EnableOrDisableInputMethod(bool bEnabled)
{
// InputMethod enable/disabled status was changed on the current focus Element.
if (TextServicesLoader.ServicesInstalled)
{
if (bEnabled)
TextServicesContext.Current.SetFocusOnDefaultTextStore();
else
TextServicesContext.Current.SetFocusOnEmptyDim();
}
// Under IMM32 enabled system, we associate default hIMC or null hIMC.
if (Imm32Manager.ImmEnabled)
{
if (bEnabled)
Imm32Manager.Current.Enable();
else
Imm32Manager.Current.Disable();
}
}
private static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
if (Imm32Manager.ImmEnabled)
{
if (Imm32Manager.Current.ProcessMessage(hWnd, msg, ref wParam, ref lParam))
return IntPtr.Zero;
}
switch (msg)
{
case NativeMethods.WM_DESTROY:
TextServicesContext.Current.Uninitialize(true);
break;
case NativeMethods.WM_CHAR:
{
if (InputMethod.Enabled)
InputMethod.OnTextInput(null, (char)wParam.ToInt32());
break;
}
}
return NativeMethods.CallWindowProc(_prevWndProc, hWnd, msg, wParam, lParam);
}
/// <summary>
/// Custom windows message pumping to fix frame stuck issue.
/// Normally, you need call this method in <see cref="Application.Idle" /> handler.
/// </summary>
public static void PumpMessage()
{
if (!Enabled) return;
if (!TextServicesLoader.ServicesInstalled) return;
bool result;
var msg = new NativeMethods.NativeMessage();
do
{
result = NativeMethods.PeekMessage(out msg, _windowHandle, 0, 0, NativeMethods.PM_REMOVE);
if (result)
{
NativeMethods.TranslateMessage(ref msg);
NativeMethods.DispatchMessage(ref msg);
}
} while (result);
NativeMethods.PostMessage(_windowHandle, NativeMethods.WM_NULL, IntPtr.Zero, IntPtr.Zero);
}
}
}

View File

@@ -1,149 +0,0 @@
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace ImeSharp.Native
{
public partial class NativeMethods
{
#region Constants
public const int S_OK = 0x00000000;
public const int S_FALSE = 0x00000001;
public const int E_FAIL = unchecked((int)0x80004005);
public const int E_INVALIDARG = unchecked((int)0x80070057);
public const int E_NOTIMPL = unchecked((int)0x80004001);
public const int WM_KEYFIRST = 0x0100;
public const int WM_KEYDOWN = 0x0100;
public const int WM_KEYUP = 0x0101;
public const int WM_CHAR = 0x0102;
public const int WM_DEADCHAR = 0x0103;
public const int WM_SYSKEYDOWN = 0x0104;
public const int WM_SYSKEYUP = 0x0105;
public const int WM_SYSCHAR = 0x0106;
public const int WM_SYSDEADCHAR = 0x0107;
public const int WM_UNICHAR = 0x0109;
public const int WM_KEYLAST = 0x0109;
public const int UNICODE_NOCHAR = 0xFFFF;
public const int WM_NOTIFY = 0x004E;
public const int WM_INPUTLANGCHANGEREQUEST = 0x0050;
public const int WM_INPUTLANGCHANGE = 0x0051;
public const int WM_TCARD = 0x0052;
public const int WM_HELP = 0x0053;
public const int WM_USERCHANGED = 0x0054;
public const int WM_NOTIFYFORMAT = 0x0055;
public const int GWL_WNDPROC = -4;
public const int WM_ACTIVATE = 0x0006;
// WM_ACTIVATE state values
public const int WA_INACTIVE = 0;
public const int WA_ACTIVE = 1;
public const int WA_CLICKACTIVE = 2;
public const int WM_SETFOCUS = 0x0007;
public const int WM_KILLFOCUS = 0x0008;
public const int WM_DESTROY = 0x0002;
public const int WM_NULL = 0x0000;
public const int WM_QUIT = 0x0012;
public const int CLSCTX_INPROC_SERVER = 0x1;
public const int PM_NOREMOVE = 0x0000;
public const int PM_REMOVE = 0x0001;
public const int PM_NOYIELD = 0x0002;
#endregion Constants
#region Structs
[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
public IntPtr handle;
public uint msg;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public int ptX;
public int ptY;
}
#endregion
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(SM nIndex);
// We have this wrapper because casting IntPtr to int may
// generate OverflowException when one of high 32 bits is set.
public static int IntPtrToInt32(IntPtr intPtr)
{
return unchecked((int)intPtr.ToInt64());
}
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
public static extern IntPtr GetKeyboardLayout(int dwLayout);
public delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
// This static method is required because legacy OSes do not support
public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
{
if (IntPtr.Size == 8)
return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
else
return new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
}
[DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Unicode)]
private static extern int SetWindowLong32(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Unicode)]
private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool GetWindowRect(IntPtr hwnd, out TsfSharp.Rect lpRect);
[DllImport("user32", ExactSpelling = true, SetLastError = true)]
public static extern int MapWindowPoints(IntPtr hWndFrom, IntPtr hWndTo, ref TsfSharp.Rect rect, [MarshalAs(UnmanagedType.U4)] int cPoints);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, StringBuilder lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, ref IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool TranslateMessage(ref NativeMessage lpMsg);
[DllImport("user32.dll")]
public static extern IntPtr DispatchMessage(ref NativeMessage lpmsg);
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
public static extern bool PeekMessage(out NativeMessage msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
[DllImport("ole32.dll", ExactSpelling = true, EntryPoint = "CoCreateInstance", PreserveSig = true)]
public static extern int CoCreateInstance([In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, IntPtr pUnkOuter, int dwClsContext, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
}
}

View File

@@ -1,156 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace ImeSharp.Native
{
public partial class NativeMethods
{
#region Constants
public const int WM_IME_SETCONTEXT = 0x0281;
public const int WM_IME_NOTIFY = 0x0282;
public const int WM_IME_CONTROL = 0x0283;
public const int WM_IME_COMPOSITIONFULL = 0x0284;
public const int WM_IME_SELECT = 0x0285;
public const int WM_IME_CHAR = 0x0286;
public const int WM_IME_REQUEST = 0x0288;
public const int WM_IME_KEYDOWN = 0x0290;
public const int WM_IME_KEYUP = 0x0291;
public const int WM_IME_STARTCOMPOSITION = 0x010D;
public const int WM_IME_ENDCOMPOSITION = 0x010E;
public const int WM_IME_COMPOSITION = 0x010F;
public const int WM_IME_KEYLAST = 0x010F;
// wParam of report message WM_IME_NOTIFY
public const int IMN_CLOSESTATUSWINDOW = 0x0001;
public const int IMN_OPENSTATUSWINDOW = 0x0002;
public const int IMN_CHANGECANDIDATE = 0x0003;
public const int IMN_CLOSECANDIDATE = 0x0004;
public const int IMN_OPENCANDIDATE = 0x0005;
public const int IMN_SETCONVERSIONMODE = 0x0006;
public const int IMN_SETSENTENCEMODE = 0x0007;
public const int IMN_SETOPENSTATUS = 0x0008;
public const int IMN_SETCANDIDATEPOS = 0x0009;
public const int IMN_SETCOMPOSITIONFONT = 0x000A;
public const int IMN_SETCOMPOSITIONWINDOW = 0x000B;
public const int IMN_SETSTATUSWINDOWPOS = 0x000C;
public const int IMN_GUIDELINE = 0x000D;
public const int IMN_PRIVATE = 0x000E;
// wParam of report message WM_IME_REQUEST
public const int IMR_COMPOSITIONWINDOW = 0x0001;
public const int IMR_CANDIDATEWINDOW = 0x0002;
public const int IMR_COMPOSITIONFONT = 0x0003;
public const int IMR_RECONVERTSTRING = 0x0004;
public const int IMR_CONFIRMRECONVERTSTRING = 0x0005;
public const int IMR_QUERYCHARPOSITION = 0x0006;
public const int IMR_DOCUMENTFEED = 0x0007;
// parameter of ImmGetCompositionString
public const int GCS_COMPREADSTR = 0x0001;
public const int GCS_COMPREADATTR = 0x0002;
public const int GCS_COMPREADCLAUSE = 0x0004;
public const int GCS_COMPSTR = 0x0008;
public const int GCS_COMPATTR = 0x0010;
public const int GCS_COMPCLAUSE = 0x0020;
public const int GCS_CURSORPOS = 0x0080;
public const int GCS_DELTASTART = 0x0100;
public const int GCS_RESULTREADSTR = 0x0200;
public const int GCS_RESULTREADCLAUSE = 0x0400;
public const int GCS_RESULTSTR = 0x0800;
public const int GCS_RESULTCLAUSE = 0x1000;
public const int GCS_COMP = (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE);
public const int GCS_COMPREAD = (GCS_COMPREADSTR | GCS_COMPREADATTR | GCS_COMPREADCLAUSE);
public const int GCS_RESULT = (GCS_RESULTSTR | GCS_RESULTCLAUSE);
public const int GCS_RESULTREAD = (GCS_RESULTREADSTR | GCS_RESULTREADCLAUSE);
public const int CFS_CANDIDATEPOS = 0x0040;
public const int CFS_POINT = 0x0002;
public const int CFS_EXCLUDE = 0x0080;
// lParam for WM_IME_SETCONTEXT
public const long ISC_SHOWUICANDIDATEWINDOW = 0x00000001;
public const long ISC_SHOWUICOMPOSITIONWINDOW = 0x80000000;
public const long ISC_SHOWUIGUIDELINE = 0x40000000;
public const long ISC_SHOWUIALLCANDIDATEWINDOW = 0x0000000F;
public const long ISC_SHOWUIALL = 0xC000000F;
#endregion Constants
[StructLayout(LayoutKind.Sequential)]
public unsafe struct CANDIDATELIST
{
public uint dwSize;
public uint dwStyle;
public uint dwCount;
public uint dwSelection;
public uint dwPageStart;
public uint dwPageSize;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1, ArraySubType = UnmanagedType.U4)]
public fixed uint dwOffset[1];
}
// CANDIDATEFORM structures
[StructLayout(LayoutKind.Sequential)]
public struct CANDIDATEFORM
{
public int dwIndex;
public int dwStyle;
public TsfSharp.Point ptCurrentPos;
public TsfSharp.Rect rcArea;
}
// COMPOSITIONFORM structures
[StructLayout(LayoutKind.Sequential)]
public struct COMPOSITIONFORM
{
public int dwStyle;
public TsfSharp.Point ptCurrentPos;
public TsfSharp.Rect rcArea;
}
[DllImport("imm32.dll", SetLastError = true)]
public static extern IntPtr ImmCreateContext();
[DllImport("imm32.dll", SetLastError = true)]
public static extern bool ImmDestroyContext(IntPtr hIMC);
[DllImport("imm32.dll", SetLastError = true)]
public static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("imm32.dll", SetLastError = true)]
public static extern IntPtr ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("imm32.dll", CharSet = CharSet.Unicode)]
public static extern uint ImmGetCandidateList(IntPtr hIMC, uint deIndex, IntPtr candidateList, uint dwBufLen);
[DllImport("imm32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int ImmGetCompositionString(IntPtr hIMC, int CompositionStringFlag, IntPtr buffer, int bufferLength);
[DllImport("imm32.dll", SetLastError = true)]
public static extern IntPtr ImmGetContext(IntPtr hWnd);
[DllImport("Imm32.dll", SetLastError = true)]
public static extern bool ImmGetOpenStatus(IntPtr hIMC);
[DllImport("Imm32.dll", SetLastError = true)]
public static extern bool ImmSetOpenStatus(IntPtr hIMC, bool open);
[DllImport("imm32.dll", SetLastError = true)]
public static extern bool ImmSetCandidateWindow(IntPtr hIMC, ref CANDIDATEFORM candidateForm);
[DllImport("imm32.dll", SetLastError = true)]
public static extern int ImmSetCompositionWindow(IntPtr hIMC, ref COMPOSITIONFORM compForm);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool CreateCaret(IntPtr hWnd, IntPtr hBitmap, int nWidth, int nHeight);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyCaret();
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetCaretPos(int x, int y);
}
}

View File

@@ -1,95 +0,0 @@
using System;
namespace ImeSharp.Native
{
/// <summary>
/// SystemMetrics. SM_*
/// </summary>
public enum SM
{
CXSCREEN = 0,
CYSCREEN = 1,
CXVSCROLL = 2,
CYHSCROLL = 3,
CYCAPTION = 4,
CXBORDER = 5,
CYBORDER = 6,
CXFIXEDFRAME = 7,
CYFIXEDFRAME = 8,
CYVTHUMB = 9,
CXHTHUMB = 10,
CXICON = 11,
CYICON = 12,
CXCURSOR = 13,
CYCURSOR = 14,
CYMENU = 15,
CXFULLSCREEN = 16,
CYFULLSCREEN = 17,
CYKANJIWINDOW = 18,
MOUSEPRESENT = 19,
CYVSCROLL = 20,
CXHSCROLL = 21,
DEBUG = 22,
SWAPBUTTON = 23,
CXMIN = 28,
CYMIN = 29,
CXSIZE = 30,
CYSIZE = 31,
CXFRAME = 32,
CXSIZEFRAME = CXFRAME,
CYFRAME = 33,
CYSIZEFRAME = CYFRAME,
CXMINTRACK = 34,
CYMINTRACK = 35,
CXDOUBLECLK = 36,
CYDOUBLECLK = 37,
CXICONSPACING = 38,
CYICONSPACING = 39,
MENUDROPALIGNMENT = 40,
PENWINDOWS = 41,
DBCSENABLED = 42,
CMOUSEBUTTONS = 43,
SECURE = 44,
CXEDGE = 45,
CYEDGE = 46,
CXMINSPACING = 47,
CYMINSPACING = 48,
CXSMICON = 49,
CYSMICON = 50,
CYSMCAPTION = 51,
CXSMSIZE = 52,
CYSMSIZE = 53,
CXMENUSIZE = 54,
CYMENUSIZE = 55,
ARRANGE = 56,
CXMINIMIZED = 57,
CYMINIMIZED = 58,
CXMAXTRACK = 59,
CYMAXTRACK = 60,
CXMAXIMIZED = 61,
CYMAXIMIZED = 62,
NETWORK = 63,
CLEANBOOT = 67,
CXDRAG = 68,
CYDRAG = 69,
SHOWSOUNDS = 70,
CXMENUCHECK = 71,
CYMENUCHECK = 72,
SLOWMACHINE = 73,
MIDEASTENABLED = 74,
MOUSEWHEELPRESENT = 75,
XVIRTUALSCREEN = 76,
YVIRTUALSCREEN = 77,
CXVIRTUALSCREEN = 78,
CYVIRTUALSCREEN = 79,
CMONITORS = 80,
SAMEDISPLAYFORMAT = 81,
IMMENABLED = 82,
CXFOCUSBORDER = 83,
CYFOCUSBORDER = 84,
TABLETPC = 86,
MEDIACENTER = 87,
REMOTESESSION = 0x1000,
REMOTECONTROL = 0x2001,
}
}

View File

@@ -1,58 +0,0 @@
using System;
using ImeSharp.Native;
namespace ImeSharp
{
/// <summary>
/// Contains properties that are queries into the system's various settings.
/// </summary>
internal sealed class SafeSystemMetrics
{
private SafeSystemMetrics()
{
}
/// <summary>
/// Maps to SM_CXDOUBLECLK
/// </summary>
public static int DoubleClickDeltaX
{
get { return NativeMethods.GetSystemMetrics(SM.CXDOUBLECLK); }
}
/// <summary>
/// Maps to SM_CYDOUBLECLK
/// </summary>
public static int DoubleClickDeltaY
{
get { return NativeMethods.GetSystemMetrics(SM.CYDOUBLECLK); }
}
/// <summary>
/// Maps to SM_CXDRAG
/// </summary>
public static int DragDeltaX
{
get { return NativeMethods.GetSystemMetrics(SM.CXDRAG); }
}
/// <summary>
/// Maps to SM_CYDRAG
/// </summary>
public static int DragDeltaY
{
get { return NativeMethods.GetSystemMetrics(SM.CYDRAG); }
}
///<summary>
/// Is an IMM enabled ? Maps to SM_IMMENABLED
///</summary>
public static bool IsImmEnabled
{
get { return (NativeMethods.GetSystemMetrics(SM.IMMENABLED) != 0); }
}
}
}

View File

@@ -1,6 +0,0 @@
namespace ImeSharp
{
public delegate void TextInputCallback(char character);
public delegate void TextCompositionCallback(IMEString compositionText, int cursorPosition, IMEString[] candidateList, int candidatePageStart, int candidatePageSize, int candidateSelection);
public delegate void CommitTextCompositionCallback(string text);
}

View File

@@ -1,374 +0,0 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;
using ImeSharp.Native;
using TsfSharp;
namespace ImeSharp
{
//------------------------------------------------------
//
// TextServicesContext class
//
//------------------------------------------------------
/// <summary>
/// This class manages the ITfThreadMgr, EmptyDim and the reference to
/// the default TextStore.
/// </summary>
/// <remarks>
/// </remarks>
internal class TextServicesContext
{
public const int TF_POPF_ALL = 0x0001;
public const int TF_INVALID_COOKIE = -1;
public static readonly Guid IID_ITfUIElementSink = new Guid(0xea1ea136, 0x19df, 0x11d7, 0xa6, 0xd2, 0x00, 0x06, 0x5b, 0x84, 0x43, 0x5c);
public static readonly Guid IID_ITfTextEditSink = new Guid(0x8127d409, 0xccd3, 0x4683, 0x96, 0x7a, 0xb4, 0x3d, 0x5b, 0x48, 0x2b, 0xf7);
public static TextServicesContext Current
{
get
{
if (InputMethod.TextServicesContext == null)
InputMethod.TextServicesContext = new TextServicesContext();
return InputMethod.TextServicesContext;
}
}
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
/// <summary>
/// Instantiates a TextServicesContext.
/// </summary>
private TextServicesContext()
{
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
Debug.WriteLine("CRASH: ImeSharp won't work on MTA thread!!!");
}
#endregion Constructors
//------------------------------------------------------
//
// public Methods
//
//------------------------------------------------------
#region public Methods
/// <summary>
/// Releases all unmanaged resources allocated by the
/// TextServicesContext.
/// </summary>
/// <remarks>
/// if appDomainShutdown == false, this method must be called on the
/// Dispatcher thread. Otherwise, the caller is an AppDomain.Shutdown
/// listener, and is calling from a worker thread.
/// </remarks>
public void Uninitialize(bool appDomainShutdown)
{
// Unregister DefaultTextStore.
if (_defaultTextStore != null)
{
UnadviseSinks();
if (_defaultTextStore.DocumentManager != null)
{
_defaultTextStore.DocumentManager.Pop(TF_POPF_ALL);
_defaultTextStore.DocumentManager.Dispose();
_defaultTextStore.DocumentManager = null;
}
_defaultTextStore = null;
}
// Free up any remaining textstores.
if (_istimactivated == true)
{
// Shut down the thread manager when the last TextStore goes away.
// On XP, if we're called on a worker thread (during AppDomain shutdown)
// we can't call call any methods on _threadManager. The problem is
// that there's no proxy registered for ITfThreadMgr on OS versions
// previous to Vista. Not calling Deactivate will leak the IMEs, but
// in practice (1) they're singletons, so it's not unbounded; and (2)
// most applications will share the thread with other AppDomains that
// have a UI, in which case the IME won't be released until the process
// shuts down in any case. In theory we could also work around this
// problem by creating our own XP proxy/stub implementation, which would
// be added to WPF setup....
if (!appDomainShutdown || System.Environment.OSVersion.Version.Major >= 6)
{
_threadManager.Deactivate();
}
_istimactivated = false;
}
// Release the empty dim.
if (_dimEmpty != null)
{
if (_dimEmpty != null)
{
_dimEmpty.Dispose();
}
_dimEmpty = null;
}
// Release the ThreadManager.
// We don't do this in UnregisterTextStore because someone may have
// called get_ThreadManager after the last TextStore was unregistered.
if (_threadManager != null)
{
if (_threadManager != null)
{
_threadManager.Dispose();
}
_threadManager = null;
}
}
// Called by framework's TextStore class. This method registers a
// document with TSF. The TextServicesContext must maintain this list
// to ensure all native resources are released after gc or uninitialization.
public void RegisterTextStore(TextStore defaultTextStore)
{
_defaultTextStore = defaultTextStore;
ITfThreadMgrEx threadManager = ThreadManager;
if (threadManager != null)
{
ITfDocumentMgr doc;
int editCookie = TF_INVALID_COOKIE;
// Activate TSF on this thread if this is the first TextStore.
if (_istimactivated == false)
{
//temp variable created to retrieve the value
// which is then stored in the critical data.
if (InputMethod.ShowOSImeWindow)
_clientId = threadManager.Activate();
else
_clientId = threadManager.ActivateEx(TfTmaeFlags.Uielementenabledonly);
_istimactivated = true;
}
// Create a TSF document.
doc = threadManager.CreateDocumentMgr();
_defaultTextStore.DocumentManager = doc;
doc.CreateContext(_clientId, 0 /* flags */, _defaultTextStore, out _editContext, out editCookie);
_defaultTextStore.EditCookie = editCookie;
_contextOwnerServices = _editContext.QueryInterface<ITfContextOwnerServices>();
doc.Push(_editContext);
AdviseSinks();
}
}
public void SetFocusOnDefaultTextStore()
{
SetFocusOnDim(TextStore.Current.DocumentManager);
}
public void SetFocusOnEmptyDim()
{
SetFocusOnDim(EmptyDocumentManager);
}
#endregion public Methods
//------------------------------------------------------
//
// public Properties
//
//------------------------------------------------------
/// <summary>
/// The default ITfThreadMgrEx object.
/// </summary>
public ITfThreadMgrEx ThreadManager
{
// The ITfThreadMgr for this thread.
get
{
if (_threadManager == null)
{
ITfThreadMgr threadMgr = null;
try
{
// This might fail in CoreRT
threadMgr = Tsf.GetThreadMgr();
}
catch (SharpGen.Runtime.SharpGenException)
{
threadMgr = null;
}
// Dispose previous ITfThreadMgr in case something weird happens
if (threadMgr != null)
{
if (threadMgr.IsThreadFocus)
threadMgr.Deactivate();
threadMgr.Dispose();
}
_threadManager = TextServicesLoader.Load();
_uiElementMgr = _threadManager.QueryInterface<ITfUIElementMgr>();
}
return _threadManager;
}
}
/// <summary>
/// Return the created ITfContext object.
/// </summary>
public ITfContext EditContext
{
get { return _editContext; }
}
/// <summary>
/// Return the created ITfUIElementMgr object.
/// </summary>
public ITfUIElementMgr UIElementMgr
{
get { return _uiElementMgr; }
}
/// <summary>
/// Return the created ITfContextOwnerServices object.
/// </summary>
public ITfContextOwnerServices ContextOwnerServices
{
get { return _contextOwnerServices; }
}
//------------------------------------------------------
//
// public Events
//
//------------------------------------------------------
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
private void SetFocusOnDim(ITfDocumentMgr dim)
{
ITfThreadMgrEx threadmgr = ThreadManager;
if (threadmgr != null)
{
ITfDocumentMgr prevDocMgr = threadmgr.AssociateFocus(InputMethod.WindowHandle, dim);
}
}
private void AdviseSinks()
{
var source = _uiElementMgr.QueryInterface<ITfSource>();
var guid = IID_ITfUIElementSink;
int sinkCookie = source.AdviseSink(guid, _defaultTextStore);
_defaultTextStore.UIElementSinkCookie = sinkCookie;
source.Dispose();
source = _editContext.QueryInterface<ITfSource>();
guid = IID_ITfTextEditSink;
sinkCookie = source.AdviseSink(guid, _defaultTextStore);
_defaultTextStore.TextEditSinkCookie = sinkCookie;
source.Dispose();
}
private void UnadviseSinks()
{
var source = _uiElementMgr.QueryInterface<ITfSource>();
if (_defaultTextStore.UIElementSinkCookie != TF_INVALID_COOKIE)
{
source.UnadviseSink(_defaultTextStore.UIElementSinkCookie);
_defaultTextStore.UIElementSinkCookie = TF_INVALID_COOKIE;
}
source.Dispose();
source = _editContext.QueryInterface<ITfSource>();
if (_defaultTextStore.TextEditSinkCookie != TF_INVALID_COOKIE)
{
source.UnadviseSink(_defaultTextStore.TextEditSinkCookie);
_defaultTextStore.TextEditSinkCookie = TF_INVALID_COOKIE;
}
source.Dispose();
}
//------------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
// Create an empty dim on demand.
private ITfDocumentMgr EmptyDocumentManager
{
get
{
if (_dimEmpty == null)
{
ITfThreadMgrEx threadManager = ThreadManager;
if (threadManager == null)
{
return null;
}
ITfDocumentMgr dimEmptyTemp;
// Create a TSF document.
dimEmptyTemp = threadManager.CreateDocumentMgr();
_dimEmpty = dimEmptyTemp;
}
return _dimEmpty;
}
}
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private TextStore _defaultTextStore;
private ITfContext _editContext;
private ITfUIElementMgr _uiElementMgr;
private ITfContextOwnerServices _contextOwnerServices;
// This is true if thread manager is activated.
private bool _istimactivated;
// The root TSF object, created on demand.
private ITfThreadMgrEx _threadManager;
// TSF ClientId from Activate call.
private int _clientId;
// The empty dim for this thread. Created on demand.
private ITfDocumentMgr _dimEmpty;
#endregion Private Fields
}
}

View File

@@ -1,338 +0,0 @@
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;
using Microsoft.Win32;
using ImeSharp.Native;
using TsfSharp;
namespace ImeSharp
{
// Creates ITfThreadMgr instances, the root object of the Text Services
// Framework.
internal class TextServicesLoader
{
public static readonly Guid CLSID_TF_ThreadMgr = new Guid("529a9e6b-6587-4f23-ab9e-9c7d683e3c50");
public static readonly Guid IID_ITfThreadMgr = new Guid("aa80e801-2021-11d2-93e0-0060b067b86e");
public static readonly Guid IID_ITfThreadMgrEx = new Guid("3e90ade3-7594-4cb0-bb58-69628f5f458c");
public static readonly Guid IID_ITfThreadMgr2 = new Guid("0AB198EF-6477-4EE8-8812-6780EDB82D5E");
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
// Private ctor to prevent anyone from instantiating this static class.
private TextServicesLoader() { }
#endregion Constructors
//------------------------------------------------------
//
// public Properties
//
//------------------------------------------------------
#region public Properties
/// <summary>
/// Loads an instance of the Text Services Framework.
/// </summary>
/// <returns>
/// May return null if no text services are available.
/// </returns>
public static ITfThreadMgrEx Load()
{
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
Debug.WriteLine("CRASH: ImeSharp won't work on MTA thread!!!");
if (ServicesInstalled)
{
// NB: a COMException here means something went wrong initialzing Cicero.
// Cicero will throw an exception if it doesn't think it should have been
// loaded (no TIPs to run), you can check that in msctf.dll's NoTipsInstalled
// which lives in nt\windows\advcore\ctf\lib\immxutil.cpp. If that's the
// problem, ServicesInstalled is out of sync with Cicero's thinking.
IntPtr ret;
var hr = NativeMethods.CoCreateInstance(CLSID_TF_ThreadMgr,
IntPtr.Zero,
NativeMethods.CLSCTX_INPROC_SERVER,
IID_ITfThreadMgrEx, out ret);
if (hr == NativeMethods.S_OK)
return new ITfThreadMgrEx(ret);
}
return null;
}
/// <summary>
/// return true if current OS version is Windows 7 or below.
/// </summary>
public static bool IsWindows7OrBelow()
{
if (Environment.OSVersion.Version.Major <= 5)
return true;
if (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor <= 1)
return true;
return false;
}
/// <summary>
/// Informs the caller if text services are installed for the current user.
/// </summary>
/// <returns>
/// true if one or more text services are installed for the current user, otherwise false.
/// </returns>
/// <remarks>
/// If this method returns false, TextServicesLoader.Load is guarenteed to return null.
/// Callers can use this information to avoid overhead that would otherwise be
/// required to support text services.
/// </remarks>
public static bool ServicesInstalled
{
get
{
lock (s_servicesInstalledLock)
{
if (s_servicesInstalled == InstallState.Unknown)
{
s_servicesInstalled = TIPsWantToRun() ? InstallState.Installed : InstallState.NotInstalled;
}
}
return (s_servicesInstalled == InstallState.Installed);
}
}
#endregion public Properties
//------------------------------------------------------
//
// public Events
//
//------------------------------------------------------
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
//
// This method tries to stop Avalon from loading Cicero when there are no TIPs to run.
// The perf tradeoff is a typically small number of registry checks versus loading and
// initializing cicero.
//
// The Algorithm:
//
// Do a quick check vs. the global disable flag, return false if it is set.
// For each key under HKLM\SOFTWARE\Microsoft\CTF\TIP (a TIP or category clsid)
// If the the key has a LanguageProfile subkey (it's a TIP clsid)
// Iterate under the matching TIP entry in HKCU.
// For each key under the LanguageProfile (a particular LANGID)
// For each key under the LANGID (an assembly GUID)
// Try to read the Enable value.
// If the value is set non-zero, then stop all processing and return true.
// If the value is set zero, continue.
// If the value does not exist, continue (default is disabled).
// If any Enable values were found under HKCU for the TIP, then stop all processing and return false.
// Else, no Enable values have been found thus far and we keep going to investigate HKLM.
// Iterate under the TIP entry in HKLM.
// For each key under the LanguageProfile (a particular LANGID)
// For each key under the LANGID (an assembly GUID)
// Try to read the Enable value.
// If the value is set non-zero, then stop all processing and return true.
// If the value does not exist, then stop all processing and return true (default is enabled).
// If the value is set zero, continue.
// If we finish iterating all entries under HKLM without returning true, return false.
//
private static bool TIPsWantToRun()
{
object obj;
RegistryKey key;
bool tipsWantToRun = false;
key = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\CTF", false);
// Is cicero disabled completely for the current user?
if (key != null)
{
obj = key.GetValue("Disable Thread Input Manager");
if (obj is int && (int)obj != 0)
return false;
}
// Loop through all the TIP entries for machine and current user.
tipsWantToRun = IterateSubKeys(Registry.LocalMachine, "SOFTWARE\\Microsoft\\CTF\\TIP", new IterateHandler(SingleTIPWantsToRun), true) == EnableState.Enabled;
return tipsWantToRun;
}
// Returns EnableState.Enabled if one or more TIPs are installed and
// enabled for the current user.
private static EnableState SingleTIPWantsToRun(RegistryKey keyLocalMachine, string subKeyName, bool localMachine)
{
EnableState result;
if (subKeyName.Length != CLSIDLength)
return EnableState.Disabled;
// We want subkey\LanguageProfile key.
// Loop through all the langid entries for TIP.
// First, check current user.
result = IterateSubKeys(Registry.CurrentUser, "SOFTWARE\\Microsoft\\CTF\\TIP\\" + subKeyName + "\\LanguageProfile", new IterateHandler(IsLangidEnabled), false);
// Any explicit value short circuits the process.
// Otherwise check local machine.
if (result == EnableState.None || result == EnableState.Error)
{
result = IterateSubKeys(keyLocalMachine, subKeyName + "\\LanguageProfile", new IterateHandler(IsLangidEnabled), true);
if (result == EnableState.None)
{
result = EnableState.Enabled;
}
}
return result;
}
// Returns EnableState.Enabled if the supplied subkey is a valid LANGID key with enabled
// cicero assembly.
private static EnableState IsLangidEnabled(RegistryKey key, string subKeyName, bool localMachine)
{
if (subKeyName.Length != LANGIDLength)
return EnableState.Error;
// Loop through all the assembly entries for the langid
return IterateSubKeys(key, subKeyName, new IterateHandler(IsAssemblyEnabled), localMachine);
}
// Returns EnableState.Enabled if the supplied assembly key is enabled.
private static EnableState IsAssemblyEnabled(RegistryKey key, string subKeyName, bool localMachine)
{
RegistryKey subKey;
object obj;
if (subKeyName.Length != CLSIDLength)
return EnableState.Error;
// Open the local machine assembly key.
subKey = key.OpenSubKey(subKeyName);
if (subKey == null)
return EnableState.Error;
// Try to read the "Enable" value.
obj = subKey.GetValue("Enable");
if (obj is int)
{
return ((int)obj == 0) ? EnableState.Disabled : EnableState.Enabled;
}
return EnableState.None;
}
// Calls the supplied delegate on each of the children of keyBase.
private static EnableState IterateSubKeys(RegistryKey keyBase, string subKey, IterateHandler handler, bool localMachine)
{
RegistryKey key;
string[] subKeyNames;
EnableState state;
key = keyBase.OpenSubKey(subKey, false);
if (key == null)
return EnableState.Error;
subKeyNames = key.GetSubKeyNames();
state = EnableState.Error;
foreach (string name in subKeyNames)
{
switch (handler(key, name, localMachine))
{
case EnableState.Error:
break;
case EnableState.None:
if (localMachine) // For lm, want to return here right away.
return EnableState.None;
// For current user, remember that we found no Enable value.
if (state == EnableState.Error)
{
state = EnableState.None;
}
break;
case EnableState.Disabled:
state = EnableState.Disabled;
break;
case EnableState.Enabled:
return EnableState.Enabled;
}
}
return state;
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
// String consts used to validate registry entires.
private const int CLSIDLength = 38; // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
private const int LANGIDLength = 10; // 0x12345678
// Status of a TIP assembly.
private enum EnableState
{
Error, // Invalid entry.
None, // No explicit Enable entry on the assembly.
Enabled, // Assembly is enabled.
Disabled // Assembly is disabled.
};
// Callback delegate for the IterateSubKeys method.
private delegate EnableState IterateHandler(RegistryKey key, string subKeyName, bool localMachine);
// Install state.
private enum InstallState
{
Unknown, // Haven't checked to see if any TIPs are installed yet.
Installed, // Checked and installed.
NotInstalled // Checked and not installed.
}
// Cached install state value.
// Writes are not thread safe, but we don't mind the neglible perf hit
// of potentially writing it twice.
private static InstallState s_servicesInstalled = InstallState.Unknown;
private static object s_servicesInstalledLock = new object();
#endregion Private Fields
}
}

View File

@@ -1,963 +0,0 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using ImeSharp.Native;
using SharpGen.Runtime;
using SharpGen.Runtime.Win32;
using TsfSharp;
namespace ImeSharp
{
internal class TextStore : CallbackBase,
ITextStoreACP,
ITfContextOwnerCompositionSink,
ITfTextEditSink,
ITfUIElementSink
{
public static readonly Guid IID_ITextStoreACPSink = new Guid(0x22d44c94, 0xa419, 0x4542, 0xa2, 0x72, 0xae, 0x26, 0x09, 0x3e, 0xce, 0xcf);
public static readonly Guid GUID_PROP_COMPOSING = new Guid("e12ac060-af15-11d2-afc5-00105a2799b5");
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
// Creates a TextStore instance.
public TextStore(IntPtr windowHandle)
{
_windowHandle = windowHandle;
_viewCookie = Environment.TickCount;
_editCookie = Tsf.TF_INVALID_COOKIE;
_uiElementSinkCookie = Tsf.TF_INVALID_COOKIE;
_textEditSinkCookie = Tsf.TF_INVALID_COOKIE;
_IMEStringPool = ArrayPool<IMEString>.Shared;
}
#endregion Constructors
//------------------------------------------------------
//
// Methods - ITextStoreACP
//
//------------------------------------------------------
#region ITextStoreACP
public void AdviseSink(Guid riid, IUnknown obj, int flags)
{
ITextStoreACPSink sink;
if (riid != IID_ITextStoreACPSink)
throw new COMException("TextStore_CONNECT_E_CANNOTCONNECT");
sink = (obj as ComObject).QueryInterface<ITextStoreACPSink>();
if (sink == null)
throw new COMException("TextStore_E_NOINTERFACE");
// It's legal to replace existing sink.
if (_sink != null)
_sink.Dispose();
(obj as ComObject).Dispose();
_sink = sink;
}
public void UnadviseSink(IUnknown obj)
{
var sink = (obj as ComObject).QueryInterface<ITextStoreACPSink>();
if (sink.NativePointer != _sink.NativePointer)
throw new COMException("TextStore_CONNECT_E_NOCONNECTION");
_sink.Release();
_sink = null;
}
private bool _LockDocument(TsfSharp.TsLfFlags dwLockFlags)
{
if (_locked)
return false;
_locked = true;
_lockFlags = dwLockFlags;
return true;
}
private void ResetIfRequired()
{
if (!_commited)
return;
_commited = false;
TsTextchange textChange;
textChange.AcpStart = 0;
textChange.AcpOldEnd = _inputBuffer.Count;
textChange.AcpNewEnd = 0;
_inputBuffer.Clear();
_sink.OnTextChange(0, textChange);
_acpStart = _acpEnd = 0;
_sink.OnSelectionChange();
_commitStart = _commitLength = 0;
//Debug.WriteLine("TextStore reset!!!");
}
private void _UnlockDocument()
{
Result hr;
_locked = false;
_lockFlags = 0;
ResetIfRequired();
//if there is a queued lock, grant it
if (_lockRequestQueue.Count > 0)
{
hr = RequestLock(_lockRequestQueue.Dequeue());
}
//if any layout changes occurred during the lock, notify the manager
if (_layoutChanged)
{
_layoutChanged = false;
_sink.OnLayoutChange(TsLayoutCode.TsLcChange, _viewCookie);
}
}
private bool _IsLocked(TsfSharp.TsLfFlags dwLockType)
{
return _locked && (_lockFlags & dwLockType) != 0;
}
public Result RequestLock(TsfSharp.TsLfFlags dwLockFlags)
{
Result hrSession;
if (_sink == null)
throw new COMException("TextStore_NoSink");
if (dwLockFlags == 0)
throw new COMException("TextStore_BadLockFlags");
hrSession = Result.Fail;
if (_locked)
{
//the document is locked
if ((dwLockFlags & TsfSharp.TsLfFlags.Sync) == TsfSharp.TsLfFlags.Sync)
{
/*
The caller wants an immediate lock, but this cannot be granted because
the document is already locked.
*/
hrSession = (int)TsErrors.TsESynchronous;
}
else
{
//the request is asynchronous
//Queue the lock request
_lockRequestQueue.Enqueue(dwLockFlags);
hrSession = (int)TsErrors.TsSAsync;
}
return hrSession;
}
//lock the document
_LockDocument(dwLockFlags);
//call OnLockGranted
hrSession = _sink.OnLockGranted(dwLockFlags);
//unlock the document
_UnlockDocument();
return hrSession;
}
public TsStatus GetStatus()
{
TsStatus status = new TsStatus();
status.DynamicFlags = 0;
status.StaticFlags = 0;
return status;
}
public void QueryInsert(int acpTestStart, int acpTestEnd, uint cch, out int acpResultStart, out int acpResultEnd)
{
acpResultStart = acpResultEnd = 0;
// Fix possible crash
if (_inputBuffer.Count == 0)
return;
//Queryins
if (acpTestStart > _inputBuffer.Count || acpTestEnd > _inputBuffer.Count)
throw new COMException("", Result.InvalidArg.Code);
//Microsoft Pinyin seems does not init the result value, so we set the test value here, in case crash
acpResultStart = acpTestStart;
acpResultEnd = acpTestEnd;
}
public uint GetSelection(uint index, ref TsSelectionAcp selection)
{
//does the caller have a lock
if (!_IsLocked(TsLfFlags.Read))
{
//the caller doesn't have a lock
//return NativeMethods.TS_E_NOLOCK;
throw new COMException("", (int)TsErrors.TsENolock);
}
//check the requested index
if (-1 == (int)index)
{
index = 0;
}
else if (index > 1)
{
/*
The index is too high. This app only supports one selection.
*/
throw new COMException("", Result.InvalidArg.Code);
}
selection.AcpStart = _acpStart;
selection.AcpEnd = _acpEnd;
selection.Style.InterimCharFlag = _interimChar;
if (_interimChar)
{
/*
fInterimChar will be set when an intermediate character has been
set. One example of when this will happen is when an IME is being
used to enter characters and a character has been set, but the IME
is still active.
*/
selection.Style.Ase = TsActiveSelEnd.TsAeNone;
}
else
{
selection.Style.Ase = _activeSelectionEnd;
}
return 1;
}
public void SetSelection(uint count, ref TsSelectionAcp selections)
{
//this implementaiton only supports a single selection
if (count != 1)
throw new COMException("", Result.InvalidArg.Code);
//does the caller have a lock
if (!_IsLocked(TsLfFlags.Readwrite))
{
//the caller doesn't have a lock
//return NativeMethods.TS_E_NOLOCK;
throw new COMException("", (int)TsErrors.TsENolock);
}
_acpStart = selections.AcpStart;
_acpEnd = selections.AcpEnd;
_interimChar = selections.Style.InterimCharFlag;
if (_interimChar)
{
/*
fInterimChar will be set when an intermediate character has been
set. One example of when this will happen is when an IME is being
used to enter characters and a character has been set, but the IME
is still active.
*/
_activeSelectionEnd = TsActiveSelEnd.TsAeNone;
}
else
{
_activeSelectionEnd = selections.Style.Ase;
}
//if the selection end is at the start of the selection, reverse the parameters
int lStart = _acpStart;
int lEnd = _acpEnd;
if (TsActiveSelEnd.TsAeStart == _activeSelectionEnd)
{
lStart = _acpEnd;
lEnd = _acpStart;
}
}
public void GetText(int acpStart, int acpEnd, System.IntPtr pchPlain, uint cchPlainReq, out uint cchPlainRet,
ref TsfSharp.TsRuninfo rgRunInfo, uint cRunInfoReq, out uint cRunInfoRet, out int acpNext)
{
cchPlainRet = 0;
cRunInfoRet = 0;
acpNext = 0;
//does the caller have a lock
if (!_IsLocked(TsLfFlags.Read))
{
//the caller doesn't have a lock
throw new COMException("", (int)TsErrors.TsENolock);
}
bool fDoText = cchPlainReq > 0;
bool fDoRunInfo = cRunInfoReq > 0;
int cchTotal;
cchPlainRet = 0;
acpNext = acpStart;
cchTotal = _inputBuffer.Count;
//validate the start pos
if ((acpStart < 0) || (acpStart > cchTotal))
{
throw new COMException("", Result.InvalidArg.Code);
}
else
{
//are we at the end of the document
if (acpStart == cchTotal)
{
return;
}
else
{
int cchReq;
/*
acpEnd will be -1 if all of the text up to the end is being requested.
*/
if (acpEnd >= acpStart)
{
cchReq = acpEnd - acpStart;
}
else
{
cchReq = cchTotal - acpStart;
}
if (fDoText)
{
if (cchReq > cchPlainReq)
{
cchReq = (int)cchPlainReq;
}
//extract the specified text range
if (pchPlain != IntPtr.Zero && cchPlainReq > 0)
{
//_inputBuffer.CopyTo(acpStart, pchPlain, 0, cchReq);
unsafe
{
var ptr = (char*)pchPlain;
for (int i = acpStart; i < cchReq; i++)
{
*ptr = _inputBuffer[i];
ptr++;
}
}
}
}
//it is possible that only the length of the text is being requested
cchPlainRet = (uint)cchReq;
if (fDoRunInfo)
{
/*
Runs are used to separate text characters from formatting characters.
In this example, sequences inside and including the <> are treated as
control sequences and are not displayed.
Plain text = "Text formatting."
Actual text = "Text <B><I>formatting</I></B>."
If all of this text were requested, the run sequence would look like this:
prgRunInfo[0].type = TS_RT_PLAIN; //"Text "
prgRunInfo[0].uCount = 5;
prgRunInfo[1].type = TS_RT_HIDDEN; //<B><I>
prgRunInfo[1].uCount = 6;
prgRunInfo[2].type = TS_RT_PLAIN; //"formatting"
prgRunInfo[2].uCount = 10;
prgRunInfo[3].type = TS_RT_HIDDEN; //</B></I>
prgRunInfo[3].uCount = 8;
prgRunInfo[4].type = TS_RT_PLAIN; //"."
prgRunInfo[4].uCount = 1;
TS_RT_OPAQUE is used to indicate characters or character sequences
that are in the document, but are used privately by the application
and do not map to text. Runs of text tagged with TS_RT_OPAQUE should
NOT be included in the pchPlain or cchPlainOut [out] parameters.
*/
/*
This implementation is plain text, so the text only consists of one run.
If there were multiple runs, it would be an error to have consecuative runs
of the same type.
*/
rgRunInfo.Type = TsRunType.TsRtPlain;
rgRunInfo.Count = (uint)cchReq;
}
acpNext = acpStart + cchReq;
}
}
}
public TsTextchange SetText(int dwFlags, int acpStart, int acpEnd, string pchText, uint cch)
{
/*
dwFlags can be:
TS_ST_CORRECTION
*/
TsTextchange change = new TsTextchange();
//set the selection to the specified range
TsSelectionAcp tsa = new TsSelectionAcp();
tsa.AcpStart = acpStart;
tsa.AcpEnd = acpEnd;
tsa.Style.Ase = TsActiveSelEnd.TsAeStart;
tsa.Style.InterimCharFlag = false;
SetSelection(1, ref tsa);
int start, end;
InsertTextAtSelection(TsIasFlags.Noquery, pchText, cch, out start, out end, out change);
return change;
}
public IDataObject GetFormattedText(int startIndex, int endIndex)
{
throw new COMException("", Result.NotImplemented.Code);
}
public IUnknown GetEmbedded(int index, Guid guidService, Guid riid)
{
throw new COMException("", Result.NotImplemented.Code);
}
public RawBool QueryInsertEmbedded(Guid guidService, ref Formatetc formatEtc)
{
throw new COMException("", Result.NotImplemented.Code);
}
public TsTextchange InsertEmbedded(int flags, int startIndex, int endIndex, TsfSharp.IDataObject dataObjectRef)
{
throw new COMException("", Result.NotImplemented.Code);
}
public void InsertTextAtSelection(TsfSharp.TsIasFlags dwFlags, string pchText, uint cch, out int pacpStart, out int pacpEnd, out TsfSharp.TsTextchange pChange)
{
pacpStart = pacpEnd = 0;
pChange = new TsTextchange();
//does the caller have a lock
if (!_IsLocked(TsLfFlags.Readwrite))
{
//the caller doesn't have a lock
throw new COMException("", (int)TsErrors.TsENolock);
}
int acpStart;
int acpOldEnd;
int acpNewEnd;
acpOldEnd = _acpEnd;
//set the start point after the insertion
acpStart = _acpStart;
//set the end point after the insertion
acpNewEnd = _acpStart + (int)cch;
if ((dwFlags & TsIasFlags.Queryonly) == TsIasFlags.Queryonly)
{
pacpStart = acpStart;
pacpEnd = acpOldEnd;
return;
}
//insert the text
_inputBuffer.RemoveRange(acpStart, acpOldEnd - acpStart);
_inputBuffer.InsertRange(acpStart, pchText);
//set the selection
_acpStart = acpStart;
_acpEnd = acpNewEnd;
if ((dwFlags & TsIasFlags.Noquery) != TsIasFlags.Noquery)
{
pacpStart = acpStart;
pacpEnd = acpNewEnd;
}
//set the TS_TEXTCHANGE members
pChange.AcpStart = acpStart;
pChange.AcpOldEnd = acpOldEnd;
pChange.AcpNewEnd = acpNewEnd;
//defer the layout change notification until the document is unlocked
_layoutChanged = true;
}
public void InsertEmbeddedAtSelection(int flags, IDataObject obj, out int startIndex, out int endIndex, out TsTextchange change)
{
startIndex = endIndex = 0;
change = new TsTextchange();
throw new COMException("", Result.NotImplemented.Code);
}
public void RequestSupportedAttrs(int flags, uint cFilterAttrs, ref Guid filterAttributes)
{
}
public void RequestAttrsAtPosition(int index, uint cFilterAttrs, ref Guid filterAttributes, int flags)
{
throw new COMException("", Result.NotImplemented.Code);
}
public void RequestAttrsTransitioningAtPosition(int position, uint cFilterAttrs, ref Guid filterAttributes, int flags)
{
throw new COMException("", Result.NotImplemented.Code);
}
public void FindNextAttrTransition(int startIndex, int haltIndex, uint cFilterAttrs, ref Guid filterAttributes, int flags, out int acpNext, out RawBool found, out int foundOffset)
{
acpNext = 0;
found = false;
foundOffset = 0;
}
public uint RetrieveRequestedAttrs(uint ulCount, ref TsfSharp.TsAttrval aAttrValsRef)
{
return 0;
}
public int GetEndACP()
{
int acp = 0;
//does the caller have a lock
if (!_IsLocked(TsLfFlags.Read))
{
//the caller doesn't have a lock
throw new COMException("", (int)TsErrors.TsENolock);
}
acp = _inputBuffer.Count;
return acp;
}
public int GetActiveView()
{
return _viewCookie;
}
public int GetACPFromPoint(int viewCookie, TsfSharp.Point tsfPoint, int dwFlags)
{
throw new COMException("", Result.NotImplemented.Code);
}
public void GetTextExt(int viewCookie, int acpStart, int acpEnd, out Rect rect, out RawBool clipped)
{
clipped = false;
rect = InputMethod.TextInputRect;
if (_viewCookie != viewCookie)
throw new COMException("", Result.InvalidArg.Code);
//does the caller have a lock
if (!_IsLocked(TsLfFlags.Read))
{
//the caller doesn't have a lock
throw new COMException("", (int)TsErrors.TsENolock);
}
//According to Microsoft's doc, an ime should not make empty request,
//but some ime draw comp text themseleves, when empty req will be make
//Check empty request
//if (acpStart == acpEnd) {
// return E_INVALIDARG;
//}
NativeMethods.MapWindowPoints(_windowHandle, IntPtr.Zero, ref rect, 2);
}
public Rect GetScreenExt(int viewCookie)
{
Rect rect = new Rect();
if (_viewCookie != viewCookie)
throw new COMException("", Result.InvalidArg.Code);
NativeMethods.GetWindowRect(_windowHandle, out rect);
return rect;
}
public IntPtr GetWnd(int viewCookie)
{
if (viewCookie != _viewCookie)
{
throw new COMException("", Result.False.Code);
}
return _windowHandle;
}
#endregion ITextStoreACP2
//------------------------------------------------------
//
// Public Methods - ITfContextOwnerCompositionSink
//
//------------------------------------------------------
#region ITfContextOwnerCompositionSink
public RawBool OnStartComposition(ITfCompositionView view)
{
// Return true in ok to start the composition.
RawBool ok = true;
_compositionStart = _compositionLength = 0;
_currentComposition.Clear();
InputMethod.OnTextCompositionStarted(this);
_compViews.Add(view);
return ok;
}
public void OnUpdateComposition(ITfCompositionView view, ITfRange rangeNew)
{
var range = view.Range;
var rangeacp = range.QueryInterface<ITfRangeACP>();
rangeacp.GetExtent(out _compositionStart, out _compositionLength);
rangeacp.Dispose();
range.Dispose();
_compViews.Add(view);
}
public void OnEndComposition(ITfCompositionView view)
{
var range = view.Range;
var rangeacp = range.QueryInterface<ITfRangeACP>();
rangeacp.GetExtent(out _commitStart, out _commitLength);
rangeacp.Dispose();
range.Dispose();
// Ensure composition string reset
_compositionStart = _compositionLength = 0;
_currentComposition.Clear();
InputMethod.ClearCandidates();
InputMethod.OnTextCompositionEnded(this);
view.Dispose();
foreach(var item in _compViews)
item.Dispose();
_compViews.Clear();
}
#endregion ITfContextOwnerCompositionSink
#region ITfTextEditSink
public void OnEndEdit(ITfContext context, int ecReadOnly, ITfEditRecord editRecord)
{
ITfProperty property = context.GetProperty(GUID_PROP_COMPOSING);
ITfRangeACP rangeACP = TextServicesContext.Current.ContextOwnerServices.CreateRange(_compositionStart, _compositionStart + _compositionLength);
Variant val = property.GetValue(ecReadOnly, rangeACP);
property.Dispose();
rangeACP.Dispose();
if (val.Value == null || (int)val.Value == 0)
{
if (_commitLength == 0 || _inputBuffer.Count == 0)
return;
//Debug.WriteLine("Composition result: {0}", new object[] { new string(_inputBuffer.GetRange(_commitStart, _commitLength).ToArray()) });
_commited = true;
for (int i = 0; i < _commitLength; i++)
InputMethod.OnTextCompositionResult(this, new string(_inputBuffer.GetRange(_commitStart, _commitLength).ToArray()));
}
if (_commited)
return;
if (_inputBuffer.Count == 0 && _compositionLength > 0) // Composition just ended
return;
_currentComposition.Clear();
for (int i = 0; i < _compositionLength; i++)
_currentComposition.Add(_inputBuffer[_compositionStart + i]);
InputMethod.OnTextComposition(this, new IMEString(_currentComposition), _acpEnd);
//var compStr = new string(_currentComposition.ToArray());
//compStr = compStr.Insert(_acpEnd, "|");
//Debug.WriteLine("Composition string: {0}, cursor pos: {1}", compStr, _acpEnd);
}
#endregion ITfTextEditSink
//------------------------------------------------------
//
// Public Methods - ITfUIElementSink
//
//------------------------------------------------------
#region ITfUIElementSink
public RawBool BeginUIElement(int dwUIElementId)
{
// Hide OS rendered Candidate list Window
RawBool pbShow = InputMethod.ShowOSImeWindow;
OnUIElement(dwUIElementId, true);
return pbShow;
}
public void UpdateUIElement(int dwUIElementId)
{
OnUIElement(dwUIElementId, false);
}
public void EndUIElement(int dwUIElementId)
{
}
private void OnUIElement(int uiElementId, bool onStart)
{
if (InputMethod.ShowOSImeWindow || !_supportUIElement) return;
ITfUIElement uiElement = TextServicesContext.Current.UIElementMgr.GetUIElement(uiElementId);
ITfCandidateListUIElementBehavior candList;
try
{
candList = uiElement.QueryInterface<ITfCandidateListUIElementBehavior>();
}
catch (SharpGenException)
{
_supportUIElement = false;
return;
}
finally
{
uiElement.Dispose();
}
uint selection = 0;
uint currentPage = 0;
uint count = 0;
uint pageCount = 0;
uint pageStart = 0;
uint pageSize = 0;
uint i, j;
selection = candList.GetSelection();
currentPage = candList.GetCurrentPage();
count = candList.GetCount();
pageCount = candList.GetPageIndex(null, 0);
if (pageCount > 0)
{
uint[] pageStartIndexes = ArrayPool<uint>.Shared.Rent((int)pageCount);
pageCount = candList.GetPageIndex(pageStartIndexes, pageCount);
pageStart = pageStartIndexes[currentPage];
if (pageStart >= count - 1)
{
candList.Abort();
ArrayPool<uint>.Shared.Return(pageStartIndexes);
return;
}
if (currentPage < pageCount - 1)
pageSize = Math.Min(count, pageStartIndexes[currentPage + 1]) - pageStart;
else
pageSize = count - pageStart;
ArrayPool<uint>.Shared.Return(pageStartIndexes);
}
selection -= pageStart;
IMEString[] candidates = _IMEStringPool.Rent((int)pageSize);
IntPtr bStrPtr;
for (i = pageStart, j = 0; i < count && j < pageSize; i++, j++)
{
bStrPtr = candList.GetString(i);
candidates[j] = new IMEString(bStrPtr);
}
//Debug.WriteLine("TSF========TSF");
//Debug.WriteLine("pageStart: {0}, pageSize: {1}, selection: {2}, currentPage: {3} candidates:", pageStart, pageSize, selection, currentPage);
//for (int k = 0; k < candidates.Length; k++)
// Debug.WriteLine(" {2}{0}.{1}", k + 1, candidates[k], k == selection ? "*" : "");
//Debug.WriteLine("TSF++++++++TSF");
InputMethod.CandidatePageStart = (int)pageStart;
InputMethod.CandidatePageSize = (int)pageSize;
InputMethod.CandidateSelection = (int)selection;
InputMethod.CandidateList = candidates;
if (_currentComposition != null)
{
InputMethod.OnTextComposition(this, new IMEString(_currentComposition), _acpEnd);
_IMEStringPool.Return(candidates);
}
candList.Dispose();
}
#endregion ITfUIElementSink
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
public static TextStore Current
{
get
{
TextStore defaultTextStore = InputMethod.DefaultTextStore;
if (defaultTextStore == null)
{
defaultTextStore = InputMethod.DefaultTextStore = new TextStore(InputMethod.WindowHandle);
defaultTextStore.Register();
}
return defaultTextStore;
}
}
public ITfDocumentMgr DocumentManager
{
get { return _documentMgr; }
set { _documentMgr = value; }
}
// EditCookie for ITfContext.
public int EditCookie
{
// get { return _editCookie; }
set { _editCookie = value; }
}
public int UIElementSinkCookie
{
get { return _uiElementSinkCookie; }
set { _uiElementSinkCookie = value; }
}
public int TextEditSinkCookie
{
get { return _textEditSinkCookie; }
set { _textEditSinkCookie = value; }
}
public bool SupportUIElement { get { return _supportUIElement; } }
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
// This function calls TextServicesContext to create TSF document and start transitory extension.
private void Register()
{
// Create TSF document and advise the sink to it.
TextServicesContext.Current.RegisterTextStore(this);
}
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
// The TSF document object. This is a native resource.
private ITfDocumentMgr _documentMgr;
private int _viewCookie;
// The edit cookie TSF returns from CreateContext.
private int _editCookie;
private int _uiElementSinkCookie;
private int _textEditSinkCookie;
private ITextStoreACPSink _sink;
private IntPtr _windowHandle;
private int _acpStart;
private int _acpEnd;
private bool _interimChar;
private TsActiveSelEnd _activeSelectionEnd;
private List<char> _inputBuffer = new List<char>();
private bool _locked;
private TsLfFlags _lockFlags;
private Queue<TsLfFlags> _lockRequestQueue = new Queue<TsLfFlags>();
private bool _layoutChanged;
private List<char> _currentComposition = new List<char>();
private int _compositionStart;
private int _compositionLength;
private int _commitStart;
private int _commitLength;
private bool _commited;
private bool _supportUIElement = true;
private List<ITfCompositionView> _compViews = new List<ITfCompositionView>();
private ArrayPool<IMEString> _IMEStringPool;
}
}

View File

@@ -111,6 +111,11 @@ namespace Microsoft.Xna.Framework {
/// Used for displaying uncommitted IME text.
/// </summary>
public event EventHandler<TextEditingEventArgs> TextEditing;
/// <summary>
/// Used for when a key is pressed, including modifiers.
/// </summary>
public event EventHandler<TextInputEventArgs> KeyDown;
#endif
#endregion Events
@@ -158,6 +163,11 @@ namespace Microsoft.Xna.Framework {
EventHelpers.Raise(this, TextInput, e);
}
protected void OnKeyDown(object sender, TextInputEventArgs e)
{
EventHelpers.Raise(this, KeyDown, e);
}
protected void OnTextEditing(object sender, TextEditingEventArgs e)
{
EventHelpers.Raise(this, TextEditing, e);

View File

@@ -75,6 +75,8 @@ internal static class Sdl
TextEditing = 0x302,
TextInput = 0x303,
TextEditingExt = 0x305,
MouseMotion = 0x400,
MouseButtonDown = 0x401,
MouseButtonup = 0x402,
@@ -139,6 +141,8 @@ internal static class Sdl
[FieldOffset(0)]
public Keyboard.TextEditingEvent Edit;
[FieldOffset(0)]
public Keyboard.TextEditingExtEvent EditExt;
[FieldOffset(0)]
public Keyboard.TextInputEvent Text;
[FieldOffset(0)]
public Mouse.WheelEvent Wheel;
@@ -241,8 +245,8 @@ internal static class Sdl
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate bool d_sdl_istextinputactive();
public static d_sdl_istextinputactive SDL_IsTextInputActive = FuncLoader.LoadFunction<d_sdl_istextinputactive>(NativeLibrary, "SDL_IsTextInputActive");
public delegate bool d_sdl_istextinputshown();
public static d_sdl_istextinputshown SDL_IsTextInputShown = FuncLoader.LoadFunction<d_sdl_istextinputshown>(NativeLibrary, "SDL_IsTextInputShown");
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void d_sdl_starttextinput();
@@ -841,6 +845,17 @@ internal static class Sdl
public int Length;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct TextEditingExtEvent
{
public EventType Type;
public uint Timestamp;
public uint WindowId;
public byte* Text;
public int Start;
public int Length;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct TextInputEvent
{

View File

@@ -147,14 +147,7 @@ namespace Microsoft.Xna.Framework
character = '\0';
}
if (char.IsControl(character) ||
key == Keys.Left ||
key == Keys.Right ||
key == Keys.Up ||
key == Keys.Down)
{
_view.CallTextInput(character, key);
}
_view.CallKeyDown(character, key);
}
else if (ev.Type == Sdl.EventType.KeyUp)
{
@@ -164,10 +157,24 @@ namespace Microsoft.Xna.Framework
else if (ev.Type == Sdl.EventType.TextEditing)
{
string text;
unsafe { text = ReadString(ev.Text.Text); }
unsafe
{
text = ReadString(ev.Edit.Text);
}
_view.CallTextEditing(text, ev.Edit.Start, ev.Edit.Length);
}
else if (ev.Type == Sdl.EventType.TextEditingExt)
{
string text;
unsafe
{
text = ReadString(ev.EditExt.Text);
Sdl.Free((IntPtr)ev.EditExt.Text);
}
_view.CallTextEditing(text, ev.EditExt.Start, ev.EditExt.Length);
}
else if (ev.Type == Sdl.EventType.TextInput)
{
string text;

View File

@@ -117,7 +117,8 @@ namespace Microsoft.Xna.Framework
* By default SDL2 will hide IME popups since it probably assumes the game will implement their own suggestions box.
* We don't want that, so this hint will allow the system native IME popups to show up when typing in the game.
*/
Sdl.SetHint("SDL_HINT_IME_SHOW_UI", "1");
Sdl.SetHint("SDL_IME_SHOW_UI", "1");
Sdl.SetHint("SDL_IME_SUPPORT_EXTENDED_TEXT", "1");
// when running NUnit tests entry assembly can be null
if (Assembly.GetEntryAssembly() != null)
@@ -339,6 +340,11 @@ namespace Microsoft.Xna.Framework
OnTextInput(this, new TextInputEventArgs(c, key));
}
public void CallKeyDown(char c, Keys key = Keys.None)
{
OnKeyDown(this, new TextInputEventArgs(c, key));
}
public void CallTextEditing(string text, int start, int length)
{
OnTextEditing(this, new TextEditingEventArgs(text, start, length));

View File

@@ -4,6 +4,11 @@ namespace Microsoft.Xna.Framework
{
public static class TextInput
{
public static bool IsTextInputShown()
{
return Sdl.SDL_IsTextInputShown();
}
public static void StartTextInput()
{
Sdl.SDL_StartTextInput();

View File

@@ -43,8 +43,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsTest", "Barotrauma\B
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeployAll", "Deploy\DeployAll\DeployAll.csproj", "{C98FE0D0-BC7D-4806-B592-734B53016FD8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImeSharp", "Libraries\ImeSharp\ImeSharp.csproj", "{D42D8E48-A687-445D-AAF1-9E7E6EBF1DE6}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
Libraries\GameAnalytics\GA-SDK-MONO-SHARED\GA-SDK-MONO-SHARED.projitems*{95c4d59d-9be4-4278-b4f8-46c0ba1a3916}*SharedItemsImports = 5