Compare commits

..
Author SHA1 Message Date
deepend 6cf145ea11 fix build warnings 2026-09-30 13:36:45 -06:00
deepend 9e39d02818 fix application crash + other fixes 2026-09-30 13:36:45 -06:00
deepend 85f8bfe4a2 enable storage information 2026-09-30 13:36:45 -06:00
deepend 93ae27a132 fix Win32 IP Helper includes for sysinfo 2026-09-30 13:36:45 -06:00
deepend 5be45f10e0 bring sysinfo into 2026 2026-09-30 13:36:45 -06:00
46 changed files with 1982 additions and 1288 deletions
-30
View File
@@ -1,30 +0,0 @@
# Optional Python asyncio integration
`run_async(coroutine, context=None, interval=10)` returns an AsyncTask with
`done()`, `cancel()` and `result()`. Each invocation owns a separate event loop
pumped by an existing main-loop timer. A ready stop callback ensures selector
polling never blocks the UI. The captured context is restored after every pump;
a closed context cancels the work. No process-wide event-loop policy is changed.
Existing synchronous scripts are unaffected. Use `asyncio.get_running_loop()`
inside the coroutine. Child tasks use the same loop/context and are cancelled
when the main coroutine finishes or the script unloads. Cancellation cleanup is
bounded; tasks must not suppress cancellation indefinitely. Cleanup uses the
captured context while it remains open; after it closes, cancellation cleanup uses
the current valid context. Use explicit Context methods for context-specific
output in finally blocks, and do not issue context-dependent commands there. This provides normal
asyncio sockets/timers, not Windows subprocess support or a new HTTP library.
Asyncio hostname resolution can use its executor; use a resolver appropriate to
your library if you require no worker threads. Blocking work inside a coroutine
still blocks the client and must be avoided.
```python
import asyncio
import zoitechat
async def later():
await asyncio.sleep(1)
zoitechat.prnt('Still in the context where this task was started')
task = zoitechat.run_async(later())
```
-35
View File
@@ -1,35 +0,0 @@
# Script-supported capabilities
`register_capability(name, userdata=None, connection_id=None)` returns a normal
hook handle. Register before connecting/reconnecting. Core requests the name only
if advertised in CAP LS or CAP NEW. Existing built-in requests remain enabled;
SASL and STS registration is rejected so scripts cannot change authentication or
transport policy through this API. None/-1 means any connection (zero is a valid
connection ID). Names are lowercase ASCII alphanumerics plus - . / _, 1-200 bytes.
Duplicate registrations do not duplicate requests for an advertised token.
`unhook(handle)` or script/plugin unload removes future request ownership. It
intentionally does not disable a capability already acknowledged on a live
connection, because another plugin/core may depend on it. Registering after CAP
LS does not retrospectively negotiate an already-advertised capability: reconnect
or wait for CAP NEW. Core retains ownership of CAP END; this API does not extend
SASL waiting or delay registration awaiting plugin-only CAP ACKs.
`hook_capability(name, callback, userdata=None, priority=PRI_NORM)` independently
observes negotiation through existing raw CAP hooks. Callback receives
`(CapabilityEvent, userdata)` with name, subcommand, value, enabled, context and
connection_id. LS/NEW mean advertised (`enabled=None`), ACK/LIST mean enabled
(unless a negative ACK), NAK means rejected and DEL means removed. Callback
returns are ignored so this observer cannot eat core negotiation. Existing raw
hooks may still eat CAP before this hook according to their normal priorities.
C: `zoitechat_register_capability(ph, name, flags, connection_id, userdata)`;
flags=0, ID=-1 for any. Check get_info("api_plugin_caps") for "1" before using
the appended Windows slot. Use existing server CAP hooks for notifications.
Requests are split at complete token boundaries within the existing wire limit.
```python
registration = zoitechat.register_capability('draft/example')
zoitechat.hook_capability('draft/example', lambda event, data:
event.context.prnt('{}: {}'.format(event.subcommand, event.enabled)))
```
@@ -1,24 +0,0 @@
# Optional plugin API additions (discussion #144)
Existing hooks, callbacks, return values, context lookup, strings and menu behaviour
remain unchanged. The C plugin function table gains four fixed append-only slots;
unused slots are NULL. Older binaries use the unchanged prefix. A plugin must check
the documented get_info() feature key before accessing a new slot on an older host.
New Python modules are installed by Meson and the existing Windows copy/installer
pipeline. Only modules present in the installation are exported; imports of
zoitechat, hexchat and xchat retain their compatibility aliases.
All APIs run on the client main thread unless explicitly documented otherwise.
New reserved flags must be zero. These changes do not make old API calls thread-safe.
Nested hook dispatch defers deleted-hook reclamation until its outermost return,
so new observers can safely emit events and unhook themselves or other hooks.
The optional asyncio module requires Python 3.7 or newer; older supported
interpreters skip that module and retain the existing scripting API.
Python reload drops the script/API modules while retaining the embedding module
so callback and lib.ph pointer types keep the same CFFI identity.
Server-hook insertion matches both raw and attrs hooks so documented priorities
are honoured for mixed registrations, including new filtered attrs callbacks.
-24
View File
@@ -1,24 +0,0 @@
# Connection and context navigation
`get_connections()` returns one Connection snapshot per live connection:
`id`, `network`, `server`, `context`. Connection IDs distinguish simultaneous
connections with identical names. Prefer its server-tab context where present.
`get_connection(context=None)` finds the owner of a live context.
`get_contexts(connection_id=None, types=None)` lists live contexts, optionally
filtered by connection ID (or a Connection object) and session types: server=1,
channel=2, query=3. `connection.contexts(types=None)` is a convenience method.
`find_context_by_id(connection_id, channel=None)` / `connection.find(channel)`
resolve using that connection's IRC comparator; no channel selects the server tab.
Missing/closed contexts return None. Existing find_context() remains unchanged.
These helpers use the existing channels list's ID/context fields, so no new C
context ABI is needed. Snapshots are not persistent IDs across client restarts;
refresh them after closing/recreating connections. Always check Context.set()
when explicitly selecting a context that might have closed.
```python
connection = zoitechat.get_connection()
for context in connection.contexts(types=(2,)):
context.prnt('A channel on this exact connection')
```
-26
View File
@@ -1,26 +0,0 @@
# Semantic IRC hooks
`on_join`, `on_part`, `on_quit`, `on_nick`, `on_kick`, `on_message`, and
`on_notice` accept `(callback, userdata=None, priority=PRI_NORM)`. The general
form is `hook_event(command, callback, userdata=None, priority=PRI_NORM)`.
Callbacks receive `(event, userdata)`, where the immutable IRCEvent has
command, prefix, nick, user, host, params, context, connection_id and time.
`params` preserves the trailing parameter, including spaces; JOIN parameters
also retain extended-join account/realname data when supplied by the server.
These wrappers use existing server hooks, before core command processing;
return the ordinary EAT_* constants. EAT_ZOITECHAT suppresses the entire IRC
command (including client state updates), not just its text. Use EAT_NONE for
observation. Other plugins' priority and eating rules remain unchanged.
The context is the core-selected context, which can be the server tab when a
query/channel does not yet exist. QUIT is one event per incoming command,
not one callback per affected channel. `event.time` is the existing server-time
attribute (zero when unavailable); other message tags are not exposed here.
```python
def joined(event, userdata):
event.context.prnt('{} joined {}'.format(event.nick, event.params[0]))
return zoitechat.EAT_NONE
zoitechat.on_join(joined)
```
-19
View File
@@ -1,19 +0,0 @@
# Python descriptor hooks
`hook_fd(fd, callback, userdata=None, flags=FD_READ)` returns an ordinary hook
handle accepted by `unhook()`. Callback arguments are `(fd, ready_flags, userdata)`;
return True to retain the watch and False/None to remove it. Exceptions print a
traceback and remove the watch. Unloading the script removes its watches.
The callback runs in the context captured during registration, and that context
is restored afterwards. A closed context removes the watch.
Use nonblocking IO and bounded reads/writes. The descriptor belongs to the script;
unhook before closing it. On Windows socket descriptors use the default flags;
CRT descriptors need FD_NOTSOCKET. The existing C API uses int descriptors: values
outside its range are rejected rather than truncated. This does not add threads.
Example:
```python
watch = zoitechat.hook_fd(sock.fileno(), on_readable, flags=zoitechat.FD_READ)
# def on_readable(fd, flags, userdata): ...; return True
```
-24
View File
@@ -1,24 +0,0 @@
# Core-filtered hooks
Python: `hook_server_filtered(name, callback, userdata=None, priority=PRI_NORM,
connection_id=None, channel=None)` and `hook_print_filtered(...)`.
Callbacks match the existing attrs variants: `(word, word_eol, userdata, attrs)`.
The connection ID distinguishes duplicate names; None/-1 means any connection
(including connection ID zero). A channel restriction uses that connection's
IRC comparator. None means any context; an empty string is an exact restriction.
C: the corresponding zoitechat_hook_*_filtered functions accept
`(ph, name, pri, flags, connection_id, channel, callback, userdata)`;
flags=0, ID=-1 for any and NULL channel for any. Check
`get_info("api_context_filters")` for "1" before accessing the new Windows slots.
Filtering takes place before Python/interpreter entry. Existing unrestricted
hooks, priority, EAT semantics and unload/unhook handling are unchanged.
Filters refer to the core-selected context, not an IRC target parsed from the
line: unknown queries/channels can still have a server-tab context, and QUIT
has its usual single server-hook context. Connection IDs last only this process.
```python
zoitechat.hook_print_filtered('Channel Message', callback,
connection_id=zoitechat.get_prefs('id'), channel='#zoitechat')
```
-21
View File
@@ -1,21 +0,0 @@
# Explicit IRC comparison keys
`IRCName(text, context=None, casemapping=None, connection_id=None)` is an
immutable helper, not a str subclass. Construct it explicitly for dictionary/set
keys; existing get_info/list/event values remain normal strings.
Equality and hashing include the connection ID and captured casemapping, so
identical-looking names on different connections are different keys. An IRCName
never compares equal to a plain str. `str(key)` / `key.text` recover the spelling.
`irc_casefold(text, casemapping='rfc1459')` supports ascii, strict-rfc1459 and
rfc1459. Only ASCII IRC characters fold; Unicode casefold would be incorrect.
The new `get_info('casemapping')` reports the comparator actually used by core.
Current master handles ASCII and RFC1459; this patch does not change its handling
of advertised strict-rfc1459. An explicit mapping is available for scripts that
need strict-rfc1459 independently. Keys capture their mapping so hashes stay
stable; rebuild a collection if the connection's mapping changes.
```python
seen = {zoitechat.IRCName('[SomeNick]')}
assert zoitechat.IRCName('{somenick}') in seen # RFC1459 connection
```
-21
View File
@@ -1,21 +0,0 @@
# Post-display print observers
Python: `hook_print_after(name, callback, userdata=None, priority=PRI_NORM)`.
The callback is `(word, word_eol, userdata, attrs)`, matching hook_print_attrs().
It runs after display_event() has handled the text, so prnt() follows the original
text. Return values are ignored. Events eaten by a pre-hook or hidden by client
preferences do not invoke these observers. This is post-display notification,
not a notification that the entire incoming IRC command has finished updating state.
C: `zoitechat_hook_print_after(ph, name, priority, flags, callback, userdata)`;
flags must be zero. Check `get_info("api_hook_print_after")` for "1" before
accessing the appended Windows function-table slot. Existing pre-hook timing is
unchanged. Unhook/unload use existing lifecycle functions. Recursive emission
skips an already-running observer to prevent it immediately invoking itself.
```python
def after(word, word_eol, userdata, attrs):
zoitechat.prnt('This follows the original channel message')
zoitechat.hook_print_after('Channel Message', after)
```
-2
View File
@@ -710,7 +710,6 @@ static int handle_fishlim(char *word[], char *word_eol[], void *userdata) {
gtk_box_pack_start(GTK_BOX(content), grid, TRUE, TRUE, 0);
fishlim_target_entry = gtk_entry_new();
gtk_widget_set_hexpand(fishlim_target_entry, TRUE);
if (target)
gtk_entry_set_text(GTK_ENTRY(fishlim_target_entry), target);
gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target nick or channel"), 0, 0, 1, 1);
@@ -761,7 +760,6 @@ static int handle_fishlim(char *word[], char *word_eol[], void *userdata) {
g_signal_connect(view, "row-activated", G_CALLBACK(fishlim_gui_row_activated), NULL);
scroll = gtk_scrolled_window_new(NULL, NULL);
gtk_widget_set_hexpand(scroll, TRUE);
gtk_widget_set_vexpand(scroll, TRUE);
gtk_container_add(GTK_CONTAINER(scroll), view);
gtk_grid_attach(GTK_GRID(grid), scroll, 0, 6, 3, 1);
-21
View File
@@ -383,24 +383,3 @@ def find_context(server=None, channel=None):
return None
return Context(ctx)
# Optional additive APIs live in separate modules so builds can select features.
def __load_additive_features():
import importlib
import os
for suffix in ('fd', 'events', 'async', 'names', 'contexts',
'post', 'filters', 'caps'):
if suffix == 'async' and sys.version_info < (3, 7):
continue
name = '_zoitechat_' + suffix
if not os.path.isfile(os.path.join(os.path.dirname(__file__), name + '.py')):
continue
module = importlib.import_module(name)
for symbol in module.__all__:
globals()[symbol] = getattr(module, symbol)
__all__.append(symbol)
__load_additive_features()
-100
View File
@@ -1,100 +0,0 @@
"""Opt-in asyncio tasks pumped without blocking the client main loop."""
import asyncio
import operator
import traceback
import _zoitechat as api
__all__ = ['AsyncTask', 'run_async']
class AsyncTask:
"""A script-owned task and isolated event loop; cancel() is cooperative."""
def __init__(self, coroutine, plugin, context, interval):
self._plugin = plugin
self._context = context
self._loop = asyncio.new_event_loop()
self._task = self._loop.create_task(coroutine)
self._closed = False
self._unload_hook = api.hook_unload(lambda data: self._close())
self._timer_hook = api.hook_timer(interval, self._tick)
def done(self):
return self._task.done()
def cancel(self):
if not self._closed:
return self._task.cancel()
return False
def result(self):
return self._task.result()
def _pump(self):
# A ready stop callback forces the selector's timeout to zero.
self._loop.call_soon(self._loop.stop)
self._loop.run_forever()
def _close(self):
if self._closed:
return
self._closed = True
previous = api.get_context()
self._context.set()
try:
pending = asyncio.all_tasks(self._loop)
for task in pending:
task.cancel()
# Bound cleanup work; tasks must cooperate with cancellation.
for unused in range(3):
if not any(not task.done() for task in pending):
break
self._pump()
for task in pending:
if not task.done():
self._loop.call_exception_handler({
'message': 'Script task ignored cancellation during unload',
'task': task,
})
finally:
self._loop.close()
previous.set()
def _tick(self, userdata):
if self._closed:
return False
previous = api.get_context()
if not self._context.set():
self._close()
self._plugin.remove_hook(self._unload_hook)
return False
try:
self._pump()
if not self._task.done():
return True
try:
self._task.result()
except asyncio.CancelledError:
pass
except Exception:
traceback.print_exc()
self._close()
self._plugin.remove_hook(self._unload_hook)
return False
except Exception:
traceback.print_exc()
self._close()
self._plugin.remove_hook(self._unload_hook)
return False
finally:
previous.set()
def run_async(coroutine, context=None, interval=10):
"""Schedule a coroutine in a captured context; return an AsyncTask handle."""
interval = operator.index(interval)
if interval < 1 or interval > 2147483647:
raise ValueError('interval must be a positive C int in milliseconds')
if not asyncio.iscoroutine(coroutine):
raise TypeError('run_async expects a coroutine object')
plugin = api.__get_current_plugin()
return AsyncTask(coroutine, plugin, context or api.get_context(), interval)
-64
View File
@@ -1,64 +0,0 @@
"""Script-owned capability requests and structured negotiation observations."""
from collections import namedtuple
import operator
import _zoitechat as api
from _zoitechat_embedded import ffi, lib
__all__ = ['CapabilityEvent', 'register_capability', 'hook_capability']
CapabilityEvent = namedtuple('CapabilityEvent',
'name subcommand value enabled context connection_id')
def register_capability(name, userdata=None, connection_id=None):
"""Request an advertised capability on next negotiation; unhook releases ownership."""
connection_id = -1 if connection_id is None else operator.index(connection_id)
if connection_id < -1 or connection_id > 2147483647:
raise ValueError('connection_id must fit the C API; -1 means any')
plugin = api.__get_current_plugin()
hook = plugin.add_hook(None, userdata)
handle = lib.zoitechat_register_capability(lib.ph, name.encode(), 0,
connection_id, hook.handle)
if handle == ffi.NULL:
hook.is_unload = True
plugin.remove_hook(id(hook))
raise ValueError('invalid capability name, reserved capability or connection ID')
hook.zoitechat_hook = handle
return id(hook)
def hook_capability(name, callback, userdata=None, priority=api.PRI_NORM):
"""Observe LS/NEW/ACK/NAK/DEL/LIST; this hook does not itself request the capability."""
if not callable(callback):
raise TypeError('callback must be callable')
def dispatch(word, word_eol, data):
if not word_eol:
return api.EAT_NONE
line = word_eol[0]
if line.startswith(':'):
unused, separator, line = line.partition(' ')
parts = line.split(' ', 3)
if len(parts) != 4 or parts[0].upper() != 'CAP':
return api.EAT_NONE
subcommand, tokens = parts[2].upper(), parts[3]
if subcommand not in ('LS', 'NEW', 'ACK', 'NAK', 'DEL', 'LIST'):
return api.EAT_NONE
if tokens.startswith('* '):
tokens = tokens[2:]
tokens = tokens.lstrip(':')
for token in tokens.split():
negative = token.startswith('-')
cap, equals, value = token.lstrip('-').partition('=')
if cap != name:
continue
enabled = None
if subcommand in ('ACK', 'LIST'):
enabled = not negative
elif subcommand in ('DEL', 'NAK'):
enabled = False
event = CapabilityEvent(cap, subcommand, value if equals else None,
enabled, api.get_context(), api.get_prefs('id'))
callback(event, data)
return api.EAT_NONE # observing CAP must not suppress core negotiation
return api.hook_server('CAP', dispatch, userdata, priority)
-75
View File
@@ -1,75 +0,0 @@
"""Navigate live connections by ID instead of ambiguous server/network names."""
from collections import namedtuple
import operator
import _zoitechat as api
__all__ = ['Connection', 'get_connections', 'get_connection', 'get_contexts',
'find_context_by_id']
class Connection(namedtuple('_Connection', 'id network server context')):
__slots__ = ()
def contexts(self, types=None):
return get_contexts(self.id, types)
def find(self, channel=None):
return find_context_by_id(self.id, channel)
def _id(connection):
return operator.index(connection.id if isinstance(connection, Connection) else connection)
def get_connections():
"""Return one snapshot per live connection, including disconnected ones."""
entries = api.get_list('channels')
by_id = {}
order = []
for entry in entries:
if entry.id not in by_id:
order.append(entry.id)
if entry.id not in by_id or entry.type == 1:
by_id[entry.id] = Connection(entry.id, entry.network, entry.server,
entry.context)
return [by_id[connection_id] for connection_id in order]
def get_connection(context=None):
"""Find the owning connection of a Context; return None when it has closed."""
context = context or api.get_context()
for entry in api.get_list('channels'):
if entry.context == context:
for connection in get_connections():
if connection.id == entry.id:
return connection
return None
def get_contexts(connection_id=None, types=None):
"""Return contexts, optionally restricted to a connection and session types."""
if connection_id is not None:
connection_id = _id(connection_id)
if types is not None:
types = frozenset(types)
return [entry.context for entry in api.get_list('channels')
if (connection_id is None or entry.id == connection_id)
and (types is None or entry.type in types)]
def find_context_by_id(connection_id, channel=None):
"""Resolve a live context on exactly one connection; None selects its server tab."""
connection_id = _id(connection_id)
previous = api.get_context()
try:
for entry in api.get_list('channels'):
if entry.id != connection_id:
continue
if channel is None:
if entry.type == 1:
return entry.context
elif entry.context.set() and api.nickcmp(entry.channel, channel) == 0:
return entry.context
finally:
previous.set()
return None
-77
View File
@@ -1,77 +0,0 @@
"""Structured, opt-in hooks before the IRC command's core processing."""
from collections import namedtuple
import _zoitechat as api
__all__ = ['IRCEvent', 'hook_event', 'on_join', 'on_part', 'on_quit',
'on_nick', 'on_kick', 'on_message', 'on_notice']
IRCEvent = namedtuple('IRCEvent', 'command prefix nick user host params context connection_id time')
_SUPPORTED = frozenset(('JOIN', 'PART', 'QUIT', 'NICK', 'KICK', 'PRIVMSG', 'NOTICE'))
def _parse(line):
prefix = ''
if line.startswith(':'):
prefix, separator, line = line[1:].partition(' ')
if not separator:
return None
command, separator, rest = line.partition(' ')
params = []
while rest:
rest = rest.lstrip(' ')
if rest.startswith(':'):
params.append(rest[1:])
break
param, separator, rest = rest.partition(' ')
if param:
params.append(param)
nick, marker, userhost = prefix.partition('!')
user, marker, host = userhost.partition('@')
return command.upper(), prefix, nick, user, host, tuple(params)
def hook_event(command, callback, userdata=None, priority=api.PRI_NORM):
"""callback(event, userdata) returns existing EAT_* constants."""
command = command.upper()
if command not in _SUPPORTED:
raise ValueError('unsupported semantic event: ' + command)
if not callable(callback):
raise TypeError('callback must be callable')
def dispatch(word, word_eol, data, attrs):
if not word_eol:
return api.EAT_NONE
parsed = _parse(word_eol[0])
if parsed is None or parsed[0] != command:
return api.EAT_NONE
event = IRCEvent(*(parsed + (api.get_context(), api.get_prefs('id'), attrs.time)))
return callback(event, data)
return api.hook_server_attrs(command, dispatch, userdata, priority)
def on_join(callback, userdata=None, priority=api.PRI_NORM):
return hook_event('JOIN', callback, userdata, priority)
def on_part(callback, userdata=None, priority=api.PRI_NORM):
return hook_event('PART', callback, userdata, priority)
def on_quit(callback, userdata=None, priority=api.PRI_NORM):
return hook_event('QUIT', callback, userdata, priority)
def on_nick(callback, userdata=None, priority=api.PRI_NORM):
return hook_event('NICK', callback, userdata, priority)
def on_kick(callback, userdata=None, priority=api.PRI_NORM):
return hook_event('KICK', callback, userdata, priority)
def on_message(callback, userdata=None, priority=api.PRI_NORM):
return hook_event('PRIVMSG', callback, userdata, priority)
def on_notice(callback, userdata=None, priority=api.PRI_NORM):
return hook_event('NOTICE', callback, userdata, priority)
-39
View File
@@ -1,39 +0,0 @@
"""File descriptor readiness hooks; callbacks execute on the main thread."""
import operator
import _zoitechat as api
from _zoitechat_embedded import ffi, lib
__all__ = ['FD_READ', 'FD_WRITE', 'FD_EXCEPTION', 'FD_NOTSOCKET', 'hook_fd']
FD_READ, FD_WRITE, FD_EXCEPTION, FD_NOTSOCKET = 1, 2, 4, 8
def hook_fd(fd, callback, userdata=None, flags=FD_READ):
"""callback(fd, ready_flags, userdata): True keeps the watch, False removes it."""
fd = operator.index(fd)
flags = operator.index(flags)
if fd < 0 or fd > 2147483647:
raise ValueError('fd must fit the C API nonnegative int descriptor')
if flags & ~15 or not flags & 7:
raise ValueError('flags must select READ, WRITE or EXCEPTION')
if not callable(callback):
raise TypeError('callback must be callable')
plugin = api.__get_current_plugin()
context = api.get_context()
def dispatch(descriptor, ready, data):
previous = api.get_context()
if not context.set():
return False
try:
return callback(descriptor, ready, data)
finally:
previous.set()
hook = plugin.add_hook(dispatch, userdata)
handle = lib.zoitechat_hook_fd(lib.ph, fd, flags, lib._on_fd_hook, hook.handle)
if handle == ffi.NULL:
hook.is_unload = True
plugin.remove_hook(id(hook))
raise RuntimeError('unable to watch descriptor')
hook.zoitechat_hook = handle
return id(hook)
-36
View File
@@ -1,36 +0,0 @@
"""Context restrictions evaluated by core before entering Python."""
import operator
import _zoitechat as api
from _zoitechat_embedded import ffi, lib
__all__ = ['hook_server_filtered', 'hook_print_filtered']
def _register(name, callback, userdata, priority, connection_id, channel, server):
if not callable(callback):
raise TypeError('callback must be callable')
connection_id = -1 if connection_id is None else operator.index(connection_id)
if connection_id < -1 or connection_id > 2147483647:
raise ValueError('connection_id must fit the C API; -1 means any')
plugin = api.__get_current_plugin()
hook = plugin.add_hook(callback, userdata)
register = lib.zoitechat_hook_server_filtered if server else lib.zoitechat_hook_print_filtered
dispatch = lib._on_server_attrs_hook if server else lib._on_print_attrs_hook
handle = register(lib.ph, name.encode(), priority, 0, connection_id,
ffi.NULL if channel is None else channel.encode(), dispatch, hook.handle)
if handle == ffi.NULL:
hook.is_unload = True
plugin.remove_hook(id(hook))
raise RuntimeError('unable to install filtered hook')
hook.zoitechat_hook = handle
return id(hook)
def hook_server_filtered(name, callback, userdata=None, priority=api.PRI_NORM,
connection_id=None, channel=None):
return _register(name, callback, userdata, priority, connection_id, channel, True)
def hook_print_filtered(name, callback, userdata=None, priority=api.PRI_NORM,
connection_id=None, channel=None):
return _register(name, callback, userdata, priority, connection_id, channel, False)
-74
View File
@@ -1,74 +0,0 @@
"""Explicit immutable IRC names; existing string-returning APIs are unchanged."""
import _zoitechat as api
__all__ = ['IRCName', 'irc_casefold']
def irc_casefold(text, casemapping='rfc1459'):
"""Fold ASCII IRC characters only; never apply Unicode lower()/casefold()."""
if casemapping not in ('ascii', 'strict-rfc1459', 'rfc1459'):
raise ValueError('unsupported IRC casemapping')
extra = {'[': '{', ']': '}', '\\': '|'} if casemapping != 'ascii' else {}
if casemapping == 'rfc1459':
extra['^'] = '~'
return ''.join(chr(ord(c) + 32) if 'A' <= c <= 'Z' else extra.get(c, c)
for c in text)
class IRCName:
"""Equality/hash include connection identity and the captured casemapping."""
__slots__ = ('_text', '_casemapping', '_connection_id', '_key')
def __init__(self, text, context=None, casemapping=None, connection_id=None):
context = context or api.get_context()
if casemapping is None:
casemapping = context.get_info('casemapping')
if casemapping is None:
raise RuntimeError('host does not expose its comparison casemapping')
if connection_id is None:
previous = api.get_context()
if not context.set():
raise ValueError('context has closed')
try:
connection_id = api.get_prefs('id')
finally:
previous.set()
object.__setattr__(self, '_text', text)
object.__setattr__(self, '_casemapping', casemapping)
object.__setattr__(self, '_connection_id', connection_id)
object.__setattr__(self, '_key', (connection_id, casemapping,
irc_casefold(text, casemapping)))
def __setattr__(self, name, value):
raise AttributeError('IRCName is immutable')
@property
def text(self):
return self._text
@property
def casemapping(self):
return self._casemapping
@property
def connection_id(self):
return self._connection_id
def __str__(self):
return self._text
def __repr__(self):
return 'IRCName({!r}, casemapping={!r}, connection_id={!r})'.format(
self._text, self._casemapping, self._connection_id)
def __eq__(self, other):
if not isinstance(other, IRCName):
return NotImplemented
return self._key == other._key
def __ne__(self, other):
result = self.__eq__(other)
return NotImplemented if result is NotImplemented else not result
def __hash__(self):
return hash(self._key)
-21
View File
@@ -1,21 +0,0 @@
"""Opt-in post-display print observers."""
import _zoitechat as api
from _zoitechat_embedded import ffi, lib
__all__ = ['hook_print_after']
def hook_print_after(name, callback, userdata=None, priority=api.PRI_NORM):
"""callback(word, word_eol, userdata, attrs); its return value is ignored."""
if not callable(callback):
raise TypeError('callback must be callable')
plugin = api.__get_current_plugin()
hook = plugin.add_hook(callback, userdata)
handle = lib.zoitechat_hook_print_after(lib.ph, name.encode(), priority, 0,
lib._on_print_attrs_hook, hook.handle)
if handle == ffi.NULL:
hook.is_unload = True
plugin.remove_hook(id(hook))
raise RuntimeError('unable to install post-display hook')
hook.zoitechat_hook = handle
return id(hook)
-1
View File
@@ -38,7 +38,6 @@ extern "Python" int _on_print_attrs_hook(char **, zoitechat_event_attrs *, void
extern "Python" int _on_server_hook(char **, char **, void *);
extern "Python" int _on_server_attrs_hook(char **, char **, zoitechat_event_attrs *, void *);
extern "Python" int _on_timer_hook(void *);
extern "Python" int _on_fd_hook(int, int, void *);
extern "Python" int _on_plugin_init(char **, char **, char **, char *, char *);
extern "Python" int _on_plugin_deinit(void);
+1 -5
View File
@@ -27,11 +27,7 @@ python3_source = custom_target('python-bindings',
command: [find_program('generate_plugin.py'), '@INPUT@', '@OUTPUT@']
)
# Include any selected additive API modules alongside the compatibility modules.
python_api_files = run_command(python3, '-c',
'import glob,sys; print("\\n".join(sorted(glob.glob(sys.argv[1] + "/_zoitechat*.py"))))',
meson.current_source_dir(), check: true).stdout().strip().split('\n')
install_data(python_api_files + ['zoitechat.py', 'xchat.py', 'hexchat.py'],
install_data(['_zoitechat.py', 'zoitechat.py', 'xchat.py', 'hexchat.py'],
install_dir: join_paths(get_option('libdir'), 'zoitechat/python')
)
+1 -28
View File
@@ -304,28 +304,6 @@ def _on_timer_hook(userdata):
return 0
@ffi.def_extern()
def _on_fd_hook(fd, flags, userdata):
hook = ffi.from_handle(userdata)
keep = False
try:
keep = bool(hook.callback(fd, flags, hook.userdata))
except Exception:
traceback.print_exc()
if not keep:
# The C watch removes itself on return; do not unhook it a second time.
try:
hook.is_unload = True
# from_handle() yields a weak proxy: id(proxy) is not id(Hook).
for existing in hook.plugin.hooks:
if existing == hook:
hook.plugin.remove_hook(id(existing))
break
except ReferenceError:
pass # the callback already unhooked itself
return int(keep)
@ffi.def_extern()
def _on_say_command(word, word_eol, userdata):
"""Handle input in the special >>python<< tab.
@@ -612,12 +590,7 @@ def _on_plugin_deinit():
sys.stderr = sys.__stderr__
pydoc.help = pydoc.Helper()
# CFFI callbacks keep the embedding module's ffi/lib identity for the
# process lifetime; deleting it creates incompatible types on reload.
for mod in ('_zoitechat', 'zoitechat', 'hexchat', 'xchat',
'_zoitechat_fd', '_zoitechat_events', '_zoitechat_async',
'_zoitechat_names', '_zoitechat_contexts', '_zoitechat_post',
'_zoitechat_filters', '_zoitechat_caps'):
for mod in ('_zoitechat', 'zoitechat', 'xchat', '_zoitechat_embedded'):
try:
del sys.modules[mod]
-1
View File
@@ -52,7 +52,6 @@
<None Include="xchat.py" />
<None Include="hexchat.py" />
<None Include="_zoitechat.py" />
<None Include="_zoitechat_*.py" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(IntDir)python.c" />
-1
View File
@@ -52,7 +52,6 @@
<None Include="xchat.py" />
<None Include="hexchat.py" />
<None Include="_zoitechat.py" />
<None Include="_zoitechat_*.py" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(IntDir)python.c" />
+33 -17
View File
@@ -6,50 +6,66 @@ sysinfo_sources = [
sysinfo_deps = [
libgio_dep,
zoitechat_plugin_dep,
common_sysinfo_deps,
]
sysinfo_includes = []
sysinfo_cargs = ['-DHAVE_CONFIG_H']
system = host_machine.system()
if system == 'linux' or system == 'gnu' or system.startswith('gnu/') or system == 'darwin' or system == 'freebsd'
sysinfo_includes += 'shared'
if system == 'linux'
if get_option('gtk-frontend')
sysinfo_deps += dependency('gtk+-3.0', version: '>= 3.22')
endif
libpci = dependency('libpci', required: false, method: 'pkg-config')
if libpci.found()
sysinfo_deps += libpci
sysinfo_cargs += '-DHAVE_LIBPCI'
endif
sysinfo_includes += 'unix'
sysinfo_sources += 'unix/modern-backend.c'
elif system == 'freebsd' or system == 'gnu' or system.startswith('gnu/')
sysinfo_includes += ['shared', 'unix']
sysinfo_sources += [
'shared/df.c'
'shared/df.c',
'unix/backend.c',
'unix/match.c',
'unix/parse.c',
]
if system == 'linux' or system == 'gnu' or system.startswith('gnu/') or system == 'freebsd'
libpci = dependency('libpci', required: false, method: 'pkg-config')
if libpci.found()
sysinfo_deps += libpci
sysinfo_cargs += '-DHAVE_LIBPCI'
sysinfo_sources += 'unix/pci.c'
picidsdir = libpci.get_variable(pkgconfig : 'idsdir')
picidsdir = libpci.get_variable(pkgconfig: 'idsdir')
pciids = join_paths(picidsdir, 'pci.ids')
sysinfo_cargs += '-DPCIIDS_FILE="@0@"'.format(pciids)
endif
if get_option('gtk-frontend')
sysinfo_deps += dependency('gtk+-3.0', version: '>= 3.22')
endif
sysinfo_includes += 'unix'
sysinfo_sources += [
'unix/backend.c',
'unix/match.c',
'unix/parse.c',
]
elif system == 'darwin'
elif system == 'darwin'
sysinfo_includes += 'shared'
sysinfo_sources += 'shared/df.c'
add_languages('objc')
sysinfo_sources += 'osx/backend.m'
endif
elif system == 'windows'
sysinfo_sources += [
'win32/backend.c',
'../../src/common/sysinfo/win32/backend.c'
cc = meson.get_compiler('c')
sysinfo_deps += [
cc.find_library('advapi32', required: true),
cc.find_library('dxgi', required: true),
cc.find_library('dxguid', required: true),
cc.find_library('iphlpapi', required: true),
cc.find_library('winmm', required: true),
]
sysinfo_sources += 'win32/backend.c'
else
error('sysinfo: Unknown system?')
endif
+29 -8
View File
@@ -40,8 +40,8 @@ static zoitechat_plugin *ph;
static char name[] = "Sysinfo";
static char desc[] = "Display info about your hardware and OS";
static char version[] = "1.0";
static char sysinfo_help[] = "SysInfo Usage:\n /SYSINFO [-e|-o] [CLIENT|UI|OS|CPU|RAM|STORAGE|GPU|CHIPSET|SOUND|ETHERNET|UPTIME], print various details about your system or print a summary without arguments\n /SYSINFO SET <variable>\n";
static char version[] = "2.0";
static char sysinfo_help[] = "SysInfo Usage:\n /SYSINFO [-e|-o] [CLIENT|UI|OS|CPU|RAM|MEMORY|STORAGE|DISK|GPU|VGA|CHIPSET|SOUND|NETWORK|ETHERNET|UPTIME], print various details about your system or print a summary without arguments\n /SYSINFO SET <variable>\n";
typedef struct
{
@@ -68,11 +68,11 @@ static hwinfo hwinfos[] = {
{"os", "OS", sysinfo_backend_get_os},
{"cpu", "CPU", sysinfo_backend_get_cpu},
{"memory", "Memory", sysinfo_backend_get_memory},
{"storage", "Storage", sysinfo_backend_get_disk, TRUE},
{"storage", "Storage", sysinfo_backend_get_disk},
{"gpu", "GPU", sysinfo_backend_get_gpu},
{"chipset", "CHIPSET", sysinfo_backend_get_chipset, TRUE},
{"chipset", "Chipset", sysinfo_backend_get_chipset, TRUE},
{"sound", "Sound", sysinfo_backend_get_sound, TRUE},
{"ethernet", "Ethernet", sysinfo_backend_get_network, TRUE},
{"ethernet", "Network", sysinfo_backend_get_network, TRUE},
{"uptime", "Uptime", sysinfo_backend_get_uptime},
{NULL, NULL},
};
@@ -115,14 +115,30 @@ print_summary (gboolean announce)
g_free (output);
}
static const char *
normalize_info_name (const char *info)
{
if (!g_ascii_strcasecmp (info, "ram"))
return "memory";
if (!g_ascii_strcasecmp (info, "disk"))
return "storage";
if (!g_ascii_strcasecmp (info, "vga"))
return "gpu";
if (!g_ascii_strcasecmp (info, "network"))
return "ethernet";
return info;
}
static void
print_info (char *info, gboolean announce)
{
const char *normalized = normalize_info_name (info);
int i;
for (i = 0; hwinfos[i].name != NULL; i++)
{
if (!g_ascii_strcasecmp (info, hwinfos[i].name))
if (!g_ascii_strcasecmp (normalized, hwinfos[i].name))
{
char *str = hwinfos[i].callback();
if (str)
@@ -183,12 +199,17 @@ sysinfo_set_pref (char *key, char *value)
}
else if (g_str_has_prefix (key, "hide_"))
{
const char *normalized = normalize_info_name (key + 5);
int i;
for (i = 0; hwinfos[i].name != NULL; i++)
{
if (!strcmp (key + 5, hwinfos[i].name))
if (!strcmp (normalized, hwinfos[i].name))
{
sysinfo_set_pref_real (key, value, hwinfos[i].def);
char canonical[32];
g_snprintf (canonical, sizeof (canonical), "hide_%s", hwinfos[i].name);
sysinfo_set_pref_real (canonical, value, hwinfos[i].def);
return;
}
}
File diff suppressed because it is too large Load Diff
+707 -81
View File
@@ -1,28 +1,34 @@
/* ZoiteChat
* Copyright (c) 2011-2012 Berke Viktor.
/*
* Modern Windows SysInfo backend for ZoiteChat.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* Avoids WMI in the plugin hot path and uses native Win32/DXGI APIs.
*/
#include <stdio.h>
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0601
#elif _WIN32_WINNT < 0x0601
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0601
#endif
#include <winsock2.h>
#include <windows.h>
#include <wbemidl.h>
#include <winternl.h>
#include <dxgi.h>
#include <iphlpapi.h>
#include <ipifcons.h>
#include <mmsystem.h>
#ifdef _MSC_VER
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "winmm.lib")
#endif
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>
@@ -30,63 +36,548 @@
#include "config.h"
#endif
#include "../../../src/common/sysinfo/sysinfo.h"
#include "../format.h"
#include "../sysinfo-backend.h"
static char *get_memory_info (void);
#define SYSINFO_SEP " \xC2\xB7 "
char *
sysinfo_backend_get_sound (void)
typedef struct
{
return NULL;
}
char *os;
char *cpu;
char *gpu;
char *sound;
} WinStaticSnapshot;
char *
sysinfo_backend_get_network (void)
static WinStaticSnapshot snapshot;
static gsize os_initialized = 0;
static gsize cpu_initialized = 0;
static gsize gpu_initialized = 0;
static gsize sound_initialized = 0;
static char *wide_to_utf8 (const WCHAR *value);
static char *read_registry_string (HKEY root, const WCHAR *subkey, const WCHAR *value_name);
static gboolean read_registry_dword (HKEY root, const WCHAR *subkey, const WCHAR *value_name, DWORD *value);
static const char *get_native_arch (void);
static DWORD get_windows_build_number (void);
static char *build_os_string (void);
static DWORD count_physical_cores (void);
static char *build_cpu_string (void);
static char *build_gpu_string (void);
static char *build_sound_string (void);
static void ensure_os_snapshot (void);
static void ensure_cpu_snapshot (void);
static void ensure_gpu_snapshot (void);
static void ensure_sound_snapshot (void);
static char *format_usage (guint64 total, guint64 available);
static char *join_ptr_array (GPtrArray *items, const char *separator);
static void ptr_array_add_unique (GPtrArray *items, const char *value);
static gboolean adapter_looks_virtual (const char *name);
static char *
wide_to_utf8 (const WCHAR *value)
{
return NULL;
}
char *
sysinfo_backend_get_uptime (void)
{
return sysinfo_format_uptime (GetTickCount64 () / 1000);
}
char *
sysinfo_backend_get_disk (void)
{
guint64 hdd_capacity;
guint64 hdd_free_space;
sysinfo_get_hdd_info (&hdd_capacity, &hdd_free_space);
if (hdd_capacity != 0)
if (!value || value[0] == L'\0')
{
return sysinfo_format_disk(hdd_capacity, hdd_free_space);
return NULL;
}
return g_utf16_to_utf8 ((const gunichar2 *) value, -1, NULL, NULL, NULL);
}
static char *
read_registry_string (HKEY root, const WCHAR *subkey, const WCHAR *value_name)
{
HKEY key;
DWORD type = 0;
DWORD size = 0;
WCHAR *buffer;
char *result = NULL;
LONG status;
status = RegOpenKeyExW (root, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &key);
if (status != ERROR_SUCCESS)
{
return NULL;
}
status = RegQueryValueExW (key, value_name, NULL, &type, NULL, &size);
if (status != ERROR_SUCCESS || (type != REG_SZ && type != REG_EXPAND_SZ) || size < sizeof (WCHAR))
{
RegCloseKey (key);
return NULL;
}
buffer = g_malloc0 ((gsize) size + sizeof (WCHAR));
status = RegQueryValueExW (key, value_name, NULL, &type, (LPBYTE) buffer, &size);
if (status == ERROR_SUCCESS)
{
result = wide_to_utf8 (buffer);
if (result)
{
g_strstrip (result);
}
}
g_free (buffer);
RegCloseKey (key);
return result;
}
static gboolean
read_registry_dword (HKEY root, const WCHAR *subkey, const WCHAR *value_name, DWORD *value)
{
HKEY key;
DWORD type = 0;
DWORD size = sizeof (*value);
LONG status;
status = RegOpenKeyExW (root, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &key);
if (status != ERROR_SUCCESS)
{
return FALSE;
}
status = RegQueryValueExW (key, value_name, NULL, &type, (LPBYTE) value, &size);
RegCloseKey (key);
return status == ERROR_SUCCESS && type == REG_DWORD;
}
static const char *
get_native_arch (void)
{
SYSTEM_INFO info;
GetNativeSystemInfo (&info);
switch (info.wProcessorArchitecture)
{
case PROCESSOR_ARCHITECTURE_AMD64:
return "x64";
case PROCESSOR_ARCHITECTURE_ARM64:
return "ARM64";
case PROCESSOR_ARCHITECTURE_INTEL:
return "x86";
default:
return "unknown-arch";
}
}
static DWORD
get_windows_build_number (void)
{
typedef LONG (WINAPI *RtlGetVersionFn) (PRTL_OSVERSIONINFOW);
HMODULE ntdll;
RtlGetVersionFn rtl_get_version;
RTL_OSVERSIONINFOW version;
DWORD build = 0;
ntdll = GetModuleHandleW (L"ntdll.dll");
if (ntdll)
{
rtl_get_version = (RtlGetVersionFn) GetProcAddress (ntdll, "RtlGetVersion");
if (rtl_get_version)
{
ZeroMemory (&version, sizeof (version));
version.dwOSVersionInfoSize = sizeof (version);
if (rtl_get_version (&version) == 0)
{
build = version.dwBuildNumber;
}
}
}
return build;
}
static char *
build_os_string (void)
{
static const WCHAR current_version_key[] = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
char *product;
char *display_version;
char *registry_build;
char *fixed_product = NULL;
char *result;
DWORD ubr = 0;
DWORD build;
guint64 parsed_build = 0;
product = read_registry_string (HKEY_LOCAL_MACHINE, current_version_key, L"ProductName");
display_version = read_registry_string (HKEY_LOCAL_MACHINE, current_version_key, L"DisplayVersion");
registry_build = read_registry_string (HKEY_LOCAL_MACHINE, current_version_key, L"CurrentBuildNumber");
read_registry_dword (HKEY_LOCAL_MACHINE, current_version_key, L"UBR", &ubr);
build = get_windows_build_number ();
if (registry_build)
{
parsed_build = g_ascii_strtoull (registry_build, NULL, 10);
if (parsed_build != 0)
{
build = (DWORD) parsed_build;
}
}
if (!product)
{
product = g_strdup (build >= 22000 ? "Windows 11" : "Windows");
}
else if (build >= 22000 && g_str_has_prefix (product, "Windows 10"))
{
fixed_product = g_strdup_printf ("Windows 11%s", product + strlen ("Windows 10"));
g_free (product);
product = fixed_product;
}
if (display_version && build != 0)
{
result = g_strdup_printf ("%s %s" SYSINFO_SEP "build %lu.%lu" SYSINFO_SEP "%s",
product, display_version, (unsigned long) build, (unsigned long) ubr, get_native_arch ());
}
else if (build != 0)
{
result = g_strdup_printf ("%s" SYSINFO_SEP "build %lu.%lu" SYSINFO_SEP "%s",
product, (unsigned long) build, (unsigned long) ubr, get_native_arch ());
}
else
{
result = g_strdup_printf ("%s" SYSINFO_SEP "%s", product, get_native_arch ());
}
g_free (product);
g_free (display_version);
g_free (registry_build);
return result;
}
static DWORD
count_physical_cores (void)
{
DWORD length = 0;
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX buffer;
BYTE *cursor;
BYTE *end;
DWORD cores = 0;
GetLogicalProcessorInformationEx (RelationProcessorCore, NULL, &length);
if (GetLastError () != ERROR_INSUFFICIENT_BUFFER || length == 0)
{
return 0;
}
buffer = g_malloc0 (length);
if (!GetLogicalProcessorInformationEx (RelationProcessorCore, buffer, &length))
{
g_free (buffer);
return 0;
}
cursor = (BYTE *) buffer;
end = cursor + length;
while (cursor < end)
{
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX info = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) cursor;
if (info->Size == 0)
{
break;
}
if (info->Relationship == RelationProcessorCore)
{
cores++;
}
cursor += info->Size;
}
g_free (buffer);
return cores;
}
static char *
build_cpu_string (void)
{
static const WCHAR cpu_key[] = L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
char *name;
DWORD cores;
DWORD threads;
SYSTEM_INFO info;
char *result;
name = read_registry_string (HKEY_LOCAL_MACHINE, cpu_key, L"ProcessorNameString");
if (!name)
{
name = g_strdup ("Unknown CPU");
}
cores = count_physical_cores ();
threads = GetActiveProcessorCount (ALL_PROCESSOR_GROUPS);
if (threads == 0)
{
GetNativeSystemInfo (&info);
threads = info.dwNumberOfProcessors;
}
if (cores != 0)
{
result = g_strdup_printf ("%s" SYSINFO_SEP "%luC/%luT",
name, (unsigned long) cores, (unsigned long) threads);
}
else
{
/* Logical processor count cannot establish the physical core count. */
result = g_strdup_printf ("%s" SYSINFO_SEP "%luT", name, (unsigned long) threads);
}
g_free (name);
return result;
}
static char *
join_ptr_array (GPtrArray *items, const char *separator)
{
GString *joined;
guint i;
if (!items || items->len == 0)
{
return NULL;
}
joined = g_string_new (NULL);
for (i = 0; i < items->len; i++)
{
if (i != 0)
{
g_string_append (joined, separator);
}
g_string_append (joined, g_ptr_array_index (items, i));
}
return g_string_free (joined, FALSE);
}
static void
ptr_array_add_unique (GPtrArray *items, const char *value)
{
guint i;
if (!value || value[0] == '\0')
{
return;
}
for (i = 0; i < items->len; i++)
{
if (g_ascii_strcasecmp (g_ptr_array_index (items, i), value) == 0)
{
return;
}
}
g_ptr_array_add (items, g_strdup (value));
}
static char *
build_gpu_string (void)
{
IDXGIFactory1 *factory = NULL;
GPtrArray *adapters;
UINT index;
HRESULT hr;
char *result;
hr = CreateDXGIFactory1 (&IID_IDXGIFactory1, (void **) &factory);
if (FAILED (hr) || !factory)
{
return NULL;
}
adapters = g_ptr_array_new_with_free_func (g_free);
for (index = 0; ; index++)
{
IDXGIAdapter1 *adapter = NULL;
DXGI_ADAPTER_DESC1 desc;
char *name;
char *label;
hr = factory->lpVtbl->EnumAdapters1 (factory, index, &adapter);
if (hr == DXGI_ERROR_NOT_FOUND)
{
break;
}
if (FAILED (hr) || !adapter)
{
/* A failed enumeration is not another adapter. Retrying every
* index can freeze the client indefinitely after a device error. */
break;
}
ZeroMemory (&desc, sizeof (desc));
hr = adapter->lpVtbl->GetDesc1 (adapter, &desc);
adapter->lpVtbl->Release (adapter);
if (FAILED (hr) || (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE))
{
continue;
}
name = wide_to_utf8 (desc.Description);
if (!name)
{
continue;
}
g_strstrip (name);
if ((guint64) desc.DedicatedVideoMemory >= (512ULL * 1024ULL * 1024ULL))
{
char *vram = g_format_size_full ((guint64) desc.DedicatedVideoMemory, G_FORMAT_SIZE_IEC_UNITS);
label = g_strdup_printf ("%s (%s VRAM)", name, vram);
g_free (vram);
}
else
{
label = g_strdup (name);
}
ptr_array_add_unique (adapters, label);
g_free (label);
g_free (name);
}
factory->lpVtbl->Release (factory);
result = join_ptr_array (adapters, ", ");
g_ptr_array_free (adapters, TRUE);
return result;
}
static char *
build_sound_string (void)
{
UINT count;
UINT i;
GPtrArray *devices;
char *result;
count = waveOutGetNumDevs ();
if (count == 0)
{
return NULL;
}
devices = g_ptr_array_new_with_free_func (g_free);
for (i = 0; i < count; i++)
{
WAVEOUTCAPSW caps;
char *name;
ZeroMemory (&caps, sizeof (caps));
if (waveOutGetDevCapsW ((UINT_PTR) i, &caps, sizeof (caps)) != MMSYSERR_NOERROR)
{
continue;
}
name = wide_to_utf8 (caps.szPname);
if (name)
{
g_strstrip (name);
ptr_array_add_unique (devices, name);
g_free (name);
}
}
result = join_ptr_array (devices, ", ");
g_ptr_array_free (devices, TRUE);
return result;
}
static void
ensure_os_snapshot (void)
{
if (g_once_init_enter (&os_initialized))
{
snapshot.os = build_os_string ();
g_once_init_leave (&os_initialized, 1);
}
}
static void
ensure_cpu_snapshot (void)
{
if (g_once_init_enter (&cpu_initialized))
{
snapshot.cpu = build_cpu_string ();
g_once_init_leave (&cpu_initialized, 1);
}
}
static void
ensure_gpu_snapshot (void)
{
if (g_once_init_enter (&gpu_initialized))
{
snapshot.gpu = build_gpu_string ();
g_once_init_leave (&gpu_initialized, 1);
}
}
static void
ensure_sound_snapshot (void)
{
if (g_once_init_enter (&sound_initialized))
{
snapshot.sound = build_sound_string ();
g_once_init_leave (&sound_initialized, 1);
}
}
static char *
format_usage (guint64 total, guint64 available)
{
guint64 used;
char *used_fmt;
char *total_fmt;
char *result;
double percent;
if (total == 0)
{
return NULL;
}
if (available > total)
{
available = total;
}
used = total - available;
percent = ((double) used / (double) total) * 100.0;
used_fmt = g_format_size_full (used, G_FORMAT_SIZE_IEC_UNITS);
total_fmt = g_format_size_full (total, G_FORMAT_SIZE_IEC_UNITS);
result = g_strdup_printf ("%s / %s used (%.0f%%)", used_fmt, total_fmt, percent);
g_free (used_fmt);
g_free (total_fmt);
return result;
}
char *
sysinfo_backend_get_os (void)
{
ensure_os_snapshot ();
return g_strdup (snapshot.os);
}
char *
sysinfo_backend_get_cpu (void)
{
return sysinfo_get_cpu ();
}
char *
sysinfo_backend_get_memory (void)
{
/* Memory information is always loaded dynamically since it includes the current amount of free memory */
return get_memory_info ();
ensure_cpu_snapshot ();
return g_strdup (snapshot.cpu);
}
char *
sysinfo_backend_get_gpu (void)
{
return sysinfo_get_gpu ();
ensure_gpu_snapshot ();
return g_strdup (snapshot.gpu);
}
char *
sysinfo_backend_get_sound (void)
{
ensure_sound_snapshot ();
return g_strdup (snapshot.sound);
}
char *
@@ -96,43 +587,178 @@ sysinfo_backend_get_chipset (void)
}
char *
sysinfo_backend_get_os (void)
sysinfo_backend_get_memory (void)
{
return sysinfo_get_os ();
}
MEMORYSTATUSEX memory;
static char *get_memory_info (void)
{
MEMORYSTATUSEX meminfo = { 0 };
meminfo.dwLength = sizeof (meminfo);
if (!GlobalMemoryStatusEx (&meminfo))
ZeroMemory (&memory, sizeof (memory));
memory.dwLength = sizeof (memory);
if (!GlobalMemoryStatusEx (&memory))
{
return NULL;
}
return sysinfo_format_memory (meminfo.ullTotalPhys, meminfo.ullAvailPhys);
return format_usage ((guint64) memory.ullTotalPhys, (guint64) memory.ullAvailPhys);
}
static const char *sysinfo_detect_toolkit(void)
char *
sysinfo_backend_get_disk (void)
{
#if defined(USE_GTK_FRONTEND)
return "GTK3";
#else
WCHAR windows_dir[MAX_PATH];
WCHAR volume_path[MAX_PATH];
ULARGE_INTEGER available;
ULARGE_INTEGER total;
ULARGE_INTEGER total_free;
char *usage;
char *volume_utf8;
char *result;
if (GetWindowsDirectoryW (windows_dir, G_N_ELEMENTS (windows_dir)) == 0)
{
return NULL;
#endif
}
if (!GetVolumePathNameW (windows_dir, volume_path, G_N_ELEMENTS (volume_path)))
{
return NULL;
}
if (!GetDiskFreeSpaceExW (volume_path, &available, &total, &total_free))
{
return NULL;
}
usage = format_usage ((guint64) total.QuadPart, (guint64) available.QuadPart);
if (!usage)
{
return NULL;
}
volume_utf8 = wide_to_utf8 (volume_path);
if (!volume_utf8)
{
return usage;
}
g_strchomp (volume_utf8);
result = g_strdup_printf ("%s" SYSINFO_SEP "%s", usage, volume_utf8);
g_free (volume_utf8);
g_free (usage);
return result;
}
char *
sysinfo_backend_get_uptime (void)
{
return sysinfo_format_uptime ((gint64) (GetTickCount64 () / 1000ULL));
}
static gboolean
adapter_looks_virtual (const char *name)
{
char *lower;
gboolean result;
if (!name)
{
return FALSE;
}
lower = g_ascii_strdown (name, -1);
result = strstr (lower, "virtual") != NULL ||
strstr (lower, "hyper-v") != NULL ||
strstr (lower, "vmware") != NULL ||
strstr (lower, "virtualbox") != NULL ||
strstr (lower, "tap-windows") != NULL ||
strstr (lower, "wireguard") != NULL ||
strstr (lower, "vpn") != NULL;
g_free (lower);
return result;
}
char *
sysinfo_backend_get_network (void)
{
ULONG size = 16 * 1024;
ULONG status;
IP_ADAPTER_ADDRESSES *addresses;
IP_ADAPTER_ADDRESSES *adapter;
GPtrArray *physical;
GPtrArray *fallback;
GPtrArray *selected;
char *result;
addresses = g_malloc0 (size);
status = GetAdaptersAddresses (AF_UNSPEC,
GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER,
NULL, addresses, &size);
if (status == ERROR_BUFFER_OVERFLOW)
{
g_free (addresses);
addresses = g_malloc0 (size);
status = GetAdaptersAddresses (AF_UNSPEC,
GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER,
NULL, addresses, &size);
}
if (status != NO_ERROR)
{
g_free (addresses);
return NULL;
}
physical = g_ptr_array_new_with_free_func (g_free);
fallback = g_ptr_array_new_with_free_func (g_free);
for (adapter = addresses; adapter != NULL; adapter = adapter->Next)
{
char *description;
char *friendly;
const char *name;
const char *type;
char *label;
if (adapter->OperStatus != IfOperStatusUp || adapter->PhysicalAddressLength == 0)
{
continue;
}
if (adapter->IfType != IF_TYPE_ETHERNET_CSMACD && adapter->IfType != IF_TYPE_IEEE80211)
{
continue;
}
description = wide_to_utf8 (adapter->Description);
friendly = wide_to_utf8 (adapter->FriendlyName);
name = description && description[0] ? description : friendly;
if (!name)
{
g_free (description);
g_free (friendly);
continue;
}
type = adapter->IfType == IF_TYPE_IEEE80211 ? "Wi-Fi" : "Ethernet";
label = g_strdup_printf ("%s: %s", type, name);
ptr_array_add_unique (fallback, label);
if (!adapter_looks_virtual (name))
{
ptr_array_add_unique (physical, label);
}
g_free (label);
g_free (description);
g_free (friendly);
}
g_free (addresses);
selected = physical->len != 0 ? physical : fallback;
result = join_ptr_array (selected, ", ");
g_ptr_array_free (physical, TRUE);
g_ptr_array_free (fallback, TRUE);
return result;
}
char *
sysinfo_backend_get_ui (void)
{
const char *toolkit = sysinfo_detect_toolkit();
/* On Windows we don't have X11/Wayland. Keep it simple. */
if (toolkit)
{
return g_strdup_printf ("Windows / %s", toolkit);
}
#if defined(USE_GTK_FRONTEND)
return g_strdup ("Windows / GTK3");
#else
return g_strdup ("Windows");
#endif
}
+5 -75
View File
@@ -41,8 +41,6 @@
#include "network.h"
#include "notify.h"
#include "outbound.h"
#include <gmodule.h>
#include "plugin.h"
#include "inbound.h"
#include "server.h"
#include "servlist.h"
@@ -1964,58 +1962,10 @@ inbound_cap_ack (server *serv, char *nick, char *extensions,
inbound_toggle_caps (serv, extensions, TRUE);
}
/* Split requests only at token boundaries, including plugin-added names. */
static void
inbound_cap_request_append (server *serv, char *buffer, gsize size,
const char *name, time_t timestamp)
{
if (strlen (buffer) + strlen (name) + 1 >= size)
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPREQ, serv->server_session,
buffer + 9, NULL, NULL, NULL, 0, timestamp);
tcp_sendf (serv, "%s\r\n", g_strchomp (buffer));
g_strlcpy (buffer, "CAP REQ :", size);
}
g_strlcat (buffer, name, size);
g_strlcat (buffer, " ", size);
}
static void
inbound_plugin_cap_new (server *serv, const char *extensions, time_t timestamp)
{
char buffer[500] = "CAP REQ :";
char **tokens = g_strsplit (extensions, " ", 0);
GHashTable *seen = g_hash_table_new (g_str_hash, g_str_equal);
int i;
gboolean requested = FALSE;
for (i = 0; tokens[i]; i++)
{
char *value = strchr (tokens[i], '=');
if (value)
*value = '\0';
if (plugin_requests_capability (serv, tokens[i]) &&
!g_hash_table_contains (seen, tokens[i]))
{
g_hash_table_add (seen, tokens[i]);
inbound_cap_request_append (serv, buffer, sizeof (buffer), tokens[i], timestamp);
requested = TRUE;
}
}
if (requested)
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPREQ, serv->server_session,
buffer + 9, NULL, NULL, NULL, 0, timestamp);
tcp_sendf (serv, "%s\r\n", g_strchomp (buffer));
}
g_hash_table_destroy (seen);
g_strfreev (tokens);
}
void
inbound_cap_new (server *serv, char *nick, char *extensions,
const message_tags_data *tags_data)
{
gboolean sts_upgrade_triggered = FALSE;
if (extensions)
{
char **tokens = g_strsplit (extensions, " ", 0);
@@ -2027,7 +1977,7 @@ inbound_cap_new (server *serv, char *nick, char *extensions,
if (!g_strcmp0 (parts[0], "sts") && parts[1] && parts[1][0])
{
sts_upgrade_triggered |= sts_handle_capability (serv, parts[1]);
sts_handle_capability (serv, parts[1]);
}
g_strfreev (parts);
@@ -2036,9 +1986,6 @@ inbound_cap_new (server *serv, char *nick, char *extensions,
g_strfreev (tokens);
}
if (extensions && !sts_upgrade_triggered)
inbound_plugin_cap_new (serv, extensions, tags_data->timestamp);
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPACK, serv->server_session, nick, extensions,
NULL, NULL, 0, tags_data->timestamp);
@@ -2169,7 +2116,6 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
gboolean want_cap = FALSE; /* format the CAP REQ string based on previous capabilities being requested or not */
gboolean sts_upgrade_triggered = FALSE;
char **extensions;
GString *requests = g_string_new ("");
int i;
if (g_str_has_prefix (extensions_str, "* "))
@@ -2235,7 +2181,7 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
}
want_cap = TRUE;
serv->waiting_on_sasl = TRUE;
g_string_append (requests, "sasl ");
g_strlcat (buffer, "sasl ", sizeof(buffer));
continue;
}
@@ -2243,44 +2189,28 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
{
if (!g_strcmp0 (extension, supported_caps[x]))
{
g_string_append (requests, extension);
g_string_append_c (requests, ' ');
want_cap = TRUE;
break;
}
}
if (x == G_N_ELEMENTS (supported_caps) && plugin_requests_capability (serv, extension))
{
g_string_append (requests, extension);
g_string_append_c (requests, ' ');
g_strlcat (buffer, extension, sizeof(buffer));
g_strlcat (buffer, " ", sizeof(buffer));
want_cap = TRUE;
}
}
}
g_strfreev (extensions);
if (sts_upgrade_triggered)
{
g_string_free (requests, TRUE);
return;
}
if (want_cap)
{
char **tokens = g_strsplit (requests->str, " ", 0);
/* Defer every batch until STS has decided whether to reconnect. */
for (i = 0; tokens[i]; i++)
if (tokens[i][0])
inbound_cap_request_append (serv, buffer, sizeof (buffer), tokens[i], tags_data->timestamp);
g_strfreev (tokens);
/* buffer + 9 = emit buffer without "CAP REQ :" */
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPREQ, serv->server_session,
buffer + 9, NULL, NULL, NULL, 0,
tags_data->timestamp);
tcp_sendf (serv, "%s\r\n", g_strchomp (buffer));
}
g_string_free (requests, TRUE);
if (!serv->waiting_on_sasl && !serv->waiting_on_cap)
{
/* if we use SASL, CAP END is dealt via raw numerics */
+2 -149
View File
@@ -99,10 +99,6 @@ struct _zoitechat_hook
int tag; /* for timers & FDs only */
int type; /* HOOK_* */
int pri; /* fd */ /* priority / fd for HOOK_FD only */
int cap_connection_id;
int connection_filter;
char *channel_filter;
gboolean context_filtered;
};
struct _zoitechat_list
@@ -147,8 +143,6 @@ enum
HOOK_PRINT_ATTRS = 1 << 4, /* same as above, with attributes */
HOOK_TIMER = 1 << 5, /* timeouts */
HOOK_FD = 1 << 6, /* sockets & fds */
HOOK_PRINT_AFTER = 1 << 8, /* opt-in post-display observers */
HOOK_CAPABILITY = 1 << 9, /* script-owned CAP requests */
HOOK_DELETED = 1 << 7 /* marked for deletion */
};
@@ -182,7 +176,6 @@ enum
GSList *plugin_list = NULL; /* export for plugingui.c */
static GSList *hook_list = NULL;
static unsigned int plugin_dispatch_depth;
extern const struct prefs vars[]; /* cfgfiles.c */
@@ -250,7 +243,7 @@ plugin_list_add (zoitechat_context *ctx, char *filename, const char *name,
{
zoitechat_plugin *pl;
pl = g_new0 (zoitechat_plugin, 1);
pl = g_new (zoitechat_plugin, 1);
pl->handle = handle;
pl->filename = filename;
pl->context = ctx;
@@ -357,10 +350,6 @@ plugin_add (session *sess, char *filename, void *handle, void *init_func,
pl->zoitechat_emit_print_attrs = zoitechat_emit_print_attrs;
pl->zoitechat_event_attrs_create = zoitechat_event_attrs_create;
pl->zoitechat_event_attrs_free = zoitechat_event_attrs_free;
pl->zoitechat_register_capability = zoitechat_register_capability;
pl->zoitechat_hook_server_filtered = zoitechat_hook_server_filtered;
pl->zoitechat_hook_print_filtered = zoitechat_hook_print_filtered;
pl->zoitechat_hook_print_after = zoitechat_hook_print_after;
/* run zoitechat_plugin_init, if it returns 0, close the plugin */
if (((zoitechat_init_func *)init_func) (pl, &pl->name, &pl->desc, &pl->version, arg) == 0)
@@ -621,7 +610,6 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
zoitechat_hook *hook;
int ret, eat = 0;
plugin_dispatch_depth++;
list = hook_list;
while (1)
{
@@ -631,22 +619,6 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
hook = list->data;
next = list->next;
if (hook->context_filtered &&
(!is_session (sess) ||
(hook->connection_filter != -1 && hook->connection_filter != sess->server->id) ||
(hook->channel_filter && sess->server->p_cmp (hook->channel_filter, sess->channel))))
{
list = next;
continue; /* filter in C before entering an interpreter */
}
if (type == HOOK_PRINT_AFTER && hook->tag)
{
list = next;
continue; /* no recursive invocation of this same observer */
}
if (type == HOOK_PRINT_AFTER)
hook->tag = 1;
hook->pl->context = sess;
/* run the plugin's callback function */
@@ -655,7 +627,6 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
case HOOK_COMMAND:
ret = ((zoitechat_cmd_cb *)hook->callback) (word, word_eol, hook->userdata);
break;
case HOOK_PRINT_AFTER:
case HOOK_PRINT_ATTRS:
ret = ((zoitechat_print_attrs_cb *)hook->callback) (word, attrs, hook->userdata);
break;
@@ -670,15 +641,6 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
break;
}
if (type == HOOK_PRINT_AFTER)
{
hook->tag = 0;
if (!is_session (sess))
goto xit;
list = next;
continue; /* display already happened; observers cannot eat it */
}
if ((ret & ZOITECHAT_EAT_ZOITECHAT) && (ret & ZOITECHAT_EAT_PLUGIN))
{
eat = 1;
@@ -693,10 +655,6 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
}
xit:
/* Nested callbacks can unhook entries still used by an outer dispatch.
* Keep both hook objects and list links alive until the outermost return. */
if (--plugin_dispatch_depth != 0)
return eat;
/* really remove deleted hooks now */
list = hook_list;
while (list)
@@ -761,15 +719,6 @@ plugin_emit_print (session *sess, char *word[], time_t server_time)
HOOK_PRINT | HOOK_PRINT_ATTRS);
}
void
plugin_emit_print_after (session *sess, char *word[], time_t server_time)
{
zoitechat_event_attrs attrs;
attrs.server_time_utc = server_time;
if (is_session (sess))
plugin_hook_run (sess, word[0], word, NULL, &attrs, HOOK_PRINT_AFTER);
}
int
plugin_emit_dummy_print (session *sess, char *name)
{
@@ -857,7 +806,7 @@ plugin_insert_hook (zoitechat_hook *new_hook)
break;
case HOOK_SERVER:
case HOOK_SERVER_ATTRS:
new_hook_type = HOOK_SERVER | HOOK_SERVER_ATTRS;
new_hook_type = HOOK_SERVER | HOOK_PRINT_ATTRS;
break;
default:
new_hook_type = new_hook->type;
@@ -1069,87 +1018,10 @@ zoitechat_unhook (zoitechat_plugin *ph, zoitechat_hook *hook)
g_free (hook->name); /* NULL for timers & fds */
g_free (hook->help_text); /* NULL for non-commands */
g_free (hook->channel_filter);
hook->channel_filter = NULL;
return hook->userdata;
}
zoitechat_hook *
zoitechat_hook_print_after (zoitechat_plugin *ph, const char *name, int pri,
int flags, zoitechat_print_attrs_cb *callback, void *userdata)
{
if (flags != 0 || !name || !callback)
return NULL;
return plugin_add_hook (ph, HOOK_PRINT_AFTER, pri, name, NULL, callback, 0, userdata);
}
static zoitechat_hook *
plugin_add_filtered_hook (zoitechat_plugin *ph, const char *name, int pri,
int flags, int connection_id, const char *channel,
int type, void *callback, void *userdata)
{
zoitechat_hook *hook;
if (flags != 0 || connection_id < -1 || !name || !callback)
return NULL;
hook = plugin_add_hook (ph, type, pri, name, NULL, callback, 0, userdata);
hook->context_filtered = TRUE;
hook->connection_filter = connection_id;
hook->channel_filter = g_strdup (channel);
return hook;
}
zoitechat_hook *
zoitechat_hook_server_filtered (zoitechat_plugin *ph, const char *name, int pri,
int flags, int connection_id, const char *channel,
zoitechat_serv_attrs_cb *callback, void *userdata)
{
return plugin_add_filtered_hook (ph, name, pri, flags, connection_id, channel,
HOOK_SERVER_ATTRS, callback, userdata);
}
zoitechat_hook *
zoitechat_hook_print_filtered (zoitechat_plugin *ph, const char *name, int pri,
int flags, int connection_id, const char *channel,
zoitechat_print_attrs_cb *callback, void *userdata)
{
return plugin_add_filtered_hook (ph, name, pri, flags, connection_id, channel,
HOOK_PRINT_ATTRS, callback, userdata);
}
zoitechat_hook *
zoitechat_register_capability (zoitechat_plugin *ph, const char *name, int flags,
int connection_id, void *userdata)
{
const unsigned char *p;
zoitechat_hook *hook;
if (flags != 0 || connection_id < -1 || !name || !name[0] || strlen (name) > 200 ||
strcmp (name, "sasl") == 0 || strcmp (name, "sts") == 0)
return NULL; /* authentication and transport policy belong to core */
for (p = (const unsigned char *)name; *p; p++)
if (!g_ascii_islower (*p) && !g_ascii_isdigit (*p) &&
*p != '-' && *p != '.' && *p != '/' && *p != '_')
return NULL;
hook = plugin_add_hook (ph, HOOK_CAPABILITY, 0, name, NULL, NULL, 0, userdata);
hook->cap_connection_id = connection_id;
return hook;
}
gboolean
plugin_requests_capability (server *serv, const char *name)
{
GSList *entry;
for (entry = hook_list; entry; entry = entry->next)
{
zoitechat_hook *hook = entry->data;
if (hook && hook->type == HOOK_CAPABILITY &&
(hook->cap_connection_id == -1 || hook->cap_connection_id == serv->id) &&
strcmp (hook->name, name) == 0)
return TRUE;
}
return FALSE;
}
zoitechat_hook *
zoitechat_hook_command (zoitechat_plugin *ph, const char *name, int pri,
zoitechat_cmd_cb *callb, const char *help_text, void *userdata)
@@ -1309,25 +1181,6 @@ zoitechat_get_info (zoitechat_plugin *ph, const char *id)
return text_find_format_string (e);
}
/* Report the comparison actually used by this connection, not an
* advertised mapping that the current core does not implement. */
if (strcmp (id, "casemapping") == 0)
{
if (!is_session (ph->context))
return NULL;
return ph->context->server->p_cmp == g_ascii_strcasecmp
? "ascii" : "rfc1459";
}
if (strcmp (id, "api_hook_print_after") == 0)
return "1";
if (strcmp (id, "api_context_filters") == 0)
return "1";
if (strcmp (id, "api_plugin_caps") == 0)
return "1";
hash = str_hash (id);
/* do the session independant ones first */
switch (hash)
-11
View File
@@ -150,15 +150,6 @@ struct _zoitechat_plugin
void (*zoitechat_event_attrs_free) (zoitechat_plugin *ph,
zoitechat_event_attrs *attrs);
/* Additive API slots: append only; old binary offsets are unchanged. */
zoitechat_hook *(*zoitechat_hook_print_after) (zoitechat_plugin *, const char *, int,
int, int (*)(char *[], zoitechat_event_attrs *, void *), void *);
zoitechat_hook *(*zoitechat_hook_server_filtered) (zoitechat_plugin *, const char *, int,
int, int, const char *, int (*)(char *[], char *[], zoitechat_event_attrs *, void *), void *);
zoitechat_hook *(*zoitechat_hook_print_filtered) (zoitechat_plugin *, const char *, int,
int, int, const char *, int (*)(char *[], zoitechat_event_attrs *, void *), void *);
zoitechat_hook *(*zoitechat_register_capability) (zoitechat_plugin *, const char *, int, int, void *);
/* PRIVATE FIELDS! */
void *handle; /* from dlopen */
char *filename; /* loaded from */
@@ -183,8 +174,6 @@ int plugin_emit_command (session *sess, char *name, char *word[], char *word_eol
int plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[],
time_t server_time);
int plugin_emit_print (session *sess, char *word[], time_t server_time);
gboolean plugin_requests_capability (server *serv, const char *name);
void plugin_emit_print_after (session *sess, char *word[], time_t server_time);
int plugin_emit_dummy_print (session *sess, char *name);
int plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, gunichar key);
GList* plugin_command_list(GList *tmp_list);
-1
View File
@@ -2306,7 +2306,6 @@ text_emit (int index, session *sess, char *a, char *b, char *c, char *d,
if (!prefs.hex_away_omit_alerts || !sess->server->is_away)
sound_play_event (index);
display_event (sess, index, word, stripcolor_args, timestamp);
plugin_emit_print_after (sess, word, timestamp);
}
char *
-33
View File
@@ -193,39 +193,10 @@ struct _zoitechat_plugin
zoitechat_event_attrs *(*zoitechat_event_attrs_create) (zoitechat_plugin *ph);
void (*zoitechat_event_attrs_free) (zoitechat_plugin *ph,
zoitechat_event_attrs *attrs);
/* Additive API slots: append only; old binary offsets are unchanged. */
zoitechat_hook *(*zoitechat_hook_print_after) (zoitechat_plugin *, const char *, int,
int, int (*)(char *[], zoitechat_event_attrs *, void *), void *);
zoitechat_hook *(*zoitechat_hook_server_filtered) (zoitechat_plugin *, const char *, int,
int, int, const char *, int (*)(char *[], char *[], zoitechat_event_attrs *, void *), void *);
zoitechat_hook *(*zoitechat_hook_print_filtered) (zoitechat_plugin *, const char *, int,
int, int, const char *, int (*)(char *[], zoitechat_event_attrs *, void *), void *);
zoitechat_hook *(*zoitechat_register_capability) (zoitechat_plugin *, const char *, int, int, void *);
};
#endif
/* api_hook_print_after=1: observer returns are ignored; flags must be zero. */
zoitechat_hook *zoitechat_hook_print_after (zoitechat_plugin *ph,
const char *name, int pri, int flags,
int (*callback) (char *word[], zoitechat_event_attrs *attrs, void *userdata),
void *userdata);
/* api_context_filters=1: connection_id -1 is any; NULL channel is any. */
zoitechat_hook *zoitechat_hook_server_filtered (zoitechat_plugin *ph,
const char *name, int pri, int flags, int connection_id, const char *channel,
int (*callback) (char *word[], char *word_eol[], zoitechat_event_attrs *attrs, void *userdata),
void *userdata);
zoitechat_hook *zoitechat_hook_print_filtered (zoitechat_plugin *ph,
const char *name, int pri, int flags, int connection_id, const char *channel,
int (*callback) (char *word[], zoitechat_event_attrs *attrs, void *userdata),
void *userdata);
/* api_plugin_caps=1: register before CAP LS; flags=0, connection_id=-1 for any. */
zoitechat_hook *zoitechat_register_capability (zoitechat_plugin *ph,
const char *name, int flags, int connection_id, void *userdata);
zoitechat_hook *
zoitechat_hook_command (zoitechat_plugin *ph,
const char *name,
@@ -445,10 +416,6 @@ zoitechat_pluginpref_list (zoitechat_plugin *ph,
#define zoitechat_hook_server ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_server)
#define zoitechat_hook_server_attrs ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_server_attrs)
#define zoitechat_hook_print ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print)
#define zoitechat_register_capability ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_register_capability)
#define zoitechat_hook_server_filtered ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_server_filtered)
#define zoitechat_hook_print_filtered ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print_filtered)
#define zoitechat_hook_print_after ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print_after)
#define zoitechat_hook_print_attrs ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print_attrs)
#define zoitechat_hook_timer ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_timer)
#define zoitechat_hook_fd ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_fd)
+3 -3
View File
@@ -138,7 +138,7 @@ ascii_open (void)
{
table_pos++;
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
gtk_container_add (GTK_CONTAINER (vbox), hbox);
gtk_widget_show (hbox);
i++;
continue;
@@ -155,14 +155,14 @@ ascii_open (void)
G_CALLBACK (ascii_click), NULL);
g_signal_connect (G_OBJECT (but), "enter-notify-event",
G_CALLBACK (ascii_enter), label);
gtk_box_pack_start (GTK_BOX (hbox), but, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (hbox), but, 0, 0, 0);
gtk_widget_show (but);
table_pos += len;
}
frame = gtk_frame_new ("");
gtk_box_pack_start (GTK_BOX (hbox), frame, TRUE, TRUE, 0);
gtk_container_add (GTK_CONTAINER (hbox), frame);
gtk_container_add (GTK_CONTAINER (frame), label);
gtk_widget_show (label);
gtk_widget_show (frame);
+3 -13
View File
@@ -1221,7 +1221,7 @@ key_dialog_load (GtkListStore *store)
void
key_dialog_show ()
{
GtkWidget *vbox, *box, *pane, *scroll;
GtkWidget *vbox, *box;
GtkWidget *view, *xtext, *delete_button;
GtkListStore *store;
XTextColor xtext_palette[XTEXT_COLS];
@@ -1237,20 +1237,10 @@ key_dialog_show ()
key_dialog = mg_create_generic_tab ("editkeys", buf, TRUE, FALSE, key_dialog_close,
NULL, 600, 360, &vbox, 0);
pane = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
gtk_box_pack_start (GTK_BOX (vbox), pane, TRUE, TRUE, 0);
box = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
gtk_paned_pack1 (GTK_PANED (pane), box, TRUE, FALSE);
view = key_dialog_treeview_new (box);
scroll = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (scroll), 80);
gtk_paned_pack2 (GTK_PANED (pane), scroll, TRUE, FALSE);
gtk_paned_set_position (GTK_PANED (pane), 220);
view = key_dialog_treeview_new (vbox);
theme_get_xtext_colors (xtext_palette, XTEXT_COLS);
xtext = gtk_xtext_new (xtext_palette, 0);
gtk_container_add (GTK_CONTAINER (scroll), xtext);
gtk_box_pack_start (GTK_BOX (vbox), xtext, FALSE, TRUE, 2);
gtk_xtext_set_font (GTK_XTEXT (xtext), prefs.hex_text_font);
g_object_set_data (G_OBJECT (key_dialog), "view", view);
+11 -17
View File
@@ -641,8 +641,9 @@ gtkutil_get_str (char *msg, char *def, void *callback, void *userdata, gboolean
if (parent_window && GTK_IS_WINDOW (parent_window))
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
gtkutil_place_dialog (dialog);
gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE);
hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, TRUE, 0);
g_object_set_data (G_OBJECT (dialog), "cb", callback);
g_object_set_data (G_OBJECT (dialog), "ud", userdata);
@@ -652,8 +653,7 @@ gtkutil_get_str (char *msg, char *def, void *callback, void *userdata, gboolean
G_CALLBACK (gtkutil_str_enter), dialog);
gtk_entry_set_text (GTK_ENTRY (entry), def ? def : "");
gtk_entry_set_visibility (GTK_ENTRY (entry), visible);
gtk_widget_set_valign (entry, GTK_ALIGN_CENTER);
gtk_box_pack_end (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
gtk_box_pack_end (GTK_BOX (hbox), entry, 0, 0, 0);
label = gtk_label_new (msg);
gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0);
@@ -661,8 +661,7 @@ gtkutil_get_str (char *msg, char *def, void *callback, void *userdata, gboolean
g_signal_connect (G_OBJECT (dialog), "response",
G_CALLBACK (gtkutil_get_str_response), entry);
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
hbox, TRUE, TRUE, 0);
gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox);
gtk_widget_show_all (dialog);
}
@@ -743,8 +742,9 @@ fe_get_int (char *msg, int def, void *callback, void *userdata)
if (parent_window && GTK_IS_WINDOW (parent_window))
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
gtkutil_place_dialog (dialog);
gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE);
hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, TRUE, 0);
g_object_set_data (G_OBJECT (dialog), "cb", callback);
g_object_set_data (G_OBJECT (dialog), "ud", userdata);
@@ -755,8 +755,7 @@ fe_get_int (char *msg, int def, void *callback, void *userdata)
gtk_adjustment_set_upper (adj, 1024);
gtk_adjustment_set_step_increment (adj, 1);
gtk_spin_button_set_value ((GtkSpinButton*)spin, def);
gtk_widget_set_valign (spin, GTK_ALIGN_CENTER);
gtk_box_pack_end (GTK_BOX (hbox), spin, TRUE, TRUE, 0);
gtk_box_pack_end (GTK_BOX (hbox), spin, 0, 0, 0);
label = gtk_label_new (msg);
gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0);
@@ -764,8 +763,7 @@ fe_get_int (char *msg, int def, void *callback, void *userdata)
g_signal_connect (G_OBJECT (dialog), "response",
G_CALLBACK (gtkutil_get_number_response), spin);
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
hbox, TRUE, TRUE, 0);
gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox);
gtk_widget_show_all (dialog);
}
@@ -785,18 +783,17 @@ fe_get_bool (char *title, char *prompt, void *callback, void *userdata)
if (parent_window && GTK_IS_WINDOW (parent_window))
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
gtkutil_place_dialog (dialog);
gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE);
g_object_set_data (G_OBJECT (dialog), "cb", callback);
g_object_set_data (G_OBJECT (dialog), "ud", userdata);
prompt_label = gtk_label_new (prompt);
gtk_label_set_line_wrap (GTK_LABEL (prompt_label), TRUE);
g_signal_connect (G_OBJECT (dialog), "response",
G_CALLBACK (gtkutil_get_bool_response), NULL);
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
prompt_label, TRUE, TRUE, 0);
gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), prompt_label);
gtk_widget_show_all (dialog);
}
@@ -888,10 +885,7 @@ gtkutil_window_new (char *title, char *role, int width, int height, int flags)
gtk_window_set_wmclass (GTK_WINDOW (win), "ZoiteChat", "zoitechat");
#endif
gtk_window_set_title (GTK_WINDOW (win), title);
/* Zero means content-sized to callers; GTK requires -1 for that. */
gtk_window_set_default_size (GTK_WINDOW (win),
width > 0 ? width : -1,
height > 0 ? height : -1);
gtk_window_set_default_size (GTK_WINDOW (win), width, height);
gtk_window_set_role (GTK_WINDOW (win), role);
if (flags & 1)
gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_MOUSE);
+11 -31
View File
@@ -1848,33 +1848,10 @@ image_viewer_button_press (GtkWidget *widget, GdkEventButton *event,
return TRUE;
}
static gboolean
image_viewer_draw (GtkWidget *widget, cairo_t *cr, gpointer data)
{
GdkPixbuf *pixbuf = data;
int width = gdk_pixbuf_get_width (pixbuf);
int height = gdk_pixbuf_get_height (pixbuf);
int available_width = gtk_widget_get_allocated_width (widget);
int available_height = gtk_widget_get_allocated_height (widget);
double scale = MIN ((double) available_width / width,
(double) available_height / height);
/* Draw from the original so repeated resizing never degrades the image. */
cairo_save (cr);
cairo_translate (cr, (available_width - width * scale) / 2,
(available_height - height * scale) / 2);
cairo_scale (cr, scale, scale);
gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
cairo_pattern_set_filter (cairo_get_source (cr), CAIRO_FILTER_BILINEAR);
cairo_paint (cr);
cairo_restore (cr);
return FALSE;
}
void
inline_image_show_viewer (GtkXText *xtext, textentry *ent)
{
GdkPixbuf *pixbuf;
GdkPixbuf *pixbuf, *scaled = NULL;
GtkWidget *window, *eventbox, *image, *toplevel;
const char *url;
int width, height, max_width = 1280, max_height = 960;
@@ -1908,15 +1885,18 @@ inline_image_show_viewer (GtkXText *xtext, textentry *ent)
double scale = MIN ((double) max_width / width,
(double) max_height / height);
width = MAX (1, (int) (width * scale));
height = MAX (1, (int) (height * scale));
scaled = gdk_pixbuf_scale_simple (pixbuf,
MAX (1, (int) (width * scale)), MAX (1, (int) (height * scale)),
GDK_INTERP_BILINEAR);
if (scaled)
pixbuf = scaled;
}
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
url = gtk_xtext_entry_get_image_url (xtext, ent);
gtk_window_set_title (GTK_WINDOW (window), url ? url : _("Image"));
gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DIALOG);
gtk_window_set_default_size (GTK_WINDOW (window), width, height);
gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (xtext));
if (GTK_IS_WINDOW (toplevel))
@@ -1928,10 +1908,7 @@ inline_image_show_viewer (GtkXText *xtext, textentry *ent)
}
eventbox = gtk_event_box_new ();
image = gtk_drawing_area_new ();
g_object_set_data_full (G_OBJECT (image), "zoitechat-viewer-image",
g_object_ref (pixbuf), g_object_unref);
g_signal_connect (G_OBJECT (image), "draw", G_CALLBACK (image_viewer_draw), pixbuf);
image = gtk_image_new_from_pixbuf (pixbuf);
gtk_container_add (GTK_CONTAINER (eventbox), image);
gtk_container_add (GTK_CONTAINER (window), eventbox);
@@ -1941,4 +1918,7 @@ inline_image_show_viewer (GtkXText *xtext, textentry *ent)
G_CALLBACK (image_viewer_button_press), window);
gtk_widget_show_all (window);
if (scaled)
g_object_unref (scaled);
}
+1
View File
@@ -137,6 +137,7 @@ joind_show_dialog (server *serv)
gtk_window_set_position (GTK_WINDOW (dialog1), GTK_WIN_POS_CENTER_ON_PARENT);
gtk_window_set_transient_for (GTK_WINDOW(dialog1), GTK_WINDOW(serv->front_session->gui->window));
gtk_window_set_modal (GTK_WINDOW (dialog1), TRUE);
gtk_window_set_resizable (GTK_WINDOW (dialog1), FALSE);
dialog_vbox1 = gtk_dialog_get_content_area (GTK_DIALOG (dialog1));
gtk_widget_show (dialog_vbox1);
+1
View File
@@ -2330,6 +2330,7 @@ mg_open_quit_dialog (gboolean minimize_button)
gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
gtk_window_set_title (GTK_WINDOW (dialog), _("Quit ZoiteChat?"));
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
dialog_vbox1 = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
gtk_widget_show (dialog_vbox1);
+4 -4
View File
@@ -1595,16 +1595,16 @@ menu_join (GtkWidget * wid, gpointer none)
NULL);
theme_manager_attach_window (dialog);
content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
gtk_box_set_homogeneous (GTK_BOX (content_area), TRUE);
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, TRUE, 0);
entry = gtk_entry_new ();
gtk_editable_set_editable (GTK_EDITABLE (entry), FALSE); /* avoid auto-selection */
gtk_entry_set_text (GTK_ENTRY (entry), "#");
g_signal_connect (G_OBJECT (entry), "activate",
G_CALLBACK (menu_join_entry_cb), dialog);
gtk_widget_set_valign (entry, GTK_ALIGN_CENTER);
gtk_box_pack_end (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
gtk_box_pack_end (GTK_BOX (hbox), entry, 0, 0, 0);
label = gtk_label_new (_("Enter Channel to Join:"));
gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0);
@@ -1612,7 +1612,7 @@ menu_join (GtkWidget * wid, gpointer none)
g_signal_connect (G_OBJECT (dialog), "response",
G_CALLBACK (menu_join_cb), entry);
gtk_box_pack_start (GTK_BOX (content_area), hbox, TRUE, TRUE, 0);
gtk_container_add (GTK_CONTAINER (content_area), hbox);
gtk_widget_show_all (dialog);
+6 -11
View File
@@ -391,39 +391,34 @@ fe_notify_ask (char *nick, char *networks)
gtk_container_set_border_width (GTK_CONTAINER (table), 12);
gtk_grid_set_row_spacing (GTK_GRID (table), 3);
gtk_grid_set_column_spacing (GTK_GRID (table), 8);
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
table, TRUE, TRUE, 0);
gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), table);
label = gtk_label_new (msg);
gtk_label_set_xalign (GTK_LABEL (label), 0.0f);
gtkutil_grid_attach (table, label, 0, 1, 0, 1, GTKUTIL_ATTACH_FILL, 0, 0, 0);
gtkutil_grid_attach_defaults (table, label, 0, 1, 0, 1);
entry = gtk_entry_new ();
gtk_entry_set_text (GTK_ENTRY (entry), nick);
g_signal_connect (G_OBJECT (entry), "activate",
G_CALLBACK (notifygui_add_enter), dialog);
gtkutil_grid_attach (table, entry, 1, 2, 0, 1,
GTKUTIL_ATTACH_EXPAND | GTKUTIL_ATTACH_FILL, 0, 0, 0);
gtkutil_grid_attach_defaults (table, entry, 1, 2, 0, 1);
g_signal_connect (G_OBJECT (dialog), "response",
G_CALLBACK (notifygui_add_cb), entry);
label = gtk_label_new (_("Notify on these networks:"));
gtk_label_set_xalign (GTK_LABEL (label), 0.0f);
gtkutil_grid_attach (table, label, 0, 1, 2, 3, GTKUTIL_ATTACH_FILL, 0, 0, 0);
gtkutil_grid_attach_defaults (table, label, 0, 1, 2, 3);
wid = gtk_entry_new ();
g_object_set_data (G_OBJECT (entry), "net", wid);
g_signal_connect (G_OBJECT (wid), "activate",
G_CALLBACK (notifygui_add_enter), dialog);
gtk_entry_set_text (GTK_ENTRY (wid), networks ? networks : "ALL");
gtkutil_grid_attach (table, wid, 1, 2, 2, 3,
GTKUTIL_ATTACH_EXPAND | GTKUTIL_ATTACH_FILL, 0, 0, 0);
gtkutil_grid_attach_defaults (table, wid, 1, 2, 2, 3);
label = gtk_label_new (NULL);
g_snprintf (buf, sizeof (buf), "<i><span size=\"smaller\">%s</span></i>", _("Comma separated list of networks is accepted."));
gtk_label_set_markup (GTK_LABEL (label), buf);
gtkutil_grid_attach (table, label, 1, 2, 3, 4, GTKUTIL_ATTACH_FILL, 0, 0, 0);
gtkutil_grid_attach_defaults (table, label, 1, 2, 3, 4);
gtk_widget_show_all (dialog);
}
+4 -20
View File
@@ -2423,8 +2423,6 @@ servlist_open_edit (GtkWidget *parent, ircnet *net)
GtkWidget *comboboxentry_charset;
GtkWidget *combobox_logintypes;
GtkWidget *hbox1;
GtkWidget *content_scroll;
GtkWidget *content_box;
GtkWidget *scrolledwindow2;
GtkWidget *scrolledwindow4;
GtkWidget *scrolledwindow5;
@@ -2451,9 +2449,7 @@ servlist_open_edit (GtkWidget *parent, ircnet *net)
gtk_container_set_border_width (GTK_CONTAINER (editwindow), 4);
g_snprintf (buf, sizeof (buf), _("Edit %s - %s"), net->name, _(DISPLAY_NAME));
gtk_window_set_title (GTK_WINDOW (editwindow), buf);
gtk_window_set_default_size (GTK_WINDOW (editwindow),
netedit_win_width > 0 ? netedit_win_width : -1,
netedit_win_height > 0 ? netedit_win_height : -1);
gtk_window_set_default_size (GTK_WINDOW (editwindow), netedit_win_width, netedit_win_height);
gtk_window_set_transient_for (GTK_WINDOW (editwindow), GTK_WINDOW (parent));
gtk_window_set_modal (GTK_WINDOW (editwindow), TRUE);
gtk_window_set_type_hint (GTK_WINDOW (editwindow), GDK_WINDOW_TYPE_HINT_DIALOG);
@@ -2462,20 +2458,10 @@ servlist_open_edit (GtkWidget *parent, ircnet *net)
vbox5 = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
gtk_container_add (GTK_CONTAINER (editwindow), vbox5);
/* Keep Close accessible on small screens, while the lists still receive
all spare space when the complete form fits in the window. */
content_scroll = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (content_scroll),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_propagate_natural_width (GTK_SCROLLED_WINDOW (content_scroll), TRUE);
gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (content_scroll), TRUE);
gtk_box_pack_start (GTK_BOX (vbox5), content_scroll, TRUE, TRUE, 0);
content_box = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
gtk_container_add (GTK_CONTAINER (content_scroll), content_box);
/* Tabs and buttons */
hbox1 = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
gtk_box_pack_start (GTK_BOX (content_box), hbox1, TRUE, TRUE, 4);
gtk_box_pack_start (GTK_BOX (vbox5), hbox1, TRUE, TRUE, 4);
scrolledwindow2 = gtk_scrolled_window_new (NULL, NULL);
scrolledwindow4 = gtk_scrolled_window_new (NULL, NULL);
@@ -2613,7 +2599,7 @@ servlist_open_edit (GtkWidget *parent, ircnet *net)
/* Checkboxes and entries */
table3 = gtkutil_grid_new (19, 2, FALSE);
gtk_box_pack_start (GTK_BOX (content_box), table3, FALSE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox5), table3, FALSE, FALSE, 0);
gtk_grid_set_row_spacing (GTK_GRID (table3), 2);
gtk_grid_set_column_spacing (GTK_GRID (table3), 8);
@@ -2842,9 +2828,7 @@ servlist_open_networks (void)
gtk_container_set_border_width (GTK_CONTAINER (servlist), 4);
g_snprintf(buf, sizeof(buf), _("Network List - %s"), _(DISPLAY_NAME));
gtk_window_set_title (GTK_WINDOW (servlist), buf);
gtk_window_set_default_size (GTK_WINDOW (servlist),
netlist_win_width > 0 ? netlist_win_width : -1,
netlist_win_height > 0 ? netlist_win_height : -1);
gtk_window_set_default_size (GTK_WINDOW (servlist), netlist_win_width, netlist_win_height);
gtk_window_set_role (GTK_WINDOW (servlist), "servlist");
gtk_window_set_type_hint (GTK_WINDOW (servlist), GDK_WINDOW_TYPE_HINT_DIALOG);
if (current_sess)
+8 -14
View File
@@ -1256,7 +1256,7 @@ setup_create_id_menu (GtkWidget *table, char *label, int row, char *dest)
static void
setup_create_menu (GtkWidget *table, int row, const setting *set)
{
GtkWidget *wid, *cbox;
GtkWidget *wid, *cbox, *box;
const char **text = (const char **)set->list;
int i;
@@ -1277,7 +1277,9 @@ setup_create_menu (GtkWidget *table, int row, const setting *set)
g_signal_connect (G_OBJECT (cbox), "changed",
G_CALLBACK (setup_menu_cb), (gpointer)set);
setup_table_attach (table, cbox, 3, 6, row, row + 1, TRUE, FALSE,
box = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box), cbox, 0, 0, 0);
setup_table_attach (table, box, 3, 4, row, row + 1, TRUE, FALSE,
SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);
}
@@ -1961,8 +1963,7 @@ setup_add_page (const char *title, GtkWidget *book, GtkWidget *tab)
sw = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new (NULL, NULL));
gtk_scrolled_window_set_shadow_type (sw, GTK_SHADOW_IN);
gtk_scrolled_window_set_policy (sw, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_propagate_natural_width (sw, TRUE);
gtk_scrolled_window_set_policy (sw, GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_container_add (GTK_CONTAINER (sw), vvbox);
viewport = gtk_bin_get_child (GTK_BIN(sw));
@@ -2006,9 +2007,7 @@ setup_ensure_page_created (int page)
}
page_widget = setup_page_factories[page] (setup_page_settings[page]);
/* Lazy pages must pass the notebook's spare space to their content,
particularly the independently scrolling Sounds list. */
gtk_box_pack_start (GTK_BOX (container), page_widget, TRUE, TRUE, 0);
gtk_container_add (GTK_CONTAINER (container), page_widget);
gtk_widget_show_all (container);
}
@@ -2186,14 +2185,9 @@ setup_create_tree (GtkWidget *box, GtkWidget *book)
-1, _("Categories"), renderer, "text", 0, NULL);
gtk_tree_view_expand_all (GTK_TREE_VIEW (tree));
frame = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (frame),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (frame), GTK_SHADOW_IN);
gtk_scrolled_window_set_propagate_natural_width (GTK_SCROLLED_WINDOW (frame), TRUE);
gtk_widget_set_vexpand (frame, TRUE);
frame = gtk_frame_new (NULL);
gtk_container_add (GTK_CONTAINER (frame), tree);
gtk_box_pack_start (GTK_BOX (box), frame, FALSE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (box), frame, 0, 0, 0);
gtk_box_reorder_child (GTK_BOX (box), frame, 0);
if (sel_iter)
+7 -10
View File
@@ -400,6 +400,7 @@ pevent_treeview_new (GtkWidget *box)
scroll = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_IN);
gtk_widget_set_size_request (GTK_WIDGET (scroll), -1, 250);
store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
g_return_val_if_fail (store != NULL, NULL);
@@ -436,7 +437,7 @@ pevent_treeview_new (GtkWidget *box)
gtk_tree_view_column_set_min_width (col, 100);
gtk_container_add (GTK_CONTAINER (scroll), view);
gtk_paned_pack1 (GTK_PANED (box), scroll, TRUE, FALSE);
gtk_container_add (GTK_CONTAINER (box), scroll);
return view;
}
@@ -480,7 +481,7 @@ pevent_hlist_treeview_new (GtkWidget *box)
gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
gtk_container_add (GTK_CONTAINER (scroll), view);
gtk_paned_pack2 (GTK_PANED (box), scroll, TRUE, FALSE);
gtk_container_add (GTK_CONTAINER (box), scroll);
return view;
}
@@ -488,7 +489,7 @@ pevent_hlist_treeview_new (GtkWidget *box)
void
pevent_dialog_show ()
{
GtkWidget *vbox, *hbox, *wid, *pane, *preview_pane;
GtkWidget *vbox, *hbox, *wid, *pane;
XTextColor xtext_palette[XTEXT_COLS];
if (pevent_dialog)
@@ -502,12 +503,8 @@ pevent_dialog_show ()
TRUE, FALSE, pevent_dialog_close, NULL,
600, 455, &vbox, 0);
preview_pane = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
gtk_box_pack_start (GTK_BOX (vbox), preview_pane, TRUE, TRUE, 0);
pane = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
gtk_paned_pack1 (GTK_PANED (preview_pane), pane, TRUE, FALSE);
gtk_paned_set_position (GTK_PANED (pane), 220);
gtk_paned_set_position (GTK_PANED (preview_pane), 330);
gtk_box_pack_start (GTK_BOX (vbox), pane, TRUE, TRUE, 0);
pevent_dialog_list = pevent_treeview_new (pane);
pevent_dialog_fill (pevent_dialog_list);
@@ -516,12 +513,12 @@ pevent_dialog_show ()
wid = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (wid), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (wid), 75);
gtk_paned_pack2 (GTK_PANED (preview_pane), wid, TRUE, FALSE);
gtk_box_pack_start (GTK_BOX (vbox), wid, FALSE, TRUE, 0);
theme_get_xtext_colors_for_widget (wid, xtext_palette, XTEXT_COLS);
pevent_dialog_twid = gtk_xtext_new (xtext_palette, 0);
gtk_widget_set_sensitive (pevent_dialog_twid, FALSE);
gtk_widget_set_size_request (pevent_dialog_twid, -1, 75);
gtk_container_add (GTK_CONTAINER (wid), pevent_dialog_twid);
gtk_xtext_set_font (GTK_XTEXT (pevent_dialog_twid), prefs.hex_text_font);
g_object_set_data (G_OBJECT (pevent_dialog), "xtext", pevent_dialog_twid);
-1
View File
@@ -1911,7 +1911,6 @@ theme_preferences_create_page (GtkWindow *parent,
G_TYPE_STRING,
G_TYPE_INT);
ui->gtk3_combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (gtk3_store));
gtk_widget_set_hexpand (ui->gtk3_combo, TRUE);
g_object_unref (gtk3_store);
renderer = gtk_cell_renderer_text_new ();
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (ui->gtk3_combo), renderer, TRUE);
-2
View File
@@ -90,7 +90,6 @@
<MSWindowsTheme Include="$(DepsRoot)\share\themes\MS-Windows\**\*" />
<HicolorIcons Include="$(DepsRoot)\share\icons\hicolor\index.theme" />
<HicolorIcons Include="$(DepsRoot)\share\icons\hicolor\**\*.*" />
<PythonAdditiveApis Include="..\..\plugins\python\_zoitechat_*.py" />
</ItemGroup>
<Copy SourceFiles="@(None)" DestinationFolder="$(ZoiteChatRel)" />
@@ -116,7 +115,6 @@
<Copy SourceFiles="..\..\plugins\python\hexchat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" />
<Copy SourceFiles="..\..\plugins\python\zoitechat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" />
<Copy SourceFiles="..\..\plugins\python\_zoitechat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" />
<Copy SourceFiles="@(PythonAdditiveApis)" DestinationFolder="$(ZoiteChatRel)\python" Condition="('$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true') and '@(PythonAdditiveApis)' != ''" />
<WriteLinesToFile File="$(ZoiteChatRel)portable-mode" Lines="2" Overwrite="true" />