/*
* Copyright (c) 2010 Mike Qin <mikeandmore@gmail.com>
*
* The contents of this file are subject to the terms of either the GNU Lesser
* General Public License Version 2.1 only ("LGPL") or the Common Development and
* Distribution License ("CDDL")(collectively, the "License"). You may not use this
* file except in compliance with the License. You can obtain a copy of the CDDL at
* http://www.opensource.org/licenses/cddl1.php and a copy of the LGPLv2.1 at
* http://www.opensource.org/licenses/lgpl-license.php. See the License for the
* specific language governing permissions and limitations under the License. When
* distributing the software, include this License Header Notice in each file and
* include the full text of the License in the License file as well as the
* following notice:
*
* NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
* (CDDL)
* For Covered Software in this distribution, this License shall be governed by the
* laws of the State of California (excluding conflict-of-law provisions).
* Any litigation relating to this License shall be subject to the jurisdiction of
* the Federal Courts of the Northern District of California and the state courts
* of the State of California, with venue lying in Santa Clara County, California.
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or only
* the LGPL Version 2.1, indicate your decision by adding "[Contributor]" elects to
* include this software in this distribution under the [CDDL or LGPL Version 2.1]
* license." If you don't indicate a single choice of license, a recipient has the
* option to distribute your version of this file under either the CDDL or the LGPL
* Version 2.1, or to extend the choice of license to its licensees as provided
* above. However, if you add LGPL Version 2.1 code and therefore, elected the LGPL
* Version 2 license, then the option applies only if the new code is made subject
* to such option by the copyright holder.
*/
#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#include <langinfo.h>
#include <locale.h>
#include <iconv.h>
#include <signal.h>
#include <stdlib.h>
#include <stdbool.h>
#include "xim.h"
#include "xmisc.h"
#include "ic.h"
#include "common.h"
#include "settings.h"
static int __preedit_x;
static int __preedit_y;
static int __input_style;
static Window __client_window;
static Window __focus_window;
static int
_xim_open(XIMHandle* handle, IMOpenStruct* proto)
{
LOG("XIM_OPEN %d", proto->connect_id);
return 1;
}
static int
_xim_close(XIMHandle* handle, IMCloseStruct* proto)
{
LOG("XIM_CLOSE %d", proto->connect_id);
return 1;
}
static int
__xim_ic_events(IMChangeICStruct* proto)
{
XICAttribute* ic_attr = proto->ic_attr;
XICAttribute* pre_attr = proto->preedit_attr;
int i = 0;
for (i = 0; i < (int) proto->ic_attr_num; i++) {
if (strcmp(XNInputStyle, ic_attr[i].name) == 0) {
LOG("got input style %d", __input_style);
__input_style = * (int*) ic_attr[i].value;
} else if (strcmp(XNClientWindow, ic_attr[i].name) == 0) {
LOG("got client window");
__client_window = * (Window*) ic_attr[i].value;
} else if (strcmp(XNFocusWindow, ic_attr[i].name) == 0) {
LOG("got focus window");
__focus_window = * (Window*) ic_attr[i].value;
}
}
for (i = 0; i < (int) proto->preedit_attr_num; i++) {
if (strcmp(XNSpotLocation, pre_attr[i].name) == 0) {
XPoint* point = pre_attr[i].value;
__preedit_x = point->x;
__preedit_y = point->y;
LOG("position (%d, %d)", __preedit_x, __preedit_y);
}
}
return 1;
}
static int
_xim_create_ic(XIMHandle* handle, IMChangeICStruct* proto)
{
IC* ic = icmgr_create_ic(proto->connect_id);
__xim_ic_events(proto);
proto->icid = ic->icid;
ic->client_window = __client_window;
ic->offset_x = __preedit_x;
ic->offset_y = __preedit_y;
LOG("XIM_CREATE_IC %d", proto->icid);
return 1;
}
static int
_xim_destroy_ic(XIMHandle* handle, IMChangeICStruct* proto)
{
LOG("XIM_DESTROY_IC %d", proto->icid);
icmgr_destroy_ic(proto->icid);
icmgr_refresh();
return 1;
}
void
__move_preedit(IC* ic)
{
int root_x, root_y;
get_window_position(ic->client_window, &root_x, &root_y);
LOG("root: %d, %d offset: %d,%d", root_x, root_y,
ic->offset_x, ic->offset_y);
if (ic->offset_x == 0 && ic->offset_y == 0) {
int height;
get_window_size(ic->client_window, NULL, &height);
root_x += 4;
root_y += height;
} else {
root_x += ic->offset_x;
root_y += ic->offset_y;
}
preedit_move(root_x, root_y);
}
static int
_xim_set_ic_values(XIMHandle* handle, IMChangeICStruct* proto)
{
__xim_ic_events(proto);
IC* ic = icmgr_get(proto->icid);
/* some crapy swing application will have synchronization problems */
if (ic == NULL)
return 1;
LOG("XIM_SET_IC_VALUES %d", proto->icid);
ic->offset_x = __preedit_x;
ic->offset_y = __preedit_y;
IC* cur_ic = icmgr_get_current();
/* if we change the current ic position, we might wanna
* move it along the way
*/
if (cur_ic != NULL && ic->icid == cur_ic->icid) {
__move_preedit(ic);
}
return 1;
}
static int
_xim_get_ic_values(XIMHandle* handle, IMChangeICStruct* proto)
{
LOG("XIM_GET_IC_VALUES %d", proto->icid);
XICAttribute* ic_attr = proto->ic_attr;
int i;
for (i = 0; i < (int) proto->ic_attr_num; i++) {
if (strcmp(XNFilterEvents, ic_attr[i].name) == 0) {
ic_attr[i].value = malloc(sizeof(CARD32));
*((CARD32*) ic_attr[i].value) = KeyPressMask | KeyPress;
ic_attr[i].value_length = sizeof(CARD32);
}
}
return 1;
}
static int
_xim_trigger_notify(XIMHandle* handle, IMTriggerNotifyStruct* proto)
{
LOG("trigger key pressed, %d", proto->icid);
IC* ic = icmgr_get(proto->icid);
if (ic == NULL)
return 1;
icmgr_set_current(proto->icid);
ic->is_enabled = true;
xim_start_preedit(handle);
icmgr_refresh();
return 1;
}
static int
_xim_set_ic_focus(XIMHandle* handle, IMChangeFocusStruct* proto)
{
DEBUG("%d", proto->icid);
LOG("set focus on ic %d %d", proto->icid, preedit_status());
/* if use didn't finish typing, we won't focus to new context */
if (preedit_status() == false) {
icmgr_set_current(proto->icid);
}
icmgr_refresh();
return 1;
}
static int
_xim_unset_ic_focus(XIMHandle* handle, IMChangeFocusStruct* proto)
{
LOG("unset focus on ic %d", proto->icid);
IC* ic = icmgr_get_current();
if (ic != NULL && ic->icid == proto->icid && preedit_status() == false) {
icmgr_clear_current();
icmgr_refresh();
}
return 1;
}
extern int _xim_forward_event(XIMHandle* handle,
IMForwardEventStruct* proto);
static int
_imdkit_protocol_hanlder(XIMHandle* handle, IMProtocol* proto)
{
assert(handle != NULL);
assert(proto != NULL);
switch (proto->major_code) {
case XIM_OPEN:
return _xim_open(handle, (IMOpenStruct *) proto);
case XIM_CLOSE:
return _xim_close(handle, (IMCloseStruct*) proto);
case XIM_CREATE_IC:
return _xim_create_ic(handle, (IMChangeICStruct*) proto);
case XIM_DESTROY_IC:
return _xim_destroy_ic(handle, (IMChangeICStruct*) proto);
case XIM_SET_IC_VALUES:
return _xim_set_ic_values(handle, (IMChangeICStruct*) proto);
case XIM_GET_IC_VALUES:
return _xim_get_ic_values(handle, (IMChangeICStruct*) proto);
case XIM_TRIGGER_NOTIFY:
return _xim_trigger_notify(handle, (IMTriggerNotifyStruct*) proto);
case XIM_FORWARD_EVENT:
return _xim_forward_event(handle, (IMForwardEventStruct *) proto);
case XIM_SET_IC_FOCUS:
return _xim_set_ic_focus(handle, (IMChangeFocusStruct *) proto);
case XIM_UNSET_IC_FOCUS:
return _xim_unset_ic_focus(handle, (IMChangeFocusStruct *) proto);
default:
LOG("unhandled major code %d", proto->major_code);
return 1;
}
}
static XIMHandle*
_open_imdkit(const char* _server_name, const char* _locale)
{
XIMStyle ims_styles_onspot [] = {
XIMPreeditPosition | XIMStatusArea, //OverTheSpot
XIMPreeditPosition | XIMStatusNothing, //OverTheSpot
XIMPreeditPosition | XIMStatusNone, //OverTheSpot
XIMPreeditNothing | XIMStatusNothing, //Root
XIMPreeditNothing | XIMStatusNone, //Root
0
};
XIMEncoding ims_encodings[] = {
"COMPOUND_TEXT",
0
};
/* this is rarely documentated, the trigger condition is
*
* keycode == keysym && (state & modifier_mask) == modifier
*
* where keycode and state is the user pressed
*/
hotkey_t hk;
settings_get(TRIGGER_KEY, &hk);
XIMTriggerKey trigger = {
.keysym = hk.keysym,
.modifier = hk.modifiers,
.modifier_mask = STATE_MASK
};
XIMTriggerKeys keys;
XIMStyles styles;
XIMEncodings encodings;
styles.count_styles =
sizeof (ims_styles_onspot)/sizeof (XIMStyle) - 1;
styles.supported_styles = ims_styles_onspot;
encodings.count_encodings =
sizeof (ims_encodings)/sizeof (XIMEncoding) - 1;
encodings.supported_encodings = ims_encodings;
keys.count_keys = 1;
keys.keylist = &trigger;
Window win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy),
0, 0, 1, 1, 1, 0, 0);
XSelectInput(dpy, win,
ExposureMask | ButtonPressMask | ButtonReleaseMask
| ButtonMotionMask | VisibilityChangeMask);
XIMHandle* handle =
IMOpenIM(dpy,
IMModifiers, "Xi18n",
IMServerWindow, win,
IMServerName, _server_name,
IMLocale, _locale,
IMServerTransport, "X/",
IMInputStyles, &styles,
IMEncodingList, &encodings,
IMProtocolHandler, _imdkit_protocol_hanlder,
IMFilterEventMask, KeyPressMask | KeyReleaseMask,
IMOnKeysList, &keys,
NULL);
if (handle == NULL) {
fprintf(stderr, "Startup xim server failed.\n");
fprintf(stderr, "Your locale is %s, please file a bug.", _locale);
}
return handle;
}
XIMHandle*
create_xim_server(const char* server_name, const char* locale)
{
XIMHandle* handle = _open_imdkit(server_name, locale);
icmgr_init();
return handle;
}
void
xim_start_preedit(XIMHandle* handle)
{
IC* ic = icmgr_get_current();
if (ic == NULL)
return;
IMPreeditStateStruct ps;
ps.icid = ic->icid;
ps.connect_id = ic->connect_id;
IMPreeditStart(handle, (XPointer) &ps);
}
void
xim_cancel_preedit(XIMHandle* handle)
{
IC* ic = icmgr_get_current();
if (ic == NULL)
return;
IMPreeditStateStruct ps;
ps.icid = ic->icid;
ps.connect_id = ic->connect_id;
IMPreeditEnd(handle, (XPointer) &ps);
}
void
xim_commit_preedit(XIMHandle* handle, const char* result_str)
{
IC* ic = icmgr_get_current();
if (ic == NULL)
return;
XTextProperty tp;
IMCommitStruct cs;
Xutf8TextListToTextProperty(dpy, (char**) &result_str, 1,
XCompoundTextStyle, &tp);
cs.major_code = XIM_COMMIT;
cs.icid = ic->icid;
cs.connect_id = ic->connect_id;
cs.flag = XimLookupChars;
cs.commit_string = (char*) tp.value;
IMCommitString(handle, (XPointer) &cs);
XFree(tp.value);
}